Docs restructuring: category grouping (Getting Started, Platform, Extension Development, Operations) with 14 ordered docs. Four new guides: Permissions & Groups, Workflows, Starlark Reference, Frontend JS Guide. Extension Guide updated with config_section docs. Content refresh across 10 existing docs: rebrand volume names, stale CSS vars, TLS_MODE env, self-hosted font notes, Architecture frontend section. Team Admin workflows.js (722 lines) split into 3 modules: workflows.js (router+CRUD), workflow-editor.js (editor+stages), workflow-monitor.js (assignments+monitor+signoff). Fix: docs outline scroll no longer pushes topbar off-screen. Fix: --bg-2 (undefined CSS var) replaced with --bg-secondary. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
233 lines
6.1 KiB
Markdown
233 lines
6.1 KiB
Markdown
# Starlark Reference
|
|
|
|
Armature extensions can include Starlark scripts for server-side logic.
|
|
Starlark is a Python-like language designed for configuration and
|
|
embedding — see the [official spec](https://github.com/google/starlark-go).
|
|
|
|
## Sandbox constraints
|
|
|
|
- **No `while` loops** — use `for` with bounded ranges.
|
|
- **No `load()`** — use `lib.require()` for library dependencies.
|
|
- **Max steps:** 1,000,000 bytecode operations per execution.
|
|
- **No filesystem or OS access** — all I/O goes through gated modules.
|
|
- **Deterministic** — same input produces same output (no `random`, no `time`).
|
|
|
|
## Always-available modules
|
|
|
|
These modules are injected into every script with no permission required.
|
|
|
|
### json
|
|
|
|
Standard Starlark JSON module.
|
|
|
|
```python
|
|
data = json.decode('{"key": "value"}')
|
|
text = json.encode({"key": "value"})
|
|
```
|
|
|
|
### settings
|
|
|
|
Read resolved package settings (global → team → user cascade).
|
|
|
|
```python
|
|
val = settings.get("theme", "light")
|
|
# Returns the resolved value, or the default if unset.
|
|
```
|
|
|
|
The cascade respects the `user_overridable` flag from the package manifest.
|
|
See [Permissions & Groups](PERMISSIONS-AND-GROUPS) for details.
|
|
|
|
### lib
|
|
|
|
Load exported functions from library packages.
|
|
|
|
```python
|
|
helpers = lib.require("my-utils")
|
|
result = helpers.format_date("2026-01-15")
|
|
```
|
|
|
|
Requirements:
|
|
- The library must be declared in your package manifest's `dependencies`.
|
|
- The library must be type `library`, status `active`, tier `starlark`.
|
|
- Circular dependencies are detected and rejected.
|
|
- Results are cached per execution (calling `require` twice returns the
|
|
same object).
|
|
|
|
## Permission-gated modules
|
|
|
|
These modules are only available if the package has the corresponding
|
|
permission granted in **Admin > Packages**.
|
|
|
|
### secrets
|
|
|
|
**Permission:** `secrets.read`
|
|
|
|
Read admin-configured secrets for this package.
|
|
|
|
```python
|
|
api_key = secrets.get("OPENAI_KEY") # str or None
|
|
all_keys = secrets.list() # list of key names
|
|
```
|
|
|
|
Secrets are set in **Admin > Packages > Secrets** and scoped per package.
|
|
|
|
### notifications
|
|
|
|
**Permission:** `notifications.send`
|
|
|
|
Send in-app notifications to users.
|
|
|
|
```python
|
|
notifications.send(
|
|
user_id, # str — target user UUID
|
|
title, # str — notification title
|
|
body="", # str — optional body text
|
|
type="extension.notify" # str — notification type
|
|
)
|
|
```
|
|
|
|
### db
|
|
|
|
**Permission:** `db.read` (queries) or `db.write` (mutations)
|
|
|
|
Read and write extension data tables. All tables are automatically
|
|
namespaced as `ext_{package_id}_{table_name}`.
|
|
|
|
#### Read operations
|
|
|
|
```python
|
|
# Query with filters, ordering, and pagination
|
|
rows = db.query(
|
|
"tasks", # table name (without prefix)
|
|
filters={"status": "open"}, # equality WHERE clauses
|
|
order="-created_at", # column name (prefix - for DESC)
|
|
limit=50, # max 1000
|
|
before={"created_at": ts}, # range: column < value
|
|
after={"created_at": ts}, # range: column > value
|
|
search_like={"title": "%bug%"} # LIKE/ILIKE search
|
|
)
|
|
|
|
# Read from system views (read-only)
|
|
users = db.view("users", filters={"display_name": "Alice"}, limit=10)
|
|
channels = db.view("channels", limit=100)
|
|
|
|
# List all tables owned by this package
|
|
tables = db.list_tables()
|
|
```
|
|
|
|
Available views: `users`, `channels`.
|
|
|
|
#### Write operations
|
|
|
|
```python
|
|
row = db.insert("tasks", {"title": "Fix bug", "status": "open"})
|
|
# Returns the inserted row dict (with generated id, created_at)
|
|
|
|
db.update("tasks", row_id, {"status": "closed"})
|
|
# Returns True on success
|
|
|
|
db.delete("tasks", row_id)
|
|
# Returns True on success
|
|
```
|
|
|
|
### http
|
|
|
|
**Permission:** `api.http`
|
|
|
|
Make outbound HTTP requests.
|
|
|
|
```python
|
|
resp = http.get("https://api.example.com/data", headers={"Authorization": "Bearer ..."})
|
|
resp = http.post(url, body='{"key": "val"}', headers={"Content-Type": "application/json"})
|
|
resp = http.put(url, body="...", headers={})
|
|
resp = http.delete(url, headers={})
|
|
resp = http.request("PATCH", url, body="...", headers={})
|
|
```
|
|
|
|
Response dict:
|
|
|
|
```python
|
|
{
|
|
"status": 200,
|
|
"headers": {"content-type": "application/json"},
|
|
"body": "..." # capped at 1 MB
|
|
}
|
|
```
|
|
|
|
**Security:**
|
|
- Private/loopback IPs are blocked (SSRF protection).
|
|
- Packages can declare `network_access.allow` (allowlist) or
|
|
`network_access.block` (blocklist) in their manifest.
|
|
- Max 10 redirects. 10-second timeout. 1 MB response body limit.
|
|
|
|
### realtime
|
|
|
|
**Permission:** `realtime.publish`
|
|
|
|
Publish WebSocket events to subscribed clients.
|
|
|
|
```python
|
|
realtime.publish(
|
|
"my-channel", # channel name
|
|
"item.updated", # event label
|
|
{"id": "abc"} # payload dict (max 7 KB)
|
|
)
|
|
```
|
|
|
|
The payload is automatically tagged with `_pkg: package_id`.
|
|
|
|
### connections
|
|
|
|
**Permission:** `connections.read`
|
|
|
|
Read external connection configurations (secrets are decrypted).
|
|
|
|
```python
|
|
conn = connections.get("postgres", "main-db")
|
|
# Returns dict with id, type, name, scope, plus flattened config fields
|
|
# Returns None if not found
|
|
|
|
all_pg = connections.list("postgres")
|
|
# Returns list of connection dicts
|
|
```
|
|
|
|
Connections are resolved via scope chain: personal → team → global.
|
|
|
|
### workflow
|
|
|
|
**Permission:** `workflow.access`
|
|
|
|
Read workflow definitions and instances (read-only from Starlark;
|
|
mutations go through the HTTP API).
|
|
|
|
```python
|
|
defn = workflow.get_definition(workflow_id)
|
|
# Returns dict: id, name, slug, entry_mode, is_active, version, stages[]
|
|
|
|
inst = workflow.get_instance(instance_id)
|
|
# Returns dict: id, workflow_id, current_stage, status, stage_data, ...
|
|
|
|
instances = workflow.list_instances(workflow_id, status="active")
|
|
# Returns list of instance dicts
|
|
```
|
|
|
|
## Example: automated stage hook
|
|
|
|
A simple hook that reads a setting, queries data, and advances:
|
|
|
|
```python
|
|
def on_run(ctx):
|
|
threshold = settings.get("approval_threshold", 1000)
|
|
amount = ctx["stage_data"].get("amount", 0)
|
|
|
|
if amount > threshold:
|
|
notifications.send(
|
|
ctx["started_by"],
|
|
"High-value submission",
|
|
body="Amount %d exceeds threshold." % amount,
|
|
)
|
|
return {"advance": True, "data": {"needs_review": True}}
|
|
|
|
return {"advance": True, "data": {"needs_review": False}}
|
|
```
|