skills/ 现包含 inkreach-teable + inkreach-teable-plugin, npx skills add 一并安装两个;install.sh 改为遍历安装 skills/*。
6.7 KiB
Plugin deployment
Uploading a bundle plugin, enabling it, and managing permissions.
Upload — POST /api/plugin/:pluginId/upload-bundle
Upload the built bundle to the host. Uses multipart/form-data.
The pluginId in the URL must match the id in plugin.json.
PLUGIN_ID=$(python3 -c "import json; print(json.load(open('plugin.json'))['id'])")
curl -s -b "auth_session=$SESSION" \
-F "js=@dist/main.js" \
-F "css=@dist/styles.css" \
-F "manifest=@plugin.json" \
"http://localhost:3003/api/plugin/$PLUGIN_ID/upload-bundle"
Auth note: Supports both session cookies (auth_session) and Personal Access
Tokens (Authorization: Bearer <PAT>).
Fields:
| Field | Required | Max size | Description |
|---|---|---|---|
js |
Yes | 5 MB | The IIFE bundle (dist/main.js) |
css |
No | 1 MB | Optional stylesheet (dist/styles.css) |
manifest |
Yes | 10 KB | plugin.json manifest |
Returns 201 with { id, name, version, ... } — the plugin ID to use for
enable and install.
Security (7-layer validation):
- Authentication — valid session cookie required (401 if missing)
- Developer whitelist —
plugin_developertable lookup (403 if not whitelisted) - Rate limit — 10 uploads/min per user (429 if exceeded)
- Size limits — JS ≤ 5 MB, CSS ≤ 1 MB, manifest ≤ 10 KB
- Manifest validation — required fields,
idregex[a-z0-9-]+ - Content validation — JS must be IIFE format (prefix
!function/(()=>), rejects<script>/eval/document.writeinjection; CSS stripsexpression()/@import/javascript:URIs - Integrity — SHA-256 hash stored in DB, verified at load time
Enable — PUT /api/base/:baseId/plugin-config/:pluginId
The plugin must be enabled before it can be installed to any position.
curl -s -b "auth_session=$SESSION" -X PUT \
"http://localhost:3003/api/base/$BASE_ID/plugin-config/$PLUGIN_ID" \
-H 'Content-Type: application/json' \
-d '{"enabled":true,"tableIds":[]}'
tableIds: []— enabled base-wide (all tables)tableIds: ["tblX", "tblY"]— enabled only for specific tables
Install by position
ContextMenu
curl -s -b "auth_session=$SESSION" -X POST \
"http://localhost:3003/api/table/$TABLE_ID/plugin-context-menu/install" \
-H 'Content-Type: application/json' \
-d '{"pluginId":"$PLUGIN_ID","name":"My Plugin"}'
Body: pluginId (required), name (optional display name).
Panel
Panel install requires an existing panel. First create one, then install to it:
# Create a panel (if none exists)
PANEL_ID=$(curl -s -b "auth_session=$SESSION" -X POST \
"http://localhost:3003/api/table/$TABLE_ID/plugin-panel" \
-H 'Content-Type: application/json' \
-d '{"name":"My Panel"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
# Install plugin to the panel
curl -s -b "auth_session=$SESSION" -X POST \
"http://localhost:3003/api/table/$TABLE_ID/plugin-panel/$PANEL_ID/install" \
-H 'Content-Type: application/json' \
-d '{"pluginId":"$PLUGIN_ID","name":"My Plugin"}'
The plg prefixed ID is the panel's UUID, not a plugin ID. Create a panel first
if you don't already have one.
Dashboard
curl -s -b "auth_session=$SESSION" -X POST \
"http://localhost:3003/api/base/$BASE_ID/dashboard/$DASHBOARD_ID/plugin" \
-H 'Content-Type: application/json' \
-d '{"pluginId":"$PLUGIN_ID","name":"My Plugin"}'
View
curl -s -b "auth_session=$SESSION" -X POST \
"http://localhost:3003/api/table/$TABLE_ID/view/plugin" \
-H 'Content-Type: application/json' \
-d '{"pluginId":"$PLUGIN_ID","name":"My Plugin"}'
Returns { pluginId, pluginInstallId, name, viewId }. Creates a new view
of type plugin in the table's view list. The plugin's definePlugin.onLoad
must call ctx.addView(id, component) to render the component as a view.
Authority Matrix entries are auto-created for the new install (default
denied for non-owner roles).
Route
Route-type plugins registered via ctx.addRoute(path, component) are
automatically available at the registered path — no install API needed.
Upload + enable is sufficient.
Authority Matrix (plugin permissions)
Plugins integrate with the 4-layer permission system. Three control layers:
| Layer | What it controls | Mechanism |
|---|---|---|
| Plugin visible | User sees plugin in context menu / panel / dashboard | AuthorityMatrixRolePluginInstall per-role per-install |
| Plugin executable | User can click and run the plugin | Checked at PluginSlot render |
| Data operations | Plugin's CRUD respects user's permissions | Already protected — plugins call @teable/openapi which passes through existing AM enforcement |
Default behavior:
- New installs default to disabled for non-owner roles
- Base owner always has full access
- Admin enables per role through the RoleEditor UI
Developer whitelist
Only users in the plugin_developer table can upload bundles. To add a developer:
INSERT INTO plugin_developer (id, user_id, email, created_by)
VALUES (gen_random_uuid(), '<userId>', '<email>', '<adminUserId>');
Getting a session cookie
curl -s -c /tmp/cookie_hr.txt http://localhost:3003/api/auth/signin \
-X POST -H 'Content-Type: application/json' \
-d '{"email":"hr_admin@xingchen.tech","password":"Xingchen@2026"}'
> Account note (2026-08): `hr_admin@xingchen.tech` no longer exists on the
> deployed instance. Create a throwaway user via `POST /api/auth/signup`
> and add a `plugin_developer` row for it before uploading (see SKILL.md).
SESSION=$(grep auth_session /tmp/cookie_hr.txt | awk '{print $NF}')
Full deploy script (copy-paste)
#!/bin/bash
set -e
DIR="${1:-.}"
UPLOAD_URL="${UPLOAD_URL:-http://localhost:3003}"
BASE_ID="${BASE_ID:-bseAi6Hazmw7jPWZbfO}"
# Read plugin ID from manifest
PLUGIN_ID=$(python3 -c "import json; print(json.load(open('$DIR/plugin.json'))['id'])")
# Get session
SESSION=$(grep auth_session /tmp/cookie_hr.txt 2>/dev/null | awk '{print $NF}')
if [ -z "$SESSION" ]; then
curl -s -c /tmp/cookie_hr.txt "$UPLOAD_URL/api/auth/signin" \
-X POST -H 'Content-Type: application/json' \
-d '{"email":"hr_admin@xingchen.tech","password":"Xingchen@2026"}' > /dev/null
SESSION=$(grep auth_session /tmp/cookie_hr.txt | awk '{print $NF}')
fi
# Upload (URL includes pluginId from manifest)
curl -s -b "auth_session=$SESSION" \
-F "js=@$DIR/dist/main.js" \
-F "css=@$DIR/dist/styles.css" \
-F "manifest=@$DIR/plugin.json" \
"$UPLOAD_URL/api/plugin/$PLUGIN_ID/upload-bundle"
echo "Uploaded: $PLUGIN_ID"
# Enable
curl -s -b "auth_session=$SESSION" -X PUT \
"$UPLOAD_URL/api/base/$BASE_ID/plugin-config/$PLUGIN_ID" \
-H 'Content-Type: application/json' \
-d '{"enabled":true,"tableIds":[]}' > /dev/null
echo "Enabled: $PLUGIN_ID"