Skip to content

Commit 7058a18

Browse files
authored
Merge pull request #3 from github/readme-dedupe-live-docs
2 parents 47c12a7 + 503f186 commit 7058a18

2 files changed

Lines changed: 26 additions & 83 deletions

File tree

‎README.md‎

Lines changed: 20 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,8 @@
11
# External custom properties example server
22

3-
This guide walks you through setting up a GitHub App that writes **external custom properties** to repositories in your organization using the external custom properties API.
3+
A runnable GitHub App that writes external custom properties to repositories in an organization.
44

5-
External custom properties allow third-party integrations (GitHub Apps) to attach metadata to repositories — such as deployment environment, owning team, service tier, compliance status, or any custom key-value data your tooling produces. These properties are scoped to a **namespace** owned by the app that created them and are visible to anyone who can view custom properties in the organization, alongside native custom properties.
6-
7-
---
8-
9-
## How it works: namespaces and registration
10-
11-
Before an app can write external custom properties, its installation must **register a namespace** in the organization. A namespace is identified by a **display name** you choose (for example, `acme`). Once registered:
12-
13-
- Every property the app writes is surfaced in GitHub as `<display_name>.<property_name>` — e.g. `acme.environment`.
14-
- The namespace is **scoped to the app installation** within that org. Properties written by App A are separate from those written by App B.
15-
- Registration is a **one-time, immutable** step per installation.
16-
- Uninstalling the GitHub App unregisters it and removes the external custom properties it created.
17-
18-
The typical calling pattern is:
19-
20-
1. **Register** the namespace (`POST /orgs/{org}/properties/installations`).
21-
2. **Batch write** values for one or more repositories (`PATCH /orgs/{org}/properties/installations/values`).
22-
3. Optionally **create, update, or unset one named property across selected repositories** (`PATCH /orgs/{org}/properties/installations/values/{property_name}`).
23-
4. Optionally **read registrations or property definitions** to verify setup.
24-
5. Read values through the existing REST or GraphQL custom property APIs. External custom property names are namespace-qualified in these responses, for example `acme.environment`.
25-
26-
Writing values **before** registering returns an error. Who performs step 1 depends on the app's permission level — see [Permissions and who registers](#permissions-and-who-registers).
27-
28-
For the display name, property name, and value constraints, see the [REST API reference](#api-reference).
5+
**New to external custom properties?** Start with [Integrating custom properties with an external system](https://docs.github.com/en/organizations/managing-organization-settings/sync-external-custom-properties), which explains what they are, how namespacing and registration work, and the permissions an app needs. This README covers configuring and running *this* server.
296

307
---
318

@@ -62,43 +39,11 @@ If you're new to building GitHub Apps, the [Quickstart for building GitHub Apps]
6239

6340
## Permissions and who registers
6441

65-
Access to each endpoint is controlled by the organization permission **"External custom properties for repositories"**, which has three levels: **Read**, **Write**, and **Admin**.
66-
67-
| Action | Endpoint | Permission required |
68-
|--------|----------|---------------------|
69-
| Get registered GitHub App installations | `GET /orgs/{org}/properties/installations` | **Admin** |
70-
| Register a GitHub App installation | `POST /orgs/{org}/properties/installations` | **Admin** |
71-
| Get external custom property definitions | `GET /orgs/{org}/properties/installations/schema` | **Read** |
72-
| Create or update external custom property values for repositories | `PATCH /orgs/{org}/properties/installations/values` | **Write** |
73-
| Create or update an external custom property value for repositories | `PATCH /orgs/{org}/properties/installations/values/{property_name}` | **Write** |
74-
| Remove one property across all organization repositories | `DELETE /orgs/{org}/properties/installations/values/{property_name}` | **Write** |
75-
76-
There are two ways to register, depending on how much control the org wants to delegate to the app:
42+
Before an app can write values, its installation must be registered with a display name. The app can do this itself, or an organization administrator can do it on the app's behalf — which one applies depends on the permission level the app is granted.
7743

78-
### Self-service apps (this guide's default)
44+
**This example server registers itself**, so it asks for **Admin** on the "External custom properties for repositories" organization permission.
7945

80-
Grant the app **Admin**. When the app is installed, it **registers its own namespace** using its installation access token, then writes values. This is what the example server does.
81-
82-
### Write-only apps (org-admin-controlled namespacing)
83-
84-
If you prefer that an org owner keep full control over namespacing rather than delegating it to the app, grant the app only **Write**. In this model:
85-
86-
- The app **cannot** register itself — the registration endpoint rejects a Write-only installation.
87-
- An **org admin**, or a user/token holding the `organization_external_properties_for_repos:admin` fine-grained permission, calls the registration endpoint **on the app's behalf**, passing the app's `installation_id`.
88-
- Until the installation is registered, the app's value writes are rejected as not registered.
89-
90-
To register a Write-only app's installation as an org admin, first find the installation ID (`GET /orgs/{org}/installations`), then call:
91-
92-
```bash
93-
curl -X POST \
94-
-H "Authorization: Bearer YOUR_FINE_GRAINED_PAT" \
95-
-H "Accept: application/vnd.github+json" \
96-
-H "X-GitHub-Api-Version: 2022-11-28" \
97-
-d '{"installation_id": 42, "display_name": "acme"}' \
98-
https://api.github.com/orgs/YOUR_ORG/properties/installations
99-
```
100-
101-
The caller must be authorized for **Admin** on "External custom properties for repositories" in the org — this can be an **organization owner/admin**, or any user or fine-grained personal access token granted the `organization_external_properties_for_repos:admin` permission. Once registered, the Write-only app can write values normally.
46+
For the full permission model — including when to choose Admin versus Read and write, and who can register on an app's behalf — see [Selecting permissions](https://docs.github.com/en/organizations/managing-organization-settings/sync-external-custom-properties#selecting-permissions). The access level and token types each endpoint requires are listed in [Permissions required for GitHub Apps](https://docs.github.com/en/rest/authentication/permissions-required-for-github-apps#organization-permissions-for-external-custom-properties-for-repositories).
10247

10348
---
10449

@@ -135,7 +80,7 @@ For full details on app registration, see [Registering a GitHub App](https://doc
13580
4. Set permissions:
13681
- Under **Organization permissions**, find **"External custom properties for repositories"** and select **Admin**.
13782

138-
> **Why Admin?** This example server registers its own namespace on installation, which requires Admin. If you instead want an org owner to control namespacing, select **Write** and follow [Write-only apps](#write-only-apps-org-admin-controlled-namespacing) — an org admin registers the app before it can write values.
83+
> **Why Admin?** This example server registers its own namespace on installation, which requires Admin. If you'd rather an org owner control namespacing, select **Read and write** instead — see [Selecting permissions](https://docs.github.com/en/organizations/managing-organization-settings/sync-external-custom-properties#selecting-permissions).
13984
14085
5. Subscribe to events:
14186
- Check the **Installation** event (this fires when the app is installed on an org)
@@ -174,7 +119,7 @@ For full details on app registration, see [Registering a GitHub App](https://doc
174119
- **APP_ID**: The App ID from your app's settings page
175120
- **WEBHOOK_SECRET**: The webhook secret you chose in Step 2
176121
- **PRIVATE_KEY_PATH**: Path to the `.pem` file you downloaded
177-
- **DISPLAY_NAME**: The namespace display name to register (1–15 alphanumeric characters, unique in the org). Property values appear as `<DISPLAY_NAME>.<property_name>`.
122+
- **DISPLAY_NAME**: The namespace display name to register. Properties appear in GitHub as `<DISPLAY_NAME>.<property_name>`. For the length and character rules, see [Register an app installation for external custom properties](https://docs.github.com/en/rest/orgs/custom-properties#register-an-app-installation-for-external-custom-properties).
178123

179124
5. Move the downloaded `.pem` file into the `example-server/` directory (or update the path in `.env` to point to its location).
180125

@@ -232,9 +177,9 @@ Periodic sync scheduled every 60 minutes
232177
1. From your app's settings page, click **"Public page"** (in the left sidebar)
233178
2. Click **Install**
234179
3. Select your organization
235-
4. Choose repository access:
236-
- Since the External custom properties permission operates at the organization level, the app will be installed with access to **All repositories** by default. You won't see an option to select individual repos unless the app also has repository-level permissions.
237-
5. Click **Install**
180+
4. Click **Install**
181+
182+
> **Note:** You won't be asked to pick individual repositories. See [Install the app](https://docs.github.com/en/organizations/managing-organization-settings/sync-external-custom-properties#4-install-the-app) for why.
238183
239184
When the installation completes, GitHub sends an `installation.created` webhook event to your configured webhook URL. The example server registers the namespace, writes external custom properties to the org's first repository, and reads the schema back.
240185

@@ -262,20 +207,16 @@ When the installation completes, GitHub sends an `installation.created` webhook
262207

263208
## API Reference
264209

265-
The External custom properties REST endpoints are documented in the GitHub REST API reference. Each link below covers the full request body, responses, and error cases for that endpoint.
266-
267-
| # | Endpoint | Reference |
268-
|---|----------|-----------|
269-
| 1 | `GET /orgs/{org}/properties/installations` | [Get registered app installations for external custom properties](https://docs.github.com/en/rest/orgs/custom-properties#get-registered-app-installations-for-external-custom-properties) |
270-
| 2 | `POST /orgs/{org}/properties/installations` | [Register an app installation for external custom properties](https://docs.github.com/en/rest/orgs/custom-properties#register-an-app-installation-for-external-custom-properties) |
271-
| 3 | `GET /orgs/{org}/properties/installations/schema` | [Get all external custom properties for a GitHub App installation in an organization](https://docs.github.com/en/rest/orgs/custom-properties#get-all-external-custom-properties-for-a-github-app-installation-in-an-organization) |
272-
| 4 | `PATCH /orgs/{org}/properties/installations/values` | [Create or update external custom property values for organization repositories](https://docs.github.com/en/rest/orgs/custom-properties#create-or-update-external-custom-property-values-for-organization-repositories) |
273-
| 5 | `PATCH /orgs/{org}/properties/installations/values/{property_name}` | [Create or update external custom property values for a property across organization repositories](https://docs.github.com/en/rest/orgs/custom-properties#create-or-update-external-custom-property-values-for-a-property-across-organization-repositories) |
274-
| 6 | `DELETE /orgs/{org}/properties/installations/values/{property_name}` | [Remove all external custom property values for a property across all organization repositories](https://docs.github.com/en/rest/orgs/custom-properties#remove-all-external-custom-property-values-for-a-property-across-all-organization-repositories) |
275-
276-
Request limits, display name rules, property name and value constraints, and cleanup behavior are all documented alongside these endpoints in the [REST API reference for organization custom properties](https://docs.github.com/en/rest/orgs/custom-properties).
210+
Each endpoint links to its entry in the [REST API reference](https://docs.github.com/en/rest/orgs/custom-properties), which documents the request body, responses, and error cases for that endpoint. Between them, those entries also cover display name rules, property name and value constraints, request size limits, and cleanup behavior. The access level and token types each endpoint requires are listed in [Permissions required for GitHub Apps](https://docs.github.com/en/rest/authentication/permissions-required-for-github-apps#organization-permissions-for-external-custom-properties-for-repositories). The [setup guide](https://docs.github.com/en/organizations/managing-organization-settings/sync-external-custom-properties#3-create-the-automation) lists them in the order an integration typically calls them, and each has a matching helper in [`example-server/app.js`](example-server/app.js).
277211

278-
Each endpoint maps to a helper function in [`example-server/app.js`](example-server/app.js), so you can see a working call for every one of them.
212+
| Endpoint | Helper function |
213+
|----------|-----------------|
214+
| [`POST /orgs/{org}/properties/installations`](https://docs.github.com/en/rest/orgs/custom-properties#register-an-app-installation-for-external-custom-properties) | `registerNamespace` |
215+
| [`GET /orgs/{org}/properties/installations`](https://docs.github.com/en/rest/orgs/custom-properties#get-registered-app-installations-for-external-custom-properties) | `getRegisteredInstallations` |
216+
| [`GET /orgs/{org}/properties/installations/schema`](https://docs.github.com/en/rest/orgs/custom-properties#get-all-external-custom-properties-for-a-github-app-installation-in-an-organization) | `readOrgSchema` |
217+
| [`PATCH /orgs/{org}/properties/installations/values`](https://docs.github.com/en/rest/orgs/custom-properties#create-or-update-external-custom-property-values-for-organization-repositories) | `writeExternalCustomProperties` |
218+
| [`PATCH /orgs/{org}/properties/installations/values/{property_name}`](https://docs.github.com/en/rest/orgs/custom-properties#create-or-update-external-custom-property-values-for-a-property-across-organization-repositories) | `updateExternalCustomPropertyValues` |
219+
| [`DELETE /orgs/{org}/properties/installations/values/{property_name}`](https://docs.github.com/en/rest/orgs/custom-properties#remove-all-external-custom-property-values-for-a-property-across-all-organization-repositories) | `deleteExternalCustomPropertyValues` |
279220

280221
---
281222

@@ -292,7 +233,7 @@ For working examples of both, see `readRepositoryCustomPropertyValues`, `readRep
292233

293234
## Troubleshooting
294235

295-
Error responses and their meanings are documented with each endpoint in the [REST API reference](#api-reference).
236+
Error responses and their meanings are documented with each endpoint in the [REST API reference](https://docs.github.com/en/rest/orgs/custom-properties).
296237

297238
### Webhook not received
298239

‎example-server/app.js‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,9 @@ async function registerNamespace(octokit, org) {
156156
`This app installation cannot register itself in ${org} (it needs ADMIN on ` +
157157
`"External custom properties for repositories"). An org admin — or a user/token with the ` +
158158
`organization_external_properties_for_repos:admin fine-grained permission — must call the ` +
159-
`registration endpoint with this installation's installation_id (see the README, ` +
160-
`"Write-only apps"). Value writes will fail until the installation is registered.`
159+
`registration endpoint with this installation's installation_id. See ` +
160+
`https://docs.github.com/en/organizations/managing-organization-settings/sync-external-custom-properties#selecting-permissions. ` +
161+
`Value writes will fail until the installation is registered.`
161162
);
162163
return false;
163164
}
@@ -205,7 +206,7 @@ async function writeExternalCustomProperties(octokit, org, repositoryNames, prop
205206
if (isNotRegisteredError(error)) {
206207
console.warn(
207208
`Cannot write properties to ${org}: the app installation is not registered yet. ` +
208-
`Register the namespace first (or wait for an org admin to register a write-only app).`
209+
`Register the namespace first (or wait for an org admin to register an app that only has Read and write).`
209210
);
210211
return false;
211212
}
@@ -505,7 +506,8 @@ app.webhooks.onError((error) => {
505506
// --- Periodic sync ---
506507
// Re-applies properties on a schedule to catch drift or newly added repos. Assumes the
507508
// installation is already registered (from the installation.created flow, or by an org admin for
508-
// write-only apps). If it is not yet registered, writeExternalCustomProperties logs a clear message.
509+
// apps that only have Read and write). If it is not yet registered, writeExternalCustomProperties
510+
// logs a clear message.
509511

510512
async function periodicSync() {
511513
console.log(`[Sync] Running periodic sync...`);

0 commit comments

Comments
 (0)