# 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: ```json { "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. ```json "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. ```json "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 1. Build the package: `cd packages && bash build.sh my-package` 2. Upload the `.pkg` file via Admin > Packages > Install. 3. Grant permissions in Admin > Packages > Permissions. 4. Enable the package. 5. 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.