> ## Documentation Index
> Fetch the complete documentation index at: https://voyant.travel/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Build an admin UI extension

> Publish sandboxed admin UI extensions, version them, and install them into a Voyant organization.

Admin UI extensions are static bundles rendered by the admin runtime in sandboxed
iframes. Each published version declares the slots it can mount into and the
admin UI extension API range it expects.

## Manifest

Every bundle includes a manifest validated as
`voyant.extension-manifest.v1`:

```json theme={null}
{
  "schemaVersion": "voyant.extension-manifest.v1",
  "key": "booking-tools",
  "displayName": "Booking tools",
  "description": "Adds booking review shortcuts.",
  "version": "1.0.0",
  "extensionApi": "^1",
  "entry": "index.html",
  "targets": [{ "slot": "booking.details.after-summary" }],
  "configSchema": {
    "type": "object",
    "properties": {
      "label": { "type": "string" }
    }
  }
}
```

| Field          | Notes                                                                                                                                                                                                                                                                                                                 |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `key`          | Globally unique. Lowercase letters, numbers, and dashes; 1-64 characters.                                                                                                                                                                                                                                             |
| `version`      | Semver. A key can publish each version once.                                                                                                                                                                                                                                                                          |
| `extensionApi` | Compatibility range. `^1`, `^1.2`, `^1.2.3`, `1.x`, and exact versions are supported.                                                                                                                                                                                                                                 |
| `entry`        | Relative path inside the bundle. Absolute paths and `..` segments are rejected.                                                                                                                                                                                                                                       |
| `targets`      | One to four target slots.                                                                                                                                                                                                                                                                                             |
| `configSchema` | Optional JSON Schema subset used to validate install config. Cloud supports object schemas with `properties` of type `string`, `number`, or `boolean`, optional `enum` arrays, and optional `required` string arrays. Unknown config keys are rejected. If `configSchema` is omitted, installs cannot include config. |

## Slots

Supported slots:

| Slot                            |
| ------------------------------- |
| `dashboard.header`              |
| `dashboard.after-kpis`          |
| `dashboard.footer`              |
| `booking.details.header`        |
| `booking.details.after-summary` |
| `invoice.details.header`        |
| `invoice.details.after-summary` |
| `workspace.header.actions`      |

## Publish

Use `voyant publish` to upload a gzipped tarball and its manifest to Cloud. The
compressed bundle must be 5 MB or smaller and must expand to 20 MB or smaller.
During upload Cloud extracts the tarball,
rejects absolute paths and path traversal, verifies the manifest entry exists,
and stores the versioned files as immutable R2 objects.

Published files are served from:

```text theme={null}
/services/v1/extension-bundles/{key}/{version}/{path}
```

HTML responses include `Content-Security-Policy: frame-ancestors *` because
extension documents are token-free static content rendered in sandboxed iframes.

## Versioning

Versions are immutable. Publish a new semver version for every bundle change,
then update installs to pin that version. If no version is provided during
install, Cloud installs the newest published version.

At install time, embedded admin sessions can carry
`adminUiExtensionApiVersion`. Cloud rejects installs when the runtime version
does not satisfy the selected version's `extensionApi` range.

## Visibility and installs

Extensions start as `private`. Owners can set visibility to `private` or
`unlisted`; `listed` is reserved for platform operations in v1.

Install rules:

| Visibility | Installable by                |
| ---------- | ----------------------------- |
| `private`  | Owner organization only       |
| `unlisted` | Owner organization only in v1 |
| `listed`   | Any organization              |

An install is organization-scoped, pinned to a specific extension version, can be
enabled or disabled, and can store optional config matching the selected
version's `configSchema`. Versions without `configSchema` do not accept install
config.
