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.
Identity
Section titled “Identity”These fields describe the plugin in the catalog:
apiVersionis1.idis the stable snake_case identifier.definitionVersionversions the plugin’s persisted and Player-facing definition.name,description,category, andicondescribe it in Studio.maintainerslists the people or teams responsible for the subject.instanceNoungives the singular and plural name shown with instance counts.requirementstells an operator what the plugin expects before installation.usesis plain catalog text describing the Tilecast capabilities it uses.
The directory name is the ID with underscores changed to hyphens.
Capabilities
Section titled “Capabilities”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.
Entry points
Section titled “Entry points”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.
API routes
Section titled “API routes”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 routes
Section titled “Studio routes”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.
Store listing
Section titled “Store listing”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.
Public docs
Section titled “Public docs”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.