v0.6.1 — Backup tooling (.swb ZIP export/import), admin backup section, docs surface with markdown rendering, 5 reference docs, 6 handler tests. v0.6.2 — Dark mode contrast fixes, topbar nav, 📖 docs icon, dynamic OpenAPI spec with extension route merging, docs outline sidebar, scroll/routing fixes; 7 new tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6.1 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 |
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 CPU budget and memory ceiling. Available modules (granted per-permission by admin):
| Module | Permission | API |
|---|---|---|
db |
db.write |
db.query(table, filters), db.insert(table, row), db.update(table, id, row), db.delete(table, id) |
http |
http |
http.get(url), http.post(url, body) -- SSRF-safe, no private IPs by default |
notifications |
notifications |
notifications.send(user_id, title, body) |
secrets |
secrets |
secrets.get(connection_type) -- reads from the credential vault |
api |
(implicit) | Registers HTTP routes at /s/:slug/api/*path |
realtime |
realtime.publish |
realtime.publish(channel, event, data) -- push to WebSocket clients |
The sandbox cannot spawn goroutines, access the filesystem, or import arbitrary packages.
Permissions Model
Extensions declare required permissions in manifest.json. The admin must grant each permission before the extension can use the corresponding module. Permission status is visible in Admin > Packages > Permissions.
Kernel permissions for users/groups: extension.use, extension.install, workflow.create, workflow.submit, admin.view, token.unlimited.
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.