plugins/README.md
Plugins are extensions that can be integrated into different positions within the application. All official plugins are managed within a single Next.js project (plugins), allowing for code sharing and simplified maintenance.
Plugins can be integrated into two main positions:
export enum PluginPosition {
Dashboard = 'dashboard',
View = 'view',
}
The plugins project uses Next.js App Router structure:
plugins/
├── src/
│ ├── app/
│ │ ├── chart/ # Chart plugin
│ │ │ ├── components/
│ │ │ ├── page.tsx
│ │ │ └── favicon.ico
│ │ ├── sheet-form/ # Sheet Form plugin
│ │ │ ├── components/
│ │ │ ├── page.tsx
│ │ │ └── favicon.ico
│ ├── components/ # Shared components
│ ├── locales/ # i18n translations
│ │ ├── chart/
│ │ │ ├── en.json
│ │ │ └── zh.json
│ │ └── sheet-form/
│ │ ├── en.json
│ │ └── zh.json
│ └── types.ts
├── package.json
└── tsconfig.json
Each plugin should have its own directory under src/app/ with the following structure:
import type { Metadata } from 'next';
import { EnvProvider } from '../../components/EnvProvider';
import { I18nProvider } from '../../components/I18nProvider';
import QueryClientProvider from '../../components/QueryClientProvider';
import { PageType } from '../../components/types';
import enCommonJson from '../../locales/chart/en.json';
import zhCommonJson from '../../locales/chart/zh.json';
import { Pages } from './components/Pages';
export async function generateMetadata({ searchParams }: Props): Promise<Metadata> {
const lang = searchParams.lang;
return {
title: lang === 'zh' ? '图表' : 'Chart',
icons: icon.src,
};
}
export default async function Home(props: { searchParams: IPageParams }) {
return (
<main className="flex h-screen flex-col items-center justify-center">
<EnvProvider>
<I18nProvider
lang={props.searchParams.lang}
resources={resources}
defaultNS="common"
pageType={PageType.Chart}
>
<QueryClientProvider>
<Pages {...props.searchParams} />
</QueryClientProvider>
</I18nProvider>
</EnvProvider>
</main>
);
}
Add a new directory under src/app/ for your plugin:
src/app/my-plugin/
├── components/
├── page.tsx
└── favicon.ico
Create a plugin configuration file:
import { PluginPosition } from '@teable/openapi';
import type { IOfficialPluginConfig } from './types';
export const myPluginConfig: IOfficialPluginConfig = {
id: 'plg-my-plugin',
name: 'My Plugin',
description: 'Plugin description',
detailDesc: `Detailed description with markdown support`,
helpUrl: 'https://help.teable.ai',
positions: [PluginPosition.Dashboard],
i18n: {
zh: {
name: '我的插件',
helpUrl: 'https://help.teable.cn',
description: '插件描述',
detailDesc: '详细描述',
},
},
logoPath: 'static/plugin/my-plugin.png',
pluginUserId: 'plgmypluginuser',
avatarPath: 'static/plugin/my-plugin.png',
};
The Plugin Bridge enables communication between your plugin and the main application.
const methods: IParentBridgeMethods = {
expandRecord: (recordIds) => {
console.log('expandRecord', recordIds);
},
updateStorage: (storage) => {
return updateDashboardPluginStorage(baseId, positionId, pluginInstallId, storage).then(
(res) => res.data.storage ?? {}
);
},
getAuthCode: () => {
return pluginGetAuthCode(pluginId, baseId).then((res) => res.data);
},
expandPlugin: () => {
onExpand?.();
},
};
export const initializeBridge = async () => {
if (typeof window === 'undefined') {
return;
}
const pluginBridge = new PluginBridge();
const bridge = await pluginBridge.init();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any)._teable_plugin_bridge = bridge;
return bridge;
};
Add translations for your plugin under src/locales/[plugin-name]/:
{
"title": "我的插件",
"description": "插件描述",
"actions": {
"save": "保存",
"cancel": "取消"
}
}
developing statusexport enum PluginStatus {
Developing = 'developing',
Reviewing = 'reviewing',
Published = 'published',
}
For more detailed information and API references, please refer to our complete API documentation.
For more detailed information and API references, please refer to our complete API documentation.