Manifest reference
Every module declares a module-manifest.json at its root. tv-sdk validate checks it against the Zod schema in @tv/extension-sdk/manifest — that is the authoritative gate, the same one CI and the registry run.
Validate
npx @tv/extension-sdk validate module-manifest.json
# exit 0 valid, 1 invalid, 2 usage errormanifest.schema.json for editor validation
The package also ships manifest.schema.json. From 1.1.0 it is generated in input mode — describing what you are allowed to write — so it agrees with tv-sdk validate and is safe to wire into your editor:
"$schema": "./node_modules/@tv/extension-sdk/manifest.schema.json"On 1.0.x it was emitted in output mode, marking every defaulted field required; it rejected 30 of the 31 modules then shipping, so leave $schema out if you're pinned below 1.1.0.
Gate CI on tv-sdk validate, not on the JSON Schema — the validator is the contract the registry enforces, and the schema is generated from it.
Top-level fields
| Field | Required | Description |
|---|---|---|
id | yes | @vendor/module-name — globally unique. Use your own scope, not @tv. |
name | yes | Human-readable display name shown in the console |
version | yes | Semver of your module |
minCoreVersion | yes | Minimum tv-api version, e.g. >=2.0.0 |
category | yes | core / operations / engagement / infrastructure / analytics / ai |
sdkVersion | yes | SDK version you authored against, e.g. 1.1.0. The platform rejects manifests targeting a newer SDK than it runs. |
buildingTypes | yes | ["all"] or specific types like ["mall","office"] |
capabilities | yes | provides + requires — named capability contracts between modules |
permissions | yes | Data domains you read/write (see below) |
events | yes | publishes + subscribes arrays |
mcpTools | yes | Copilot/MCP tools your module contributes. [] if none. |
lifecycle | yes | healthEndpoint, init, dependencies |
description | no | One-liner shown in the catalog |
maxCoreVersion | no | Upper bound, if you know you break above one |
mcpEndpoint | no | Cluster-internal URL the platform calls for your MCP tools |
ui | when you have UI | routes + navigation entries |
author | no | name, email, url |
Defaults exist for capabilities, events, mcpTools, lifecycle, and buildingTypes, so tv-sdk validate accepts a manifest that omits them — but declare them explicitly. It's the difference between "I have no events" and "I forgot to think about events", and a reviewer can't tell those apart.
Permissions
"permissions": [
{
"subject": "building.spaces",
"actions": ["read"],
"reason": "Lists spaces on the module's landing page."
}
]reason is optional to the validator and mandatory in practice — it's shown verbatim to the admin approving your install.
The catalog
Subjects and actions come from tv-api's permission registry, shipped with the SDK as permissions.snapshot.json. The full list — every subject, every action, and the roles each action is granted to — is rendered from that same snapshot on the Permission catalog page, so it cannot drift from what the platform enforces. That page also lists the reserved first-party-only prefixes.
Read it from the package directly if you'd rather not leave the terminal:
cat node_modules/@tv/extension-sdk/permissions.snapshot.json | jq '.subjects[].subject'UI
"ui": {
"remoteEntry": "./Shell",
"routes": [{ "path": "/cafm", "requiresLicense": true }],
"navigation": [
{ "label": "CAFM", "icon": "Wrench", "path": "/cafm", "section": "operations", "order": 100 }
]
}routes are where your federated Shell mounts. navigation is what appears in the Building OS sidebar; section is one of operations, engagement, infrastructure, analytics, admin.
remoteEntry must be ./Shell — the shell resolves exactly that name. Enforce it in CI:
npx @tv/extension-sdk check-exposes module-manifest.json --config=./vite.config.tsLifecycle
"lifecycle": {
"healthEndpoint": "/health",
"init": "on_demand",
"dependencies": []
}init: "on_demand" lazy-loads your module when its route is first hit; on_boot starts it with the shell. dependencies lists module ids that must reach HEALTHY before yours starts — keep it empty unless you genuinely can't function without another module.
Full example
See the reference module for a complete, validated manifest with mcpTools, mcpEndpoint, heartbeat, and HMAC-signed federation. The module catalog lists every module shipping today.
Stability of the schema
The manifest schema follows the SDK's semver. Breaking changes (new required fields, removed fields) bump the SDK major. Additive fields bump the minor. See the stability policy.