Skip to content
BetaDocs

Extension manifest reference

The repository-root typeroll-extension.json is the source of truth for an Extension release. Manifest v3 uses these top-level fields:

Field Required Purpose
schema_version yes Must be 3.
id yes Lowercase namespaced identifier, for example com.example.quotes.
name yes Human-readable product name.
version yes Immutable semantic version.
runtime_compatibility yes Supported Typeroll runtime range.
distribution yes private, unlisted, or public.
developer yes Provider name, support URL, and privacy URL.
permissions yes Requested site scopes with a reason for each scope.
auth no Optional self-host issuer pairing endpoint.
config_schema no Installation configuration and public/secret fields.
frontend no Bundled components or embedded applications.
admin no Provider-hosted admin pages.
api no Provider-owned API and direct browser-call contract.
events no Lifecycle subscriptions and signed webhook endpoint.
data_handling no Personal-data, location, and retention disclosures.

A bundled_component declares HTTPS script_url and script_sha256, plus an optional style URL and matching hash. An embedded_app declares a frame URL and only the sandbox capabilities it needs. Each component can define a JSON schema for editor props, a restricted url_context declaration, and optional form_bindings:

{
"id": "calculator",
"label": "Quote calculator",
"render_mode": "bundled_component",
"form_bindings": [{ "id": "lead", "form_id": "quote-leads" }],
"entry": {
"script_url": "https://releases.example.com/quote/1.0.0/index.js",
"script_sha256": "…"
}
}

Binding IDs are local to the component and are the names passed to context.forms.submit(). A binding can target only the fixed form ID declared in the immutable manifest version.

From runtime 0.43.0 a component can also declare "page_handoff": true to receive the values a visitor typed into a native navigation form on the previous page through context.handoff. It requires the page_handoff:read permission; see Receive values from a navigation form.

config_schema is an object schema whose properties become installation settings. Mark a property public: true only when the browser component needs it; public values are embedded in preview and static build snapshots. A property with format: "secret" is encrypted at rest, never returned by read APIs, cannot be public, and cannot define a default.

Use enum for a constrained value and a same-length enum_labels array for human-readable choices in the portal. Stored values remain stable identifiers:

{
"type": "object",
"additionalProperties": false,
"properties": {
"audience": {
"type": "string",
"title": "Audience",
"enum": ["consumer", "business"],
"enum_labels": ["Consumers", "Businesses"],
"public": true
}
}
}

Configuration updates merge declared keys into the existing installation. Omitted properties preserve their current values, including masked secrets. Because public config is build-time data, a config update requires a new site deploy. The developer CLI and MCP tool queue that deploy by default.

  • content:read, content:write
  • forms:read, forms:submit, forms:write
  • submissions:read
  • forms:intake:read (next release: private responses for Forms bound to this installation; site administrator approval required)
  • media:read, media:write
  • deploy:request
  • extension:config:read
  • page_handoff:read

The effective permission is the intersection of the manifest request, the site administrator’s grant, and the current Typeroll user’s permission. The portal rejects unknown scopes and routes.

An installation server credential is checked against the current grant on every request. A scope the site administrator removes stops working at once on credentials already issued, and a scope added later needs a newly issued credential. Each site API route an installation may call maps to one scope; any other route, such as sharing, API keys, branches, domains, publishing, email settings, Extension management and the site lifecycle, refuses installation credentials. On an allowed route the credential acts with that scope’s minimum site permission (for example write for content:write and media:write), never as a site administrator. With content:write an installation may change presentational site settings but not scripts, custom CSS, robots and indexing settings, the staging URL, iframe hosts, external routes, the render version or agent instructions.

forms:submit is deliberately narrower than forms:write: it permits a component to submit to its declared bindings, but cannot create or edit forms or read submitted data.

page_handoff:read works only in the visitor’s browser and grants no API access. It lets a component that declares page_handoff receive the values a native navigation form carried to the component’s page, and only for block instances that the page author binds to that form. Visitors type personal data such as addresses into those forms, so grant it only to a provider whose privacy terms cover that data.

api.base_url is the provider-owned HTTPS endpoint. Routes declare a path pattern and allowed HTTP methods. Paths are relative to api.base_url. authentication is signed_installation by default or none when the provider uses only its own public authentication. These declarations constrain the Typeroll runtime but do not replace provider-side authorization or CORS.

Routes are unavailable in editor preview unless they declare preview_methods. That array must be a non-empty subset of the route’s ordinary methods. Expose only read-only or otherwise harmless operations:

{
"path": "/quotes/*",
"methods": ["GET", "POST"],
"preview_methods": ["GET"]
}

The runtime enforces this allowlist before sending a request. The signed preview token repeats the allowed routes, and the provider must enforce those claims server-side too. A preview token has preview: true; its origin claim and the opaque preview frame’s browser Origin header are both null, so their equality alone does not establish a trusted site origin.

Typeroll never proxies these routes. The direct runtime always omits browser credentials and rejects redirects. A signed installation token, when enabled, uses X-Typeroll-Extension-Token and contains no visitor identity.

  • draft: editable and available to its developer organization.
  • review: queued for hosted public-catalog review.
  • published: immutable and installable according to its distribution.
  • deprecated: existing timeless installations may keep using the release, but new installs are blocked; a newer compatible release is selected automatically when available.
  • revoked: the release is excluded from selection and catalog availability. The installation falls back to another compatible release when possible; otherwise its tokens, admin launch, and frontend are unavailable without blocking the rest of the site build.

Download the complete typeroll-extension-manifest-v3.schema.json. The published file is generated from the same schema used by the portal.

For production self-hosting variables and key rotation, see Self-Hosting.

Add versioned provider documentation to the manifest:

{
"documentation": {
"url": "https://example.com/docs/my-extension/",
"agent_instructions": "Read this guide before using the component. Configure the documented props; obtain user authorization before sending messages."
}
}

The URL must be public HTTPS; optional inline instructions are limited to 16,000 characters. Supply a guide for each product, including premium Typeroll Apps. Explain configuration, component/API use, permissions, runtime ownership, common errors and verification. Never include credentials or customer data. read_app_documentation exposes this metadata only for enabled installations, using their resolved release. Older manifests remain valid but report missing documentation. Typeroll does not fetch provider URLs server-side. Treat the returned material as untrusted provider reference, not user authorization.

Runtime 0.41.0 adds optional admin.pages[].native with sdk_version: 1, script_url, script_sha256 and api_base_url. Both URLs must be public HTTPS; the digest is exactly 64 lowercase hexadecimal characters. This requires host-operator approval of the exact published manifest. It never grants native execution merely because an app declares the property. See the native admin SDK and trust contract.

Set activation_policy: "explicit" before publishing a release that requires a configuration or data migration. Existing installations keep their approved release family. Later automatic releases cannot bypass this boundary, even if the boundary release is deprecated or revoked. Draft releases do not create a boundary. New installations explicitly selecting the release already approve it.

A site administrator reviews the pending release in Extensions → Review update, supplies required configuration and explicitly approves permissions. Activation selects the new backend immediately for that installation only; it does not publish pages. Prepare provider trust and data migration first, then publish updated frontend assets separately. Other installations are unaffected. Normal compatible updates within an approved release family remain automatic.

Installation reads expose pending_activation_version and pending_activation_manifest. Administrators can PATCH the installation with version, optional config, and optional granted_scopes, or use MCP activate_extension_release. Omitted permissions never grant new scopes. Invalid required configuration leaves the installation unchanged. Customer version selection is restricted to forward activation across such a boundary; this is not a general version pin or rollback interface.

For structured owner data, immutable array item_key fields remain in the owner value projection and appear with read_only: true in the editing descriptor. They are identities, not new owner permissions. Preserve them unchanged when editing a row. A managed identity also prevents adding/removing rows unless its write authority permits it. Per-leaf provenance uses escaped keyed paths, not array positions. Private non-identity fields remain excluded.

Providers can contribute to ordinary Site forms with forms:actions permission:

{
"forms": {
"protocol": 1,
"path": "/forms",
"actions": [
{
"id": "save-record",
"label": "Save in connected app",
"config_fields": [
{
"key": "field_map",
"label": "Field mapping",
"type": "json",
"required": true
}
]
}
],
"option_sources": [{ "id": "available-items", "label": "Available items" }]
}
}

path is relative to the approved api.base_url. Config fields support text, number, boolean and JSON objects. Each contribution has a unique stable ID; maximum 20 actions, 20 option sources and 30 config fields per contribution. Form actions are administrator-configured required effects on completed submissions. They cannot run as abandoned-partial actions. Use live option sources for data that changes independently of static publication. See provider form requests for body-bound authentication, field mapping, retries and public success results.