Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
7.3 KiB
Extension Guide
Package Types
| Type | Description |
|---|---|
surface |
A routable UI page rendered in the shell viewport |
extension |
Starlark hooks, tools, API routes, DB tables |
full |
Both surface and extension combined |
library |
Shared code imported by other packages via lib.require() |
workflow |
Bundled workflow definition |
Tiers
| Tier | Runs | Capabilities |
|---|---|---|
browser |
Client-side JS only | DOM access, SDK hooks, no server-side logic |
starlark |
Sandboxed server-side | DB, HTTP, notifications, secrets, API routes, realtime |
sidecar |
Separate container | Full runtime (future) |
manifest.json
Every package has a manifest.json at its root. Example for a surface:
{
"id": "my-surface",
"title": "My Surface",
"type": "full",
"tier": "starlark",
"version": "1.0.0",
"description": "A custom surface with server-side logic.",
"icon": "🔧",
"author": "you",
"route": "/s/my-surface",
"auth": "authenticated",
"permissions": ["db.write"],
"api_routes": [
{"method": "GET", "path": "/items"},
{"method": "POST", "path": "/items"}
],
"db_tables": {
"items": {
"columns": {
"title": "text",
"done": "int"
},
"indexes": [["title"]]
}
},
"settings": {
"page_size": {
"type": "string",
"label": "Items Per Page",
"description": "Number of items shown per page",
"default": "25"
}
}
}
Field Reference
| Field | Required | Description |
|---|---|---|
id |
yes | Unique kebab-case identifier |
title |
yes | Display name |
type |
yes | surface, extension, full, library, workflow |
tier |
yes | browser, starlark, sidecar |
version |
yes | Semver string |
description |
no | Short description |
icon |
no | Emoji icon for sidebar/menu |
route |
surfaces | URL path (e.g., /s/my-surface) |
auth |
no | authenticated (default) or public |
permissions |
no | Capabilities requested: db.write, http, notifications, secrets, realtime.publish |
api_routes |
no | Array of {method, path} for extension HTTP endpoints |
api_schema |
no | OpenAPI documentation for extension API routes (see below) |
db_tables |
no | Table definitions (see below) |
settings |
no | User-configurable settings schema |
exports |
libraries | Functions exported for other packages |
hooks |
no | Event bus subscriptions |
config_section |
no | Settings/Admin panel injection (see below) |
schema_version |
no | Integer for additive schema migrations |
db_tables Schema
Tables are automatically namespaced as ext_{package_id}_{table_name}. Column types: text, int. Every table gets an auto-generated id primary key and created_at timestamp.
"db_tables": {
"notes": {
"columns": {
"title": "text",
"body": "text",
"creator_id": "text",
"pinned": "int"
},
"indexes": [
["creator_id"],
["pinned"]
]
}
}
api_schema (OpenAPI Documentation)
Extensions can optionally declare an api_schema array in their manifest to provide rich API documentation. Declared routes appear in Swagger UI at /api/docs with parameters, request bodies, and response schemas. Routes without api_schema entries still appear as auto-generated stubs.
"api_schema": [
{
"path": "/items",
"method": "GET",
"summary": "List items",
"description": "Returns paginated items for the current user",
"params": {
"limit": {"type": "integer", "default": 50, "description": "Max results"},
"offset": {"type": "integer", "default": 0}
},
"response": {
"type": "object",
"example": {"data": [{"id": "string", "title": "string"}]}
}
},
{
"path": "/items",
"method": "POST",
"summary": "Create item",
"body": {
"title": {"type": "string", "required": true},
"description": {"type": "string"}
}
}
]
Only path and method are required. All other fields are optional. Malformed entries are logged and skipped without blocking extension loading.
Starlark Sandbox API
Starlark scripts run server-side with a 1M operation budget and no filesystem access. See the Starlark Reference for the complete module catalog, function signatures, and permission gates.
config_section — Settings Panel Injection
Packages can inject configuration panels into the Settings, Admin, or
Team Admin surfaces. Declare config_section in manifest.json:
{
"config_section": {
"label": "My Config",
"icon": "M12 2L2 7l10 5 10-5-10-5z",
"component": "js/config.js",
"surfaces": ["settings", "admin"],
"category": "system"
}
}
| Field | Required | Description |
|---|---|---|
label |
yes | Navigation label shown in the sidebar or tab list |
icon |
no | SVG path data for the nav icon |
component |
no | JS asset path (default: js/config.js). Must export default a Preact component. |
surfaces |
yes | Target surfaces: "settings", "admin", "team-admin" |
category |
no | Admin surface category tab (default: "system"). Ignored for settings/team-admin. |
How it works:
- At page load, the backend scans all enabled packages for
config_sectionentries targeting the current surface. - Matching sections are injected into the page as
__CONFIG_SECTIONS__. - The frontend dynamically imports the component module and renders it as an additional tab/section.
- The component receives
{ packageId, teamId }as props.
Example component (js/config.js):
const { html } = window;
const { useState, useEffect } = hooks;
export default function MyConfig({ packageId }) {
const [val, setVal] = useState('');
useEffect(() => {
sw.api.ext(packageId).get('/settings').then(r => setVal(r.value));
}, []);
return html`<div>
<label>API Key</label>
<input value=${val} onInput=${e => setVal(e.target.value)} />
<button onClick=${() => sw.api.ext(packageId).put('/settings', { value: val })}>Save</button>
</div>`;
}
Permissions Model
Extensions declare required permissions in manifest.json. The admin
must grant each permission before the extension can use the corresponding
sandbox module. Permission status is visible in Admin > Packages > Permissions.
See Permissions & Groups for the full RBAC model, user permission slugs, and settings cascade.
File Structure
my-package/
manifest.json # required
js/ # browser-side JavaScript
index.js
css/ # stylesheets
styles.css
script.star # Starlark entry point
star/ # additional Starlark modules
assets/ # static assets
migrations/ # schema migration files
Testing Extensions
- Build the package:
cd packages && bash build.sh my-package - Upload the
.pkgfile via Admin > Packages > Install. - Grant permissions in Admin > Packages > Permissions.
- Enable the package.
- If it is a surface, navigate to its route (e.g.,
/s/my-package).
Extension API routes are accessible at /s/{slug}/api/{path} and require an authenticated Bearer token.