news 2026/9/9 13:28:03

ToolJet Marketplace 插件开发实战:使用 tooljet CLI 从零构建 GitHub 数据源插件

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ToolJet Marketplace 插件开发实战:使用 tooljet CLI 从零构建 GitHub 数据源插件

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.jsonoperations.jsonindex.tsicon.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_FEATUREtruefalse开启/关闭 Marketplace 功能开关
ENABLE_MARKETPLACE_DEV_MODEtruefalse开发者模式:每当包发生变化时自动构建插件;同时会提供一个“刷新”按钮,用于从文件系统重新加载已安装插件的最新本地改动,极大方便开发迭代

注意 Marketplace 默认是不开启的,修改环境变量后需重启 ToolJet 实例。搭建完成后可通过/integrations路由访问 Marketplace。

3. 安装 marketplace 依赖并构建

marketplace根目录执行:

cd marketplace npm install npm run build

4. 安装 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_tokenname(展示名)为 "Use Personal Access Token"。

personal_access_token属性声明了具体令牌输入框,其下的token键包含:

  • label:展示为 "Token";
  • key:存储键为personal_token
  • typepassword,即密文输入框;
  • 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_infoget_repoget_repo_issuesget_repo_pull_requests)下都声明了执行该操作所需的参数字段;
  • 参数控件大量使用codehinter类型——它支持解析{{ }}内的 JavaScript 表达式,意味着用户可以引用应用中的其他变量(如组件值、查询结果)来动态填充参数;
  • 对于state这类有枚举取值的参数,则使用type: "dropdown"+list数组声明可选值(open/closed/all)。

参考仓库中 GitHub 插件的真实 operations.json 可以发现,实际 schema 比文档示例更丰富——例如get_repo_issuesget_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(提供QueryServiceQueryResultQueryError等公共类型与基类)与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 的pageper_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; }

其中SourceOptionsQueryOptionsOperation等类型统一定义在 types.ts 中。此外,@tooljet-marketplace/common(见 marketplace/plugins/common)为所有插件提供了QueryServiceQueryResultConnectionTestResultQueryError等公共契约。

Step 6:错误处理——把 errorDetails 回传给 Plugin SDK

查询执行出错时,必须把从 Plugin SDK 收到的错误信息返回给用户。为此需要在index.tsrun方法中构造并抛出带errorDetailsQueryError。需要注意的是,错误的具体参数因插件而异:

  • 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_NAME

CLI 在删除前会先询问确认:该插件是否为 marketplace 插件,确认无误后再继续删除操作。

发布插件到 Marketplace

插件开发完成后,即可准备发布:在 ToolJet 的 GitHub 仓库上提交一个 Pull Request(创建插件的 PR)。ToolJet 团队会进行 review,若被批准,插件将随下一个版本一同包含并发布到 Marketplace。

小结

从本文可以梳理出开发一个 ToolJet Marketplace 插件的完整闭环:

  1. 搭建 Marketplace 开发环境(ENABLE_MARKETPLACE_FEATURE/ENABLE_MARKETPLACE_DEV_MODE),用tooljet plugin create github生成脚手架;
  2. 编写manifest.json声明数据源认证 schema,驱动连接弹窗 UI;
  3. 编写operations.json声明可执行操作与参数 schema,驱动查询管理器 UI;
  4. 通过npm i <pkg> --workspace=@tooljet-marketplace/<name>安装 Octokit 等依赖;
  5. query_operations.ts中实现查询函数,在index.ts中实现run/testConnection/getConnection,完成 QueryService;
  6. QueryError+errorDetails做统一错误处理;
  7. 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 13:24:59

ECC内存从原理到排障:读懂uncorr.ecc与MBIST

前阵子帮朋友处理一台数据库服务器的告警&#xff0c;后台日志里赫然写着“uncorr. ecc 显示2”。对于刚接触服务器运维的人来说&#xff0c;这种信息很容易被当成“内存要挂了、系统快崩了”的凶兆&#xff0c;但实际上它背后是ECC内存机制在工作。ECC&#xff08;Error Corre…

作者头像 李华
网站建设 2026/9/9 13:23:57

跨量级数据可视化:分段线性映射如何破解大屏图表失真难题

刚接手一个数据看板类的项目时&#xff0c;我遇到过一个特别头疼的场景&#xff1a;同一张大屏上&#xff0c;既要展示在线人数的实时波动&#xff0c;又要展示接口请求耗时&#xff0c;还要展示订单金额的分布。这三个指标的数据范围完全不在一个世界里——在线人数可能是几万…

作者头像 李华
网站建设 2026/9/9 13:23:19

goose 如何使用 Code Mode 降低启用大量扩展时的上下文开销?

goose 如何使用 Code Mode 降低启用大量扩展时的上下文开销&#xff1f; 【免费下载链接】goose an open source, extensible AI agent that goes beyond code suggestions - install, execute, edit, and test with any LLM 项目地址: https://gitcode.com/GitHub_Trending/…

作者头像 李华
网站建设 2026/9/9 13:20:36

从1234567到写出旋律:简谱入门与数字音频合成实践

这串数字在我电脑的文件夹里躺了十几年。有人看到"1234567"只当它是普通计数&#xff0c;可在音乐人眼里&#xff0c;它就是旋律最原始的编码&#xff1a;Do、Re、Mi、Fa、Sol、La、Si。这篇文章我想把这串数字拆开&#xff0c;讲讲它背后的简谱体系、音高物理、节奏…

作者头像 李华