Skip to content

Write the plugin manifest

The manifest is plugins/<name>/tilecast.plugin.json. Tilecast validates it at build time and when the server hosts the plugin.

Use the generated JSON Schema in your editor. The exact field rules live in the Plugin API v1 engineering reference.

These fields describe the plugin in the catalog:

  • apiVersion is 1.
  • id is the stable snake_case identifier.
  • definitionVersion versions the plugin’s persisted and Player-facing definition.
  • name, description, category, and icon describe it in Studio.
  • maintainers lists the people or teams responsible for the subject.
  • instanceNoun gives the singular and plural name shown with instance counts.
  • requirements tells an operator what the plugin expects before installation.
  • uses is plain catalog text describing the Tilecast capabilities it uses.

The directory name is the ID with underscores changed to hyphens.

Only declare capabilities the implementation uses.

{
"capabilities": {
"playerManifest": true,
"backgroundWorkers": true,
"network": ["api.example.org"]
}
}

playerManifest is required when the server projects Player manifest entries. backgroundWorkers is required for a WorkerProvider. network lists server-side hosts the plugin contacts.

plugin.CheckDeclarations compares the manifest with the Go interfaces the plugin implements.

The build uses fixed paths:

{
"server": { "entrypoint": "./plugin.go" },
"studio": {
"route": "/plugins/transit-alerts",
"entrypoint": "./studio/index.tsx"
},
"runtime": {
"entrypoint": "./runtime/index.ts",
"manifestTypes": ["transit_alert"],
"surfaces": ["strip.bottom"],
"tier": "scheduled"
}
}

Leave out runtime when the plugin does not draw on the Player.

A plugin with HTTP routes declares the prefixes it owns and its OpenAPI fragment:

{
"api": {
"basePaths": ["/plugins/transit-alerts"],
"openapi": "./api/openapi.yaml"
}
}

The server refuses route collisions with core or another plugin.

studio.route is the management route under /plugins/<directory>.

Use studio.additionalRoutes when the plugin owns a separate Studio surface such as a portal or inbox. The Studio definition must contribute matching standaloneRoutes.

A plugin can dress up its Plugin Store page with a second file, plugins/<name>/tilecast.store.json. It holds presentation only:

{
"publisher": { "name": "Tilecast", "url": "https://tilecast.org" },
"longDescription": "A few sentences for the page's About section.",
"artwork": {
"icon": "./store/icon.webp",
"screenshots": [
{ "src": "./store/01-overview.webp", "alt": "What the screenshot shows." }
]
}
}

Keep the images in the plugin’s store/ folder as .webp or .png. Use up to five screenshots at one size, 16:9 (about 1920 × 1080), and write alternative text for each. Attach the file in plugin.go with .WithStore(...). Tilecast checks the paths when the server starts, so a typo is a build failure rather than a broken image.

The file never changes what the plugin is or does. Identity, requirements, capabilities, and trust all still come from tilecast.plugin.json. A plugin without a Store file still works and shows its built-in icon and no screenshots. See the engineering reference for every rule.

A plugin can publish pages from its own directory:

{
"docs": {
"pages": [
{
"source": "./docs/index.mdx",
"slug": "operations/plugins/transit-alerts"
}
]
}
}

The docs build checks slug collisions and adds declared pages to the public docs collection. The docs sidebar doesn’t list plugin pages. Link your page from the plugin catalog page, apps/docs/src/content/docs/operations/plugins.mdx, so readers can find it. A docs test fails when a declared page has no link there.