This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/docs/EXTENSION-GUIDE.md
Jeffrey Smith a887b4c78b
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-frontend (push) Successful in 6s
CI/CD / test-go-pg (push) Successful in 2m46s
CI/CD / test-sqlite (push) Successful in 2m49s
CI/CD / build-and-deploy (push) Successful in 28s
V0.6.2 docs openapi (#37)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
2026-03-31 12:01:51 +00:00

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

  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.