ToolJet Marketplace 插件开发实战:使用 tooljet CLI 从零构建 GitHub 数据源插件
【免费下载链接】ToolJetOpen-source foundation of ToolJet AI - the enterprise app generation platform for internal tools, dashboards, business applications, workflows and AI agents. Build visually, from a prompt, or from Claude Code, Codex and Cursor over MCP 🚀项目地址: https://gitcode.com/GitHub_Trending/to/ToolJet
ToolJet 的可扩展性是其核心设计理念之一,而 Marketplace 插件正是这种可扩展性的落地载体:开发者可以用 JavaScript/TypeScript 编写数据源连接器插件,并发布到 ToolJet Marketplace 供所有实例使用。本文以官方文档为主线,结合本仓库中的真实实现,带你用tooljetCLI 一步步创建一个基于 GitHub Personal Access Token 认证的 GitHub 数据源插件,实现获取用户信息、仓库、Issue 与 Pull Request 等基础能力。
读完本文,你将掌握:ToolJet 插件目录结构与四个核心文件(manifest.json、operations.json、index.ts、icon.svg)的作用、如何用 CLI 脚手架创建/删除插件、如何通过两个 schema 文件驱动前端动态 UI、如何用 Octokit 实现 QueryService 查询逻辑与错误处理,以及最终如何将插件发布到 Marketplace。
什么是 ToolJet 插件与 Marketplace
ToolJet 的开发一直以可扩展性为中心,允许开发者编写插件来扩展其能力。目前这些插件主要形态是连接器(connectors)——例如 PostgreSQL、MySQL、Twilio、Stripe 等数据源连接器。开发者可以使用 JavaScript/TypeScript 编写插件来增强 ToolJet 的功能,并通过 ToolJet Marketplace 发布、分发这些插件。
在发布侧,Marketplace 插件的源码统一存放在本仓库的 marketplace/plugins 目录中;在运行侧,ToolJet 服务端通过读取插件注册表来决定加载哪些插件。该注册表即 plugins.json 文件,其中记录了每个插件的名称、描述、版本、作者、时间戳等信息,例如 GitHub 插件的注册条目大致如下:
{ "name": "GitHub", "description": "Plugin for GitHub APIs", "version": "1.0.0", "id": "github", "author": "Tooljet", "timestamp": "Thu, 02 Mar 2023 11:52:32 GMT" }当通过tooljetCLI 创建一个插件时,CLI 会自动把上述格式的对象追加写入 plugins.json。ToolJet 服务端启动时会读取该文件并加载其中列出的所有插件。
注意:不要手工编辑 plugins.json。该文件由
tooljetCLI 自动生成与维护,手工改动可能导致插件在系统中无法正常工作。
一个典型插件(以 GitHub 为例)的目录结构如下:
github/ package.json lib/ icon.svg index.ts operations.json manifest.json对照本仓库中 marketplace/plugins/github 的实际实现,目录里除上述文件外还包含types.ts(TypeScript 类型定义)、query_operations.ts(具体查询函数)以及__tests__测试目录,说明仓库在实践中文档所述结构的基础上又做了进一步拆分。各核心文件职责如下:
manifest.json:描述插件(数据源)的名称、认证方式等元信息,用于生成连接表单;operations.json:描述该数据源支持的全部操作及其参数,用于在查询管理器中生成查询 UI;index.ts:定义插件的QueryService,负责处理查询执行、连接测试、连接缓存等核心逻辑;icon.svg:插件在界面中展示的图标;package.json:插件 npm 包定义,通常由 CLI 自动生成,之后可手工补充依赖。
前置准备:搭建 Marketplace 开发环境
动手开发插件前,需要先完成 Marketplace 的本地开发环境搭建,详细步骤见 Marketplace 开发环境搭建指南,核心要点如下:
1. 环境要求
- Node.jsv18.18.2
- npmv9.8.1
2. 在本地启动 ToolJet 并开启 Marketplace
按环境选择合适的本地 Setup 指南(macOS / Docker / Ubuntu)启动 ToolJet 后,需要在.env中配置以下环境变量:
| 变量 | 取值 | 作用 |
|---|---|---|
ENABLE_MARKETPLACE_FEATURE | true或false | 开启/关闭 Marketplace 功能开关 |
ENABLE_MARKETPLACE_DEV_MODE | true或false | 开发者模式:每当包发生变化时自动构建插件;同时会提供一个“刷新”按钮,用于从文件系统重新加载已安装插件的最新本地改动,极大方便开发迭代 |
注意 Marketplace 默认是不开启的,修改环境变量后需重启 ToolJet 实例。搭建完成后可通过/integrations路由访问 Marketplace。
3. 安装 marketplace 依赖并构建
在marketplace根目录执行:
cd marketplace npm install npm run build4. 安装 tooljet CLI
管理 Marketplace 插件的创建、更新与删除都需要tooljetCLI:
npm install -g @tooljet/cli # 验证安装成功 tooljet --version在 cli 目录的源码中可以看到 CLI 的插件子命令实现,包括 cli/src/commands/plugin/create.ts(创建插件)、cli/src/commands/plugin/delete.ts(删除插件)与 cli/src/commands/plugin/install.ts(安装插件)。
Step 1:使用 CLI 创建一个 GitHub 插件
在完成上述 Marketplace 环境搭建后,即可开始插件开发。在终端执行:
# 创建新插件 tooljet plugin create github命令执行期间 CLI 会依次向你提问:
- 输入插件名称(plugin name);
- 选择插件类型(plugin type),本示例选择
api; - 询问是否要为 marketplace 创建插件,选择yes;
- 如果你的插件托管在 GitHub 上,按提示提供仓库 URL;否则留空即可。
插件创建完成后,CLI 会自动在 plugins.json 中登记该插件的元数据对象,内容包含名称、描述、版本、作者及其他相关信息。
脚手架模板同样存放在仓库中,位于 marketplace/_templates/plugin,CLI 正是基于这类模板生成插件骨架。
Step 2:用 manifest.json 定义连接表单
连接表单(即用户在 ToolJet 中新建数据源时填写的凭据表单)由manifest.json驱动。为了让表单符合 GitHub 的认证需求,需要在其中声明认证相关选项。文档给出的核心示例为properties部分:
"properties": { "credentials": { "label": "Authentication", "key": "auth_type", "type": "dropdown-component-flip", "description": "A single select dropdown to choose credentials", "list": [ { "value": "personal_access_token", "name": "Use Personal Access Token" } ] }, "personal_access_token": { "token": { "label": "Token", "key": "personal_token", "type": "password", "description": "Enter your personal access token", "hint": "You can generate a personal access token from your Github account settings." } } }上述 schema 声明了两个顶级字段:
credentials属性用于声明认证方式,包含的键含义如下:
label:面向用户的友好标签,此处为 "Authentication";key:认证方式的唯一标识,值为auth_type,将作为存储时的字段名;type:控件类型,dropdown-component-flip表示一个可翻转展开方向的下拉选择器;description:字段用途说明;list:可用认证方式列表,其每个对象的value(存储值)为personal_access_token,name(展示名)为 "Use Personal Access Token"。
personal_access_token属性声明了具体令牌输入框,其下的token键包含:
label:展示为 "Token";key:存储键为personal_token;type:password,即密文输入框;description:提示文案 "Enter your personal access token";hint:辅助提示,建议用户从 GitHub 账户设置中生成 Personal Access Token。
在manifest.json中可用的type控件类型包括:
| type | 说明 |
|---|---|
password | 密文输入框,用于密码、Access Token 等敏感值 |
dropdown-component-flip | 下拉菜单,相对触发组件自动翻转展开方向 |
text | 单行文本输入 |
textarea | 多行文本输入 |
toggle | 简单的开/关开关 |
react-component-headers | 用于展示 React 组件分组标题 |
codehinter | 代码输入框,支持解析双花括号{{}}内的 JavaScript 表达式 |
结合本仓库中 GitHub 插件的真实 manifest.json,可以看到文档示例之外还有若干值得了解的字段:
source.name/source.kind/source.type:声明数据源名称、唯一 kind(如github)与类型(api);source.options:声明认证字段的数据类型,其中personal_token被标记为"encrypted": true,表示该凭据在服务端会加密存储;defaults:为字段提供默认值(例如auth_type默认personal_access_token);required:声明必填字段数组,例如["personal_token"]。
schema定义可参考仓库中的 manifest.schema.json(对应的操作 schema 见 operations.schema.json)。
manifest.json 与前端 UI 的关系:React 组件会读取manifest.json,依据其 schema 动态生成连接表单的 UI 组件——文本输入框、下拉框、复选框等控件均由此渲染而来。文件中的properties定义了连接 API 或数据源所需的字段及其类型。
Step 3:用 operations.json 定义操作 schema
operations.json描述某个特定数据源(如 GitHub)支持的全部操作及其参数,ToolJet 查询管理器(Query Manager)依据它生成“新建查询”界面,让用户选择操作并填写参数。文档中给出的 GitHub 插件示例properties如下:
"properties": { "operation": { "label": "Operation", "key": "operation", "type": "dropdown-component-flip", "description": "Single select dropdown for operation", "list": [ { "value": "get_user_info", "name": "Get user info" }, { "value": "get_repo", "name": "Get repository" }, { "value": "get_repo_issues", "name": "Get repository issues" }, { "value": "get_repo_pull_requests", "name": "Get repository pull requests" } ] }, "get_user_info": { "username": { "label": "Username", "key": "username", "type": "codehinter", "lineNumbers": false, "description": "Enter username", "width": "320px", "height": "36px", "className": "codehinter-plugins", "placeholder": "Enter username" } }, "get_repo": { "owner": { "label": "Owner", "key": "owner", "type": "codehinter", "lineNumbers": false, "description": "Enter owner name", "width": "320px", "height": "36px", "className": "codehinter-plugins", "placeholder": "developer" }, "repo": { "label": "Repository", "key": "repo", "type": "codehinter", "lineNumbers": false, "description": "Enter repository name", "width": "320px", "height": "36px", "className": "codehinter-plugins", "placeholder": "tooljet" } }, "get_repo_issues": { "owner": { "label": "Owner", "key": "owner", "type": "codehinter", "lineNumbers": false, "description": "Enter owner name", "width": "320px", "height": "36px", "className": "codehinter-plugins", "placeholder": "developer" }, "repo": { "label": "Repository", "key": "repo", "type": "codehinter", "lineNumbers": false, "description": "Enter repository name", "width": "320px", "height": "36px", "className": "codehinter-plugins", "placeholder": "tooljet" }, "state": { "label": "State", "key": "state", "className": "codehinter-plugins col-4", "type": "dropdown", "description": "Single select dropdown for choosing state", "list": [ { "value": "open", "name": "Open" }, { "value": "closed", "name": "Closed" }, { "value": "all", "name": "All" } ] } }, "get_repo_pull_requests": { "owner": { "label": "Owner", "key": "owner", "type": "codehinter", "lineNumbers": false, "description": "Enter owner name", "width": "320px", "height": "36px", "className": "codehinter-plugins", "placeholder": "developer" }, "repo": { "label": "Repository", "key": "repo", "type": "codehinter", "lineNumbers": false, "description": "Enter repository name", "width": "320px", "height": "36px", "className": "codehinter-plugins", "placeholder": "tooljet" }, "state": { "label": "State", "key": "state", "type": "dropdown", "className": "codehinter-plugins col-4", "description": "Single select dropdown for choosing state", "list": [ { "value": "open", "name": "Open" }, { "value": "closed", "name": "Closed" }, { "value": "all", "name": "All" } ] } } }operations.json的结构要点:
- 顶层的
operation下拉框用于让用户选择具体操作,其值对应后续各操作的字段组名称; - 每个操作(如
get_user_info、get_repo、get_repo_issues、get_repo_pull_requests)下都声明了执行该操作所需的参数字段; - 参数控件大量使用
codehinter类型——它支持解析{{ }}内的 JavaScript 表达式,意味着用户可以引用应用中的其他变量(如组件值、查询结果)来动态填充参数; - 对于
state这类有枚举取值的参数,则使用type: "dropdown"+list数组声明可选值(open/closed/all)。
参考仓库中 GitHub 插件的真实 operations.json 可以发现,实际 schema 比文档示例更丰富——例如get_repo_issues与get_repo_pull_requests还声明了page(页码)与page_size(每页条数)两个分页参数,印证了同一套 schema 规则可自由扩展。
两个 schema 文件的分工:manifest.json被连接弹窗组件使用,用于让用户填写数据源凭据;operations.json被查询管理器使用,用于在用户针对已连接的数据源创建查询时渲染参数表单。两者采用相同的 schema 约定。
Step 4:为插件安装 octokit npm 依赖
查询逻辑将基于 GitHub 官方 SDKoctokit实现。切换工作目录到插件目录,并以 workspace 方式安装:
# 切换到插件目录并安装 npm 包 npm i octokit --workspace=@tooljet-marketplace/github向某个插件安装 npm 包的一般形式是:
npm i <npm-package-name> --workspace=<plugin-name-in-package-json>--workspace标志用于在多包(monorepo)仓库中指定要安装包的特定 workspace。在这里,包会被安装到名为@tooljet-marketplace/github的 workspace 中。查看仓库中 GitHub 插件的 package.json,可以确认其name正是@tooljet-marketplace/github,并且:
- 运行时依赖为
@tooljet-marketplace/common(提供QueryService、QueryResult、QueryError等公共类型与基类)与octokit; build脚本为ncc build lib/index.ts -o dist,即插件会被打包为单个dist入口文件;- 打包产物声明为
dist/index.js,类型声明为dist/index.d.ts。
Step 5:在 index.ts 中实现查询执行逻辑
index.ts定义了插件的QueryService,负责处理查询执行的完整流程。它接收两类信息:
sourceOptions:数据源信息,包含连接凭据与配置。对于 GitHub 数据源即personal_token等认证信息;queryOptions:查询信息,包含用户为本次查询选择的配置与参数(如要拉取哪个用户/仓库的数据)。
QueryService 据此构造并执行对 GitHub API 的请求,最终把结果返回给调用方继续处理。
文档建议在插件源码目录下新建query_operations.ts,将每个操作的请求函数独立成文件(文档中目录写作plugins/github/src,而本仓库实际的 GitHub 插件将其放在 marketplace/plugins/github/lib/query_operations.ts,可理解为版本演进中目录命名的差异)。核心代码如下:
import { Octokit } from 'octokit'; import { QueryOptions } from './types'; export async function getUserInfo(octokit: Octokit, options: QueryOptions): Promise<object> { const { data } = await octokit.request('GET /users/{username}', { username: options.username, }); return data; } export async function getRepo(octokit: Octokit, options: QueryOptions): Promise<object> { const { data } = await octokit.request('GET /repos/{owner}/{repo}', { owner: options.owner, repo: options.repo, }); return data; } export async function getRepoIssues(octokit: Octokit, options: QueryOptions): Promise<object> { const { data } = await octokit.request('GET /repos/{owner}/{repo}/issues', { owner: options.owner, repo: options.repo, state: options.state || 'all', }); return data; } export async function getRepoPullRequests(octokit: Octokit, options: QueryOptions): Promise<object> { const { data } = await octokit.request('GET /repos/{owner}/{repo}/pulls', { owner: options.owner, repo: options.repo, state: options.state || 'all', }); return data; }query_operations.ts中每个函数负责一次具体查询,由index.ts中的 QueryService 按需调用。实际仓库中的 query_operations.ts 还加入了分页与参数校验:当传入page/page_size时,会用validateNumber校验取值范围(page ≥ 1,page_size 在 1~100 之间),再映射为 GitHub API 的page与per_page参数。
随后,在index.ts中定义Github类并实现QueryService接口(见 marketplace/plugins/github/lib/index.ts),其中三个关键方法各司其职:
run(sourceOptions, queryOptions, dataSourceId)——执行查询的入口。内部先从queryOptions.operation解析出操作类型,通过getConnection拿到已认证的 Octokit 客户端,再用switch分发到getUserInfo/getRepo/getRepoIssues/getRepoPullRequests对应函数;遇到未知操作或执行异常则抛出QueryError,成功则返回{ status: 'ok', data: result }的QueryResult。
testConnection(sourceOptions)——测试连接。在 ToolJet 应用中新建数据源时点击“测试连接”即触发该方法。它复用getConnection建立客户端,然后调用octokit.rest.users.getAuthenticated()获取当前认证用户:若请求成功返回{ status: 'ok' },失败则返回{ status: 'failed', message: 'Invalid credentials' }。在真实实现中,这些方法还会先封装一层try/catch再返回结果。
提示:并非所有数据源都支持连接测试。如果该能力不适用于你的数据源,可以在插件的
manifest.json中加入"customTesting": true来关闭“测试连接”按钮。
getConnection(sourceOptions)——辅助函数。从sourceOptions.personal_token读取令牌并构造一个已认证的 Octokit 客户端:
async getConnection(sourceOptions: SourceOptions): Promise<any> { const octokitClient = new Octokit({ auth: sourceOptions.personal_token, }); return octokitClient; }其中SourceOptions、QueryOptions、Operation等类型统一定义在 types.ts 中。此外,@tooljet-marketplace/common(见 marketplace/plugins/common)为所有插件提供了QueryService、QueryResult、ConnectionTestResult、QueryError等公共契约。
Step 6:错误处理——把 errorDetails 回传给 Plugin SDK
查询执行出错时,必须把从 Plugin SDK 收到的错误信息返回给用户。为此需要在index.ts的run方法中构造并抛出带errorDetails的QueryError。需要注意的是,错误的具体参数因插件而异:
- Plugin SDK 中的
data字段对应代码中的errorDetails; - 动态生成的
errorMessage对应错误预览中的description字段。
以 MongoDB 场景为例,若出现如下错误(点击“测试”可看到 MongoDB 返回的完整错误预览):
可以这样实现错误处理:
catch (error) { let errorMessage = 'An unknown error occurred'; let errorDetails = {}; if (error instanceof Error) { errorMessage = error.message || errorMessage; errorDetails = { name: error.name, code: (error as any).code || null, codeName: (error as any).codeName || null, keyPattern: (error as any).keyPattern || null, keyValue: (error as any).keyValue || null, }; } throw new QueryError('Query could not be completed', errorMessage, errorDetails); }这段代码确保错误消息与详情被正确送回 Plugin SDK,从而让用户在查询错误预览中看到有意义的提示。错误在 ToolJet 查询编辑器中的预览效果如下:
从 index.ts 的实际实现可见,错误处理的核心方式是一致的:将原始错误对象封装进new QueryError('Query could not be completed', error.message, errorDetails)后抛出。插件内各查询函数的错误信息最终都会汇聚到这一层,再由 ToolJet 前端呈现给用户。
删除一个插件
如需删除插件,执行以下命令:
tooljet plugin delete PLUGIN_NAMECLI 在删除前会先询问确认:该插件是否为 marketplace 插件,确认无误后再继续删除操作。
发布插件到 Marketplace
插件开发完成后,即可准备发布:在 ToolJet 的 GitHub 仓库上提交一个 Pull Request(创建插件的 PR)。ToolJet 团队会进行 review,若被批准,插件将随下一个版本一同包含并发布到 Marketplace。
小结
从本文可以梳理出开发一个 ToolJet Marketplace 插件的完整闭环:
- 搭建 Marketplace 开发环境(
ENABLE_MARKETPLACE_FEATURE/ENABLE_MARKETPLACE_DEV_MODE),用tooljet plugin create github生成脚手架; - 编写
manifest.json声明数据源认证 schema,驱动连接弹窗 UI; - 编写
operations.json声明可执行操作与参数 schema,驱动查询管理器 UI; - 通过
npm i <pkg> --workspace=@tooljet-marketplace/<name>安装 Octokit 等依赖; - 在
query_operations.ts中实现查询函数,在index.ts中实现run/testConnection/getConnection,完成 QueryService; - 用
QueryError+errorDetails做统一错误处理; - 用
tooljet plugin delete管理生命周期,并通过提交 PR 发布到 Marketplace。
本仓库中 marketplace/plugins/github 是这套流程的完整范本,marketplace/plugins 目录下还有 OpenAI、Anthropic、Jira、Salesforce 等四十余个插件可以对照研读;manifest.schema.json 与 operations.schema.json 则提供了两个 schema 文件的字段约束定义。掌握了这套流程后,你几乎可以为任何 REST API 快速产出可安装、可发布、可复用的 ToolJet 数据源插件。
【免费下载链接】ToolJetOpen-source foundation of ToolJet AI - the enterprise app generation platform for internal tools, dashboards, business applications, workflows and AI agents. Build visually, from a prompt, or from Claude Code, Codex and Cursor over MCP 🚀项目地址: https://gitcode.com/GitHub_Trending/to/ToolJet
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考