- 导入规则精确化:@teable/* 禁运行时值导入(import type 可); react 仅在 webpack externals 映射到宿主全局时允许 - 新增效率守则:信任技能文档、知识题不读宿主源码、不上传演示插件验证 - 修复失效引用:hr_admin 死账号、404 测试基地 → 改为一次性沙箱基地模式 - 补充认证提示:管理类路由(如 DELETE /api/plugin)PAT 403 需 session 回退 - deployment.md 部署脚本改用 PAT 鉴权与必填 BASE_ID 评估:6 题 × 2 轮 24 次运行(含真实上传/清理链路);description 经 触发评估循环验证为原版最优,保持不变
8.7 KiB
name, description
| name | description |
|---|---|
| inkreach-teable-plugin | Develop dynamic React component plugins for inkreach-teable — IIFE bundles loaded at runtime via window.__TEABLE_HOST__, no iframe sandbox, full host API access. Scaffold with plugin-cli, build with webpack, deploy via upload-bundle API, register via definePlugin + PluginContext. Positions: contextMenu, panel, view, dashboard, route. Use this whenever the user mentions plugins, 插件, batch-edit, context-menu actions, panel widgets, dashboard charts, custom views, plugin.json, definePlugin, bundle plugin, plugin-cli, plugin-sdk, plugin-host, or window.__TEABLE_HOST__. Even if the user just says "add a right-click action" or "make a side panel" or "I need a dashboard widget" — or wants the native form embedded or extended inside a plugin (原生表单, 表单注入, 表单虚拟字段, custom form layout, NativeForm, NativeFormScope, form panel) — load this skill. Reference example at `plugins/batch-edit-pro/` (on the deployment host: `/root/deployments/inkreach-teable/plugins/batch-edit-pro/`). |
inkreach-teable dynamic plugin development
This skill develops React component plugins that load at runtime without
rebuilding the host Next.js app. Plugins are IIFE bundles — they ship as plain
JS that executes in the host page and accesses host APIs through
window.__TEABLE_HOST__.
Contract: A plugin = a webpack IIFE bundle (
dist/main.js+ optionaldist/styles.css) +plugin.jsonmanifest, uploaded to the host viaPOST /api/plugin/:id/upload-bundle. It runs in the same React tree as the host and has full access to the host's SDK, UI library, and OpenAPI helpers.Critical constraint: no RUNTIME imports from
@teable/sdk,@teable/ui-lib,@teable/openapi— webpack maps them towindow.__TEABLE_HOST__.*externals, so a value import compiles toundefinedat runtime.import type { X } from '@teable/...'IS fine (erased at compile time).react/react-dommay be imported ONLY when your webpackexternalsmaps them towindow.__TEABLE_HOST__.React/ReactDOM(see the batch-edit-pro webpack config). For everything else, read from the host global:const { useFields } = window.__TEABLE_HOST__.SDK. Seedynamic-plugins/troubleshooting.mdPitfall 1 for details and symptoms.
0. Two efficiency rules (read first)
- Trust these docs. The
dynamic-plugins/documents are the verified contract for this deployment. Answer plugin capability/behavior questions directly from them — do NOT re-derive behavior by spelunkingnestjs-backend/srcor the host source tree; that burns minutes and adds nothing. Source diving is only for debugging a concrete discrepancy the docs can't explain. - Don't upload demo plugins to verify knowledge. For how-to/consultation questions, a written answer (with code) is the deliverable. Only upload a bundle when the user actually asked to deploy — test uploads belong in a throwaway sandbox base, never the production plugin registry.
1. When to use this skill
- User says "写个插件 / 做个插件 / make a plugin" — yes.
- User wants a context-menu action (batch edit / process selected records) — yes.
- User wants a panel gadget (real-time stats / summary) — yes.
- User wants a custom view (kanban, gallery, timeline) — yes.
- User wants a dashboard chart — yes.
- User wants to embed / extend the NATIVE FORM in a plugin (原生表单, form
injection, virtual form fields, custom multi-step form with native field
editors,
host.NativeForm,NativeFormScope) — yes, seesdk-api.md. - User mentions
definePlugin,plugin-cli,plugin-sdk,window.__TEABLE_HOST__, bundle plugin — yes. - User wants to manage tables/automations (not plugins) — use
inkreach-teableskill instead.
2. Module map (read in order when developing)
| Stage | File | Read it when... |
|---|---|---|
| 1. Understand architecture | dynamic-plugins/overview.md |
always first — how window.__TEABLE_HOST__, PluginLoader, PluginSlot work |
| 2. Dev workflow | dynamic-plugins/dev-workflow.md |
when building a plugin — the step-by-step recipe from scaffold to deploy |
| 3. SDK API | dynamic-plugins/sdk-api.md |
when writing plugin logic — definePlugin, PluginContext, events, storage, useSelection, native form embedding (NativeForm Tier-1 injection / NativeFormScope+useNativeForm Tier-2 kit), NativeGrid |
| 4. Deployment | dynamic-plugins/deployment.md |
when uploading + enabling + authority matrix permissions |
| 5. Troubleshooting | dynamic-plugins/troubleshooting.md |
before writing logic — common pitfalls (host globals, developer whitelist, AM, empty states) |
| Ref: batch-edit-pro | plugins/batch-edit-pro/ |
the canonical example — contextMenu plugin, full React + SDK |
3. Quick decision: which position?
| User wants | Position | definePlugin registration |
|---|---|---|
| Right-click action on records | ContextMenu | ctx.addContextMenuAction(id, component) |
| Side gadget while viewing a table | Panel | ctx.addPanel(id, component) |
| A new view type | View | ctx.addView(id, component) |
| Card on the space dashboard | Dashboard | ctx.addDashboardWidget(id, component) |
| A new page/route | Route | ctx.addRoute(path, component) |
4. Auth & test credentials
- Endpoint:
http://localhost:3003(backend;:3000is taken by an unrelated docker container on this host — see AGENTS.md), frontendhttp://localhost:3002. - Plugin upload requires a
plugin_developerrow for the uploading user (INSERT INTO plugin_developer (id, user_id, email, created_time, created_by) VALUES ('plgd<name>', '<userId>', '<email>', now(), '<userId>')). - Sandbox base for any hands-on test: create a throwaway base
(
POST /api/basewith{"spaceId":"<spaceId>","name":"Sandbox(可删)"}), do all table/install testing inside it, and delete it (DELETE /api/base/:id) when done. Never hardcode base/table IDs here — they rot; resolve them at runtime (inkreach-teable base list) instead. - Auth: PAT (
Authorization: Bearer <PAT>; upload-bundle supports it) or session cookieauth_session. Note: some admin-gated routes (e.g.DELETE /api/plugin/:id) pass with an admin session but 403 with a plain PAT even for the same user — fall back to the session for those.
5. Start here
For any plugin request, read dynamic-plugins/overview.md then
dynamic-plugins/dev-workflow.md. Reference plugins/batch-edit-pro/ for a
complete working example.
6. Plugin backend services — MUST use the same-origin proxy (2026-08-21)
The instance is publicly served at https://table.inkreach.cc (frp + cloud Caddy);
browsers on the public internet CANNOT reach LAN addresses like
http://192.168.124.92:8001. Plugin bundles that need their own backend service
MUST call it through the session-gated dynamic proxy instead:
- URL contract:
fetch('/api/plugin-proxy/<service>/<path...>')— same-origin, works from LAN and public internet alike. NEVER hardcode LAN IPs /localhostin a bundle. - Registry: one line in
PLUGIN_API_SERVICES(JSON, inapps/nextjs-app/.envAND.env.development), then restartteable-backend.service. No rebuild, no code change. Example:PLUGIN_API_SERVICES={"track":"http://127.0.0.1:8001"} - Implementation: NestJS controller
apps/nestjs-backend/src/features/plugin/plugin-proxy.controller.ts(global AuthGuard gates login; upstream whitelist = registry; session cookies are never forwarded upstream). Do NOT move this to a Next.js pages-router API route — the/api/:path*rewrite innext.config.jsoutranks catch-all file routes, and files outsidepages/api/get compiled as SSR pages (observed on Next 16.1.6/turbopack). - CSP: same-origin fetches need nothing; truly external origins must be
declared in the plugin manifest
apiOrigins(appended to connect-src byplugin-runtime.controller.ts). - Re-uploading a plugin zip OVERWRITES the bundle — re-check for hardcoded LAN
URLs afterwards (
grep -o 'http://192.168[^\"']*' <bundle>/main.js). Live example:apps/nestjs-backend/.assets/plugins/bundle/track-panel/(patched from LAN URL to/api/plugin-proxy/track; backups kept asmain.js.bak-*). - Root fix (2026-08-22 recurrence): a developer re-uploaded track-panel at
02:23 with the LAN URL back in the source-built bundle, breaking public
search with a CORS error within hours of the last patch. Patching
main.json disk only fixes the CURRENT artifact — the plugin SOURCE must use/api/plugin-proxy/trackbefore building/uploading, otherwise every re-upload reintroduces the bug. The backend serves bundle files from disk per-request, so a disk patch takes effect without a backend restart.