关于 fcode-forms
fcode-forms 是 Factorial Code 的表单技能,可将流程参数表单嵌入网页,并在提交时启动流程。它说明表单配置、版本固定、结果处理及异步执行方式。
主要功能
- 01将流程表单嵌入网页
- 02通过参数 Schema 定义表单
- 03使用 stable 固定流程版本
- 04处理提交结果、错误和重定向
- 05将耗时任务改为异步执行
适用场景
- 01在网站中收集数据并启动 Factorial 流程
- 02在 React 页面嵌入表单
- 03通过 stable 控制表单使用的流程版本
- 04将长时间运行的工作移至异步流程
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ factorialco/ factorial-code-skills --skill fcode-forms安装后在 Claude Code 中输入/fcode-forms确认已加载。
附带文件references/advanced.md
详细介绍
Factorial Code — 表单
Factorial Code 表单可将流程的输入参数表单嵌入任意网页。
每次提交都会使用表单数据启动一次流程执行,结果会在页面内处理(消息、重定向、回调)。有关架构本身,请参阅
fcode-json-schema。
注意事项
- 表单就是流程的
parametersSchema.json——没有单独的表单定义。若要更改字段/验证/标签,请编辑架构,而不是 嵌入代码。 - 每个嵌入项都必须同时指定
team和process/processId,且两者都必须使用代号,而不是每个工作区的 UUID。 - 始终将嵌入项固定到
stable别名 (data-fcode-form-process-version="stable"/processVersion: "stable")。 未固定版本的表单会运行当前版本,因此每次fcode push都会立即更改它。详情见下文。 - 版本或别名未知时,表单不会报错——而是静默运行当前版本(见“将表单固定到某个版本”)。
- 必须启用
Forms标志——在流程控制面板的从 Factorial 触发部分启用,或在流程的metadata.json中设置factorial.form.enabled(旧版写法:"form": { "enabled": true }),然后执行fcode push;否则嵌入表单不会呈现。 - 表单需要 Factorial 身份,除非你将其公开发布。 默认情况下,表单通过 Factorial 访问;Factorial 会验证用户身份并注入可信上下文,而只有团队和流程代号的匿名访客无法读取架构或提交表单。对于确实需要公开的表单,可通过
factorial.form.public关闭此限制。无论哪种情况,表单背后的流程都会限制调用者可以执行的操作(见下文)。 - 从 Factorial 内部打开的表单通过
factorial.form配置,即 Factorial Action 模块(fcode-factorial-actions)。form.enabled仍然有效,并会保持同步。向用户展示在 App 自有页面上的表单由factorial.form.appTool指定;安装/设置/卸载表单则由form.appRole命名;如果流程未声明角色,则使用保留代号(fcode-factorial-actions)。对于面向 Factorial 之外用户的表单,factorial.form.public会将其公开给任何持有链接的人。 - 架构不能包含可执行的 JavaScript。
embedFormOptions.onChange和字段transformFn已移除,消息使用 Markdown 格式——不会呈现原始 HTML。客户端行为应由嵌入表单的网页实现。 - 切勿将密钥放入嵌入代码或
options中——它们会在浏览器中运行。 - 连接第三方账户(Slack、GitHub 等)是架构功能——使用
"ui:widget": "oauth",并配置一个fcode.oauth流程;该流程完成后会重定向到 SDK 的回调页面。若服务商提供 OAuth,切勿要求用户在表单中粘贴 API 令牌。请参阅“连接外部账户”。 - 表单文本通过架构中的
fcode.i18n("key")令牌翻译,并在向客户端提供架构前由服务器替换。请参阅fcode-i18n。 - 表单提交会受到请求超时限制(约一分钟)——请确保同步流程运行迅速,或将耗时工作异步执行(见下文)。
- 优先通过流程返回值驱动用户体验(见下文);仅将
onSuccess/onError用于客户端逻辑。
启用表单
- 创建流程并定义其输入参数(这些参数会成为字段)。
- 启用
Forms标志——可在流程 Dashboard 的 Trigger from Factorial 部分启用,也可在 CLI 工作区的processes/<slug>/metadata.json中启用,然后运行fcode push:
{
"name": "Contact request",
"tags": [],
"factorial": { "enabled": true, "form": { "enabled": true } }
}
表单是 Factorial Action 的一个入口点——同一个 factorial
区块还包含按钮、Factorial One 工具和后端任务
(fcode-factorial-actions,字段参考见 fcode-cli)。旧版顶层 "form": { "enabled": true } 仍然有效,并会与 factorial.form.enabled 保持同步;新代码中请优先使用 factorial。
Marketplace 应用的生命周期表单是指使用保留 slug install、settings 和 uninstall 的流程(sync 是第四个保留 slug,用于集成同步流程);平台会根据 slug 判断其角色。
应用在 Factorial 中的页面上向自己的用户提供的表单,配置为
"factorial": { "form": { "enabled": true, "appTool": true } }。在新流程中,切勿编写
form.appRole: USER_FACING_FORM——这正是 appTool 设置的值,平台会让两者保持同步。其他三个值则不同:此区块没有对应的开关,因此安装、设置或卸载表单要么通过 form.appRole 命名,要么使用保留 slug。新流程请优先使用 slug;fcode-factorial-actions 中说明了相关规则,以及如何在已发布的应用中采用该设置。
应用配置完成后,install 或 settings 表单会再次打开,因此应显示当前值,而不是空表单。关于如何实现,可参阅 references/advanced.md 中的 preRenderProcess 模式。
在流程代码中像读取其他参数一样读取已提交的值:
const { context: { parameters } } = fcode;
表单可以信任什么
用户通过 Factorial 访问表单时,Factorial 会对其进行身份验证:安装/设置/卸载页面、应用在自身页面上提供的表单,以及 UI 触发器对话框(fcode-ui-triggers)都通过 Factorial 的后端提交,并且在执行任何操作之前都会强制检查 requiredPolicies(fcode-factorial-actions)。仅持有团队和流程 slug 的匿名访客无法访问任何内容。
除非表单设置了 factorial.form.public。该标志会让任何持有链接的人都能访问表单,此时调用者完全未经身份验证:此类提交携带的 company_id 和 access_id 都是页面发送的值。只有当表单面向 Factorial 之外的人员时,才启用此标志;并且应将以下内容视为表单仅有的保护措施。
这解决的是谁在调用的问题,但无法确保他们提交了什么,因此表单背后的流程仍然是防护措施:
- 将每个已提交参数都视为由调用者控制——包括嵌入页面预填的参数(
company_id、triggered_from_location、access_id)。切勿根据这些参数授权。 - 不要返回调用者本不应已拥有的信息——提交结果以及
preRenderProcess的variables都会发送给打开 URL 的人。 - 凭据只允许单向流动:设置表单只能报告是否已设置密钥,绝不能返回密钥的值(
references/advanced.md)。
嵌入表单
两个必填输入项,均为 slug,另外还应始终添加版本固定值:
fcode-team-slug— 来自https://code.factorialhr.com/platform/<fcode-team-slug>fcode-process-slug— 流程 Dashboard 上的 Slug 字段(例如send-welcome-email)- 流程版本 — 固定为
stable别名(见下一节)
请使用 slug,而不是流程 ID。 ID 是每个工作区独有的 UUID,因此基于 ID 的嵌入代码在不同工作区之间迁移时会失效;slug 则不会(现有基于 ID 的嵌入代码仍可正常工作,而且 React 属性仍名为 processId——请传入 slug)。slug 可以编辑,但旧值不会重定向,因此在分发嵌入代码前请确定 slug——重命名会导致所有已粘贴到页面中的嵌入代码失效。
只需加载一次 SDK(数据属性和 Fcode.initForm 方法都需要它):
<script defer src="https://code.factorialhr.com/sdk/forms.js"></script>
方法 1 — 数据属性(SDK 会替换该元素):
<div
data-fcode-form-team="<fcode-team-slug>"
data-fcode-form-process="<fcode-process-slug>"
data-fcode-form-process-version="stable"
></div>
方法 2 — Fcode.initForm(选择器或 DOM 元素):
<div id="my-fcode-form"></div>
<script>
Fcode.initForm("#my-fcode-form", {
team: "<fcode-team-slug>",
process: "<fcode-process-slug>",
processVersion: "stable",
});
</script>
方法 3 — FcodeForm React 组件(React 17/18;安装
@factorialco/fcode-react-forms):
import FcodeForm from "@factorialco/fcode-react-forms";
const MyComponent = () => (
<FcodeForm
team={"<fcode-team-slug>"}
processId={"<fcode-process-slug>"}
processVersion={"stable"}
/>
);
在 SSR 框架(例如 Next.js)中,通过动态导入并设置 ssr: false。
将表单固定到某个版本
版本固定值(data-fcode-form-process-version / processVersion)可以是已发布的版本标签(v1.0.0)或别名——请使用 stable,这样只需移动别名即可发布和回滚,无需编辑嵌入代码所在的页面(别名模式见 fcode-core-concepts;发布命令见 fcode-cli)。该固定值适用于表单发出的两种请求——加载定义和提交表单。流程 Dashboard 会为你填写该值:在嵌入代码旁的选择器中选择版本或别名,然后复制生成的代码片段。
未知版本或别名会静默地运行当前版本——平台只会记录一条服务器端警告,webhook 也是如此(参见 fcode-cli)。属性中的拼写错误不会显现出来:如果提交行为异常,请检查执行记录中的版本。直接调用表单端点时,也可以使用 fcode-cli 中说明的 version_tag 查询参数。
处理提交结果
默认情况下:执行期间会显示加载遮罩;成功时表单会替换为成功消息,出错时则替换为错误消息。
回调(所有方法的格式相同):
Fcode.initForm("#my-fcode-form", {
team: "<fcode-team-slug>",
process: "<fcode-process-slug>",
onSuccess: (formId, processExecutionResult, formSubmittedData) => {},
onError: (formId, error, formSubmittedData) => {},
});
使用数据属性时,通过全局函数指定处理程序:
data-fcode-form-on-success="HANDLER_NAME"、
data-fcode-form-on-next-step="..." 和 data-fcode-form-on-error="..."。
根据流程返回值驱动行为(无需客户端代码):
return { message: "Thanks, **we received your request**." }; // success message (markdown)
return { message: "| Item | Status |\n|---|---|\n| Sync | Done |" }; // GFM tables work too
return { status: 400, body: { formErrors: { // inline validation errors
fields: { email: "Invalid email." }, global: ["A global error."] } } };
return { status: 400, body: { errorMessage: "**Sync failed** — retry." } }; // error message (markdown)
return { redirect: { url: "https://example.com", timeout: 2000 } }; // redirect after submit
消息使用 Markdown,而非 HTML。 message、errorMessage 以及 schema 中的
markdown.before / markdown.after 块会按 GitHub Flavored Markdown 渲染——包括表格、标题、链接、图片、代码块和任务列表。原始 HTML 会被直接丢弃,不会渲染(因此不会执行任何编写的内容);请改用成功 / 下一步 / 错误回调来实现行为。
保持快速,或改用异步
提交会等待进程完成,并受请求超时限制(约一分钟)。在行内执行的繁重工作——缓慢的 API 调用、大型导出、多记录同步——会导致超时,提交失败。
如果工作可能耗时,请改用异步:
- 嵌入
async: true——提交会立即返回201和执行 ID,而不是等待结果(参见references/advanced.md)。 - 交由另一个进程处理——使用
fcode.processes.run("process-identifier", options)启动繁重工作(参见fcode-javascript/fcode-python),然后快速返回确认信息(message/redirect),不要在行内等待它完成。
仅当请求确实很快,或数据必须在步骤之间流转时,才保持同步。传递数据时,不要阻塞提交,而应使用:
preRenderProcess在表单呈现前于服务端计算variables(参见references/advanced.md)。- 多步骤链式表单通过
nextProcessId+variables传递状态(见下文)。如果这些步骤只有视觉上的区分——步骤之间不执行任何工作——请改用ui:steps,并保留单个进程。
多步骤表单
有两种机制,选择合适的一种很重要:
| 机制 | 进程 | 执行 | 适用情况 |
|---|---|---|---|
ui:steps——单个表单中的步骤 | 一个 | 最后一步之后执行一次 | 仅在视觉上拆分;所有逻辑都在一个进程中 |
nextProcessId——链式流程 | 每步一个 | 每步执行一次 | 中间步骤必须实际执行某些操作(例如外部验证、为下一个表单计算 variables) |
两者可以组合使用:链式流程中的表单本身也可以声明 ui:steps。
当表单变得较长——大约有十个或更多字段时——请用 ui:steps 拆分,将相关字段分组到各个步骤中,并将必填字段放在前面。
单个表单中的步骤(ui:steps)
在架构的根 ui 节点中声明步骤,并将每个属性分配给一个步骤。SDK 一次显示一个步骤,并提供“下一步”按钮;只有最后一步的按钮会提交表单并启动(单次)执行。该执行会接收所有收集到的参数,与单页表单完全相同——无需更改进程代码。
"ui": {
"ui:steps": {
"config": { "layout": "tabs" },
"steps": [
{ "title": "About you", "description": "Optional text", "fields": ["name"] },
{ "title": "Details", "fields": ["age", "subscribe"] }
]
}
}
config(可选):layout——"tabs"(默认)或"sidebar";nextLabel/backLabel——按钮标签,默认为"Next"/"Back"。ui:submitButtonOptions适用于最后一步的按钮。- “下一步”只会验证当前显示的字段(包括该步骤所需的
required);返回按钮和步骤导航可以重新访问已完成的步骤,且不会丢失输入;尚未到达的步骤则保持禁用状态。 - 未列入任何步骤的属性会追加到最后一步;无法使用的
ui:steps会回退到普通单页表单,并在控制台显示警告。 - 支持根级别的
properties+required+dependencies;根级别的oneOf/allOf组合不会拆分为多个步骤。SDK 版本要求见references/advanced.md。
链接进程(nextProcessId)
每个步骤都是独立的进程。返回下一个进程的slug以继续:
return { nextProcessId: "collect-shipping-address" };
字段名仍为 nextProcessId,它接受 slug 或 ID——请使用 slug,这样同一条链就能在每个工作区中运行。
接下来,SDK 会呈现 nextProcessId 对应的表单。每个后续步骤都会在 fcode.context.parameters 的 steps 数组中接收前面所有步骤的数据和结果。返回与 nextProcessId 并列的 variables 节点,即可向后续步骤传递状态。
连接外部账户(OAuth)
表单可以要求用户先授权第三方服务提供商,再提交表单。声明一个 string 或 boolean 属性,并设置 "ui:widget": "oauth"(object 属性则应改用 "ui:field": "oauth"——rjsf 不会读取对象上的 ui:widget):
"github_account": {
"title": "GitHub account",
"description": "You will be asked to authorize in a new window.",
"type": "string",
"ui": {
"ui:widget": "oauth",
"ui:options": {
"authorizationUrl": { "$ref": "#/variables/authorizeUrl" },
"connectLabel": "Connect GitHub",
"connectedLabel": { "$ref": "#/variables/connectedLabel" },
"onComplete": "reload"
}
}
}
SDK 会渲染一个 连接 按钮,在小型
弹出窗口中打开 authorizationUrl,等待流程结束,关闭弹出窗口并填充字段。该
属性的 title / description 是按钮周围的说明(和其他地方一样支持 Markdown,
较长的文案可使用 markdown.before/after)。
ui:options | 默认值 | 用途 |
|---|---|---|
authorizationUrl | — | fcode.oauth.start() 返回的平台授权 URL。必填;URL 无效时会停用按钮并显示控制台警告 |
connectLabel / connectedLabel / pendingLabel | Connect / Connected / Waiting for authorization… | 流程开始前、结束后和进行期间的按钮文字 |
onComplete | none | 连接成功后表单的处理方式(见下文) |
popup | { "width": 600, "height": 700 } | 弹出窗口的尺寸,在页面中居中显示 |
closeDelay | 3000 | 完成后弹出窗口保持打开的毫秒数,以便用户看到确认信息 |
让它正常工作的三条规则:
-
authorizationUrl来自fcode.oauth.start(),该函数在preRenderProcess中调用,并通过{"$ref": "#/variables/…"}注入——普通的{{mustache}}标记会经过 HTML 转义(/→/,&→&), SDK 会拒绝此 URL。预渲染约定见references/advanced.md。const flow = await fcode.oauth.start({ authorizeUrl: "https://github.com/login/oauth/authorize", clientId: fcode.env.GITHUB_CLIENT_ID, scope: ["public_repo"], onComplete: "github-oauth-callback", data: { companyId }, }); return { variables: { authorizeUrl: flow.authorizationUrl } };切勿手动构建授权 URL、
state或 PKCE 密钥对。平台持有这三者,以及向提供商注册的唯一redirect_uri——这是整个流程能够正常工作的唯一方式,因为提供商要求 URI 必须预先注册,而且每次安装都有自己的deploy-工作区。 -
完成流程不需要 webhook。 平台验证并使用
state后,会自行调用onComplete,因此无需公开端点,也无需进行任何身份验证。调用时会传入code、codeVerifier、redirectUri(在令牌交换中原样传回——提供商会逐字节比较该值)、data和state;如果提供商拒绝请求,则会传入error/errorDescription。流程会交换授权码,在服务端存储令牌(存入敏感变量,或使用加密标记的 datastore——参见fcode-javascript/fcode-python),最后将 弹出窗口重定向到 SDK 的回调页面:const { state } = fcode.context.parameters; return { status: 302, headers: { Location: "https://code.factorialhr.com/sdk/oauth-callback.html" + "?status=success&value=" + encodeURIComponent(login) + "&state=" + encodeURIComponent(state), }, };该页面会告知表单流程结果——表单只信任来自它所打开窗口的消息——然后自行关闭。查询参数:
status(success;其他任何值都视为错误)、value(成为字段值)、state(必填,从回调参数中原样传回)、message(出错时显示在按钮下方),以及object字段应接收的任何额外参数。每种退出情况都要传回
state,包括错误情况。它用于将处理结果绑定到表单打开的弹出窗口;如果通过备用通道返回时未包含该值,完成结果就会被丢弃——onComplete: "reload"用于恢复因 COOP 断开连接的情况。即使令牌交换成功且令牌已存储,流程仍会以取消状态结束。 -
value是不透明句柄(例如账户登录名、连接 ID)——绝不能是令牌:它会传到浏览器,并随提交内容一起发送。已连接状态只是向用户发出的信号,不是凭证:接收提交内容的流程必须在自身侧验证连接。 字段值。boolean→true;string→value参数;object→ 除status/message外的每个参数。连接前字段值为空, 因此将字段标记为required可避免账户连接前提交表单。提前关闭弹出窗口 会被视为取消,并重新启用按钮。发生错误时,会在按钮下方显示错误的message,并重新加载表单定义,无论onComplete的设置是什么:回调已经运行, 可能已消耗授权 URL 中的一次性state,因此只有重新运行preRenderProcess,才能为按钮提供有效 URL。输入的值和消息会在重新加载后保留, 不会提交任何内容,用户只需重试。若重新加载失败,表单将保持原样。
设置 default 会将按钮渲染为已连接状态。 只要字段有值,按钮就处于已连接
状态,因此,如果 preRenderProcess 检测到现有连接,就会返回带有 default
的属性,并将连接句柄设为其值——按钮会渲染为已连接(禁用),该句柄也会像其他
默认值一样随提交一起传递。用户再次返回、在关闭弹出窗口后完成连接,或使用
onComplete: "reload" 时,都会进入已连接视图;以此方式传入的值也会清除较早的
错误消息。
onComplete — 连接后要执行的操作:
none— 按钮显示已连接状态;用户照常提交,流程会将该值与其他参数一并接收。submit— 立即提交表单,因此返回值中的message/redirect/nextProcessId会立即生效。在ui:steps表单中,这会进入下一步。reload— 重新获取表单定义,再次运行preRenderProcess,以便服务器检查连接并渲染已连接状态(不同的文案、更多字段, 或通过default将按钮变为“已连接为 …”)。其他字段中输入的值会保留。 由于服务器是此处的事实来源,即使关闭弹出窗口时尚未到达回调页面,表单也会重新加载—— 这是唯一会在取消时重新加载的模式,因为回调并未运行,URL 仍然有效。
需要注意的限制:弹出窗口由点击操作直接打开,因此默认的弹出窗口拦截器会允许打开——
如果浏览器设置为阻止所有弹出窗口,则会在按钮下方显示一条消息;嵌入页面或提供方上的
Cross-Origin-Opener-Policy 标头可能会切断与弹出窗口的连接,此时流程会以取消结束,
而 onComplete: "reload" 是恢复方式。
需要托管版 SDK 或 @factorialco/fcode-react-forms ≥ 3.3.0(f0 控件:
@factorialco/rjsf-f0 ≥ 2.3.0)。
fcode-examples
(references/oauth-connect.md)中提供了完整示例,涵盖启动流程的预渲染、
完成流程,以及验证连接的表单流程。
自动文件上传
文件字段是 "type": "string",并且在属性内部包含
"ui": { "ui:widget": "file" } 键:
{
"properties": {
"inputFile": { "type": "string", "ui": { "ui:widget": "file" } }
}
}
(也会合并以字段名为键的根级 ui 映射——这是 rjsf 的 uiSchema 约定——但每个属性上的
ui 才是文档所述的形式;根级 ui 主要用于 ui:submitButtonOptions 等表单级选项。
对于机密输入,建议在属性上使用 "isSensitive": true——它会自动渲染为密码控件;
参见 fcode-json-schema。)
提交时,文件会在流程启动前上传到 Storage,参数则以 fcode.storage://…
引用的形式传入(允许多个文件时为数组)。去掉前缀即可下载:
const { context: { parameters } } = fcode;
const stream = await fcode.storage.download(
parameters.inputFile.replace("fcode.storage://", "")
);
上传的文件会计入存储限制(1,000 个文件,每个 50 MB——
fcode-core-concepts › 计划与限制);如果文件仅需临时使用,请在流程结束时删除。
高级
有关样式和两种主题(包括在 Factorial 内的 React 应用中嵌入时使用的 f0 主题)、使用 f0 组件渲染 Markdown 消息(markdownComponents)、初始值/隐藏值、异步提交、自定义请求头、API 主机覆盖、变量替换、将当前值预渲染到安装/设置表单中、响应来自你自己页面的用户输入(React onChange 属性、fcode-forms-* DOM 事件)以及模态框渲染,请参阅 references/advanced.md。