Files

9.8 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 (or vite) IIFE bundle (dist/main.js + optional lazy chunks *.chunk.js, source maps *.js.map, styles.css) + a plugin.json manifest, uploaded to the host via POST /api/plugin/:id/upload-bundle (or teable-plugin deploy, which handles all assets). It runs in the same React tree as the host and has full access to the host's SDK, UI library, and OpenAPI helpers.

Dev loop: teable-plugin dev (or pnpm dev in scaffolded projects) watch-builds and auto-uploads with a devMode manifest; the open host page remounts the plugin within ~4s without a page refresh. See dynamic-plugins/dev-workflow.md.

Full-page apps: install at position route and the host serves the plugin on /base/<baseId>/p/<pluginId> (whole viewport, own router OK — HashRouter recommended).

Critical constraint: no RUNTIME imports from @teable/sdk, @teable/ui-lib, @teable/openapi — a value import via monorepo aliases inlines the whole package source (via window-global externals it compiles to undefined). import type { X } from '@teable/...' IS fine (erased at compile time). react/react-dom may be imported when your webpack externals maps them to window.__TEABLE_HOST__.React/ReactDOM (see the plugin-showcase webpack config; react/jsx-runtime is intentionally bundled — element factories only, hooks stay on the host singleton). For everything else, read from the host global: const { useFields } = window.__TEABLE_HOST__.SDK. See dynamic-plugins/troubleshooting.md Pitfall 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 spelunking nestjs-backend/src or 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, see sdk-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-teable skill 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
3b. Native form quick answer dynamic-plugins/native-form.md when the user ASKS whether/how to embed native field editors, custom form layout, or multi-step forms — read this FIRST and answer directly (consultative questions need no scaffold/build)
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, chunk-name flattening, terser banners)
Ref: plugin-showcase plugins/plugin-showcase/ the v2 example — lazy chunks + source maps + Tailwind + HashRouter route app
Ref: batch-edit-pro plugins/batch-edit-pro/ the classic example — single-file 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; :3000 is taken by an unrelated docker container on this host — see AGENTS.md), frontend http://localhost:3002.
  • Plugin upload requires a plugin_developer row 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/base with {"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 cookie auth_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 / localhost in a bundle.
  • Registry: one line in PLUGIN_API_SERVICES (JSON, in apps/nextjs-app/.env AND .env.development), then restart teable-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 in next.config.js outranks catch-all file routes, and files outside pages/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 by plugin-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 as main.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.js on disk only fixes the CURRENT artifact — the plugin SOURCE must use /api/plugin-proxy/track before 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.