fcode-forms

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Factorial Code — Forms

Factorial Code — 表单

A Factorial Code Form embeds a process's input-parameter form on any webpage. Each submission starts a process execution with the form data, and the result is handled in-page (messages, redirects, callbacks). For the schema itself, see
fcode-json-schema
.
Factorial Code表单可将流程的输入参数表单嵌入到任意网页中。每次提交都会使用表单数据启动流程执行,结果会在页面内处理(消息、重定向、回调)。关于架构本身,请参阅
fcode-json-schema

Gotchas

注意事项

  • The form is the process's
    parametersSchema.json
    — there is no separate form definition. To change fields/validation/labels, edit the schema, not the embed code.
  • team
    and
    process
    /
    processId
    are both mandatory
    on every embed, and both take slugs — not the per-workspace UUIDs.
  • Always pin the embed to the
    stable
    alias
    (
    data-fcode-form-process-version="stable"
    /
    processVersion: "stable"
    ). An unpinned form runs the current version, so every
    fcode push
    changes it immediately. Details below.
  • An unknown version or alias doesn't fail the form — it silently runs the current version (see "Pin the form to a version").
  • The
    Forms
    flag must be enabled
    — on the process Dashboard, or via
    "form": { "enabled": true }
    in the process's
    metadata.json
    +
    fcode push
    — or the embed won't render.
  • A new form requires a Factorial user by default (
    authMode: FACTORIAL
    ). An embed on a public page needs
    authMode: NONE
    , or every request gets a
    401
    (below).
  • A schema can't carry executable JavaScript.
    embedFormOptions.onChange
    and field
    transformFn
    were removed, a returned
    jsCallback
    is ignored, and authored HTML is sanitized. Client-side behaviour lives in the embedding page.
  • Never put secrets in embed code or
    options
    — they run in the browser.
  • Form text is translated with
    fcode.i18n("key")
    tokens in the schema
    , substituted server-side before the schema is served. See
    fcode-i18n
    .
  • Form submissions run under a request timeout (about a minute) — keep the synchronous process fast, or run long work asynchronously (see below).
  • Prefer driving UX from the process return value (below); reserve
    onSuccess
    /
    onError
    for client-only logic.
  • 表单即流程的
    parametersSchema.json
    ——没有单独的表单定义。要更改字段/验证/标签,请编辑架构,不要修改嵌入代码。
  • team
    process
    /
    processId
    在所有嵌入中都是必填项
    ,且两者均使用slug——而非每个工作区的UUID。
  • 始终将嵌入固定到
    stable
    别名
    data-fcode-form-process-version="stable"
    /
    processVersion: "stable"
    )。未固定版本的表单会运行当前版本,因此每次
    fcode push
    都会立即更改它。详情见下文。
  • 未知版本或别名不会导致表单失败——它会静默运行当前版本(请参阅“将表单固定到版本”)。
  • 必须启用
    Forms
    标志
    ——在流程仪表板上,或通过流程的
    metadata.json
    中的
    "form": { "enabled": true }
    +
    fcode push
    ——否则嵌入将无法渲染。
  • 默认情况下,新表单需要Factorial用户
    authMode: FACTORIAL
    )。公共页面上的嵌入需要设置
    authMode: NONE
    ,否则每个请求都会返回
    401
    (见下文)。
  • 架构不能包含可执行JavaScript
    embedFormOptions.onChange
    和字段
    transformFn
    已被移除,返回的
    jsCallback
    会被忽略,编写的HTML会被清理。客户端行为由嵌入页面控制。
  • 切勿在嵌入代码或
    options
    中放入机密信息
    ——它们会在浏览器中运行。
  • 表单文本通过架构中的
    fcode.i18n("key")
    令牌进行翻译
    ,在架构被提供前由服务器端替换。请参阅
    fcode-i18n
  • 表单提交受请求超时限制(约一分钟)——保持同步流程快速,或将长时间任务异步运行(见下文)。
  • 优先通过流程返回值驱动用户体验(见下文);将
    onSuccess
    /
    onError
    保留用于客户端专属逻辑。

Enable a form

启用表单

  1. Create the process and define its input parameters (these become the fields).
  2. Enable the
    Forms
    flag — either on the process Dashboard, or from the CLI workspace in
    processes/<slug>/metadata.json
    , then
    fcode push
    :
json
{
  "name": "Contact request",
  "tags": [],
  "form": { "enabled": true }
}
For marketplace app processes,
form
also takes an optional
"appRole"
(
INSTALL
|
SETTINGS
|
USER_FACING_FORM
|
UNINSTALL
) marking the process's role in the app. Field reference in
fcode-cli
.
An
INSTALL
or
SETTINGS
form is re-opened after the app is already configured, so it should show the current values rather than an empty form. Add a
preRenderProcess
that reads them from team variables and the datastore — and never echo a stored secret back, only whether one is set. Pattern and code in
references/advanced.md
.
Read submitted values in process code like any parameters:
const { context: { parameters } } = fcode;
  1. 创建流程并定义其输入参数(这些将成为表单字段)。
  2. 启用
    Forms
    标志——可以在流程仪表板上操作,或在CLI工作区的
    processes/<slug>/metadata.json
    中设置,然后执行
    fcode push
json
{
  "name": "联系请求",
  "tags": [],
  "form": { "enabled": true }
}
对于市场应用流程,
form
还可以接受可选的
"appRole"
INSTALL
|
SETTINGS
|
USER_FACING_FORM
|
UNINSTALL
),用于标记流程在应用中的角色。字段参考请见
fcode-cli
INSTALL
SETTINGS
表单会在应用已配置后重新打开,因此它应显示当前值而非空表单。添加一个
preRenderProcess
,从团队变量和数据存储中读取这些值——切勿回显存储的机密信息,仅需显示是否已设置。模式和代码请见
references/advanced.md
在流程代码中像读取其他参数一样读取提交的值:
const { context: { parameters } } = fcode;

Restrict who can open the form

限制可打开表单的用户

The
Authentication
field next to the
Forms
flag (
form.authMode
in
metadata.json
) decides who may read the form schema and submit it:
authMode
Who gets in
FACTORIAL
Only Factorial users of the company that installed the app. Every request must carry a Factorial-issued user token in the
Fcode-Factorial-Token
header, and that token's company must own the workspace. Anything else gets a
401
NONE
Anyone who knows the form URL can open and submit it
  • New forms are created requiring a Factorial user. Forms enabled before this field existed keep behaving as public forms until you change them.
  • Forms embedded inside Factorial (the marketplace
    INSTALL
    /
    SETTINGS
    /
    USER_FACING_FORM
    /
    UNINSTALL
    screens) send the token for you — this is what
    FACTORIAL
    is for.
  • A protected form is still openable from the playground link on the process Dashboard: the playground sends the developer's own Factorial Code token as
    Fcode-Platform-Token
    and access is granted through workspace membership.
json
{
  "name": "Connect your account",
  "form": { "enabled": true, "authMode": "FACTORIAL", "appRole": "INSTALL" }
}
Public forms carry no
authMode
entry. To lift protection, set
"authMode": "NONE"
explicitly — omitting the field leaves the form protected. Field reference in
fcode-cli
.
Forms
标志旁边的
Authentication
字段(
metadata.json
中的
form.authMode
)决定了谁可以读取表单架构提交表单:
authMode
允许访问的用户
FACTORIAL
仅安装应用的公司的Factorial用户。每个请求必须在
Fcode-Factorial-Token
头中携带Factorial颁发的用户令牌,且该令牌所属公司必须拥有工作区。其他情况都会返回
401
NONE
任何知道表单URL的人都可以打开并提交表单
  • 新创建的表单默认要求Factorial用户访问。在此字段存在之前启用的表单会保持公共表单的行为,直到您更改设置。
  • 嵌入Factorial内部的表单(市场的
    INSTALL
    /
    SETTINGS
    /
    USER_FACING_FORM
    /
    UNINSTALL
    屏幕)会自动发送令牌——这就是
    FACTORIAL
    模式的用途。
  • 受保护的表单仍可通过流程仪表板上的沙箱链接打开:沙箱会将开发者自己的Factorial Code令牌作为
    Fcode-Platform-Token
    发送,并通过工作区成员身份授予访问权限。
json
{
  "name": "连接您的账户",
  "form": { "enabled": true, "authMode": "FACTORIAL", "appRole": "INSTALL" }
}
公共表单不包含
authMode
条目。要取消保护,请显式设置
"authMode": "NONE"
——省略该字段会保持表单的保护状态。字段参考请见
fcode-cli

Embed a form

嵌入表单

Two mandatory inputs, both slugs, plus the version pin you should always add:
  • fcode-team-slug
    — from
    https://code.factorial.dev/platform/<fcode-team-slug>
  • fcode-process-slug
    — the Slug field on the process Dashboard (e.g.
    send-welcome-email
    )
  • process version — pin it to the
    stable
    alias (next section)
Use the slug, not the process ID. A process ID is a UUID that differs per workspace, so an id-based embed breaks when the snippet moves between workspaces (staging → production, or a customer's deploy workspace); with slugs, only the team slug changes. Existing id-based embeds keep working — the API resolves either — and the React prop is still named
processId
, but feed it a slug.
The Slug field is editable and there is no redirect for the old value, so renaming a process's slug breaks every embed already pasted into a page (the same exposure webhooks have). Settle the slug before handing out embed code.
Load the SDK once (needed for the data-attribute and
Fcode.initForm
methods):
html
<script defer src="https://code.factorial.dev/sdk/forms.js"></script>
Method 1 — data attributes (SDK replaces the element):
html
<div
  data-fcode-form-team="<fcode-team-slug>"
  data-fcode-form-process="<fcode-process-slug>"
  data-fcode-form-process-version="stable"
></div>
Method 2 —
Fcode.initForm
(selector or DOM element):
html
<div id="my-fcode-form"></div>
<script>
  Fcode.initForm("#my-fcode-form", {
    team: "<fcode-team-slug>",
    process: "<fcode-process-slug>",
    processVersion: "stable",
  });
</script>
Method 3 —
FcodeForm
React component
(React 17/18; install
@factorialco/fcode-react-forms
):
jsx
import FcodeForm from "@factorialco/fcode-react-forms";

const MyComponent = () => (
  <FcodeForm
    team={"<fcode-team-slug>"}
    processId={"<fcode-process-slug>"}
    processVersion={"stable"}
  />
);
In SSR frameworks (e.g. Next.js), import it dynamically with
ssr: false
.
两个必填输入项,均为slug,加上您应始终添加的版本固定:
  • fcode-team-slug
    ——来自
    https://code.factorial.dev/platform/<fcode-team-slug>
  • fcode-process-slug
    ——流程仪表板上的Slug字段(例如
    send-welcome-email
  • 流程版本——固定到
    stable
    别名(下一节)
使用slug而非流程ID。流程ID是每个工作区不同的UUID,因此基于ID的嵌入在代码片段在工作区之间移动时会失效( staging → production,或客户的部署工作区);使用slug的话,只有团队slug需要更改。现有的基于ID的嵌入仍可正常工作——API会解析两者——React属性仍命名为
processId
,但请传入slug。
Slug字段可编辑,且旧值没有重定向,因此重命名流程的slug会破坏所有已粘贴到页面中的嵌入代码(与webhook的情况相同)。在分发嵌入代码前确定slug。
加载一次SDK(数据属性和
Fcode.initForm
方法需要):
html
<script defer src="https://code.factorial.dev/sdk/forms.js"></script>
方法1 — 数据属性(SDK会替换元素):
html
<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元素):
html
<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
):
jsx
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
动态导入。

Pin the form to a version

将表单固定到版本

The version pin (
data-fcode-form-process-version
/
processVersion
) takes a published process version tag (
v1.0.0
) or a version alias. Always pin to the
stable
alias
— it always exists, points at the workspace's stable version, and:
  • Pushes never change live forms. An unpinned embed runs the current version, so every
    fcode push
    hits it immediately. Pinned to
    stable
    , the form only changes when a release moves the alias (from the web UI's team settings → Versions tab; CLI equivalents in
    fcode-cli
    ) — and rolling back is re-pointing the alias, without editing the embedded page.
  • Pinning to
    stable
    rather than a hardcoded tag follows the same portability logic as using slugs instead of IDs: the embed survives releases without edits.
The version applies to both requests the form makes — loading the form definition and submitting it. The process Dashboard writes the pin for you: pick a version or alias in the selector next to the embed code and copy the generated snippet.
An unknown version or alias falls back to the current one. The form still loads and still submits — it runs the process's current version, and the platform records only a server-side warning. That keeps a page you no longer control working after a version is deleted, but a typo in the attribute is silent: check the execution's version if a submission behaves unexpectedly.
Calling the form endpoints directly (not through the SDK)? They also accept a
version_tag
query parameter, which takes precedence over the underlying
Fcode-Version-Tag
header — same as webhooks (see
fcode-cli
).
版本固定(
data-fcode-form-process-version
/
processVersion
)可接受已发布的流程版本标签(
v1.0.0
)或版本别名。始终固定到
stable
别名
——它始终存在,指向工作区的稳定版本,并且:
  • 推送不会更改实时表单。未固定版本的嵌入会运行当前版本,因此每次
    fcode push
    都会立即影响它。固定到
    stable
    后,只有当版本发布移动了别名时(从Web UI的团队设置→版本选项卡;CLI等效命令见
    fcode-cli
    ),表单才会更改——回滚只需重新指向别名,无需编辑嵌入页面。
  • 固定到
    stable
    而非硬编码标签,与使用slug而非ID的可移植性逻辑相同:嵌入无需编辑即可在版本发布后继续工作。
版本适用于表单发出的两个请求——加载表单定义和提交表单。流程仪表板会为您生成版本固定:在嵌入代码旁边的选择器中选择版本或别名,然后复制生成的代码片段。
未知版本或别名会回退到当前版本。表单仍会加载并提交——它会运行流程的当前版本,平台仅记录服务器端警告。这可以让您无法控制的页面在版本被删除后仍能工作,但属性中的拼写错误不会有提示:如果提交行为异常,请检查执行的版本。
直接调用表单端点(不通过SDK)?它们也接受
version_tag
查询参数,该参数优先于底层的
Fcode-Version-Tag
头——与webhook相同(见
fcode-cli
)。

Handle submission results

处理提交结果

Default: a loading overlay shows during execution; on success the form is replaced with a success message, on error an error message.
Callbacks (same shape across methods):
js
Fcode.initForm("#my-fcode-form", {
  team: "<fcode-team-slug>",
  process: "<fcode-process-slug>",
  onSuccess: (formId, processExecutionResult, formSubmittedData) => {},
  onError: (formId, error, formSubmittedData) => {},
});
With data attributes, point to global functions via
data-fcode-form-on-success="HANDLER_NAME"
,
data-fcode-form-on-next-step="..."
and
data-fcode-form-on-error="..."
.
Drive behavior from the process return value (no client code needed):
js
return { message: "Thanks, <b>we received your request</b>." };            // success message (HTML allowed)
return { status: 400, body: { formErrors: {                                 // inline validation errors
  fields: { email: "Invalid email." }, global: ["A global error."] } } };
return { redirect: { url: "https://example.com", timeout: 2000 } };         // redirect after submit
Authored HTML is sanitized. Markup in
message
(and in a schema's
rawHtml
blocks) renders, but
<script>
tags, inline
on*
handlers and
javascript:
URLs are stripped and never execute. A returned
jsCallback
is ignored — put behaviour in the success / next-step / error callbacks instead.
默认行为:执行期间显示加载覆盖层;成功时表单会被替换为成功消息,错误时显示错误消息。
回调(所有方法的格式相同):
js
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="..."
指向全局函数。
通过流程返回值驱动行为(无需客户端代码):
js
return { message: "感谢您的提交,<b>我们已收到您的请求</b>." };            // 成功消息(允许HTML)
return { status: 400, body: { formErrors: {                                 // 内联验证错误
  fields: { email: "无效的邮箱地址." }, global: ["全局错误."] } } };
return { redirect: { url: "https://example.com", timeout: 2000 } };         // 提交后重定向
编写的HTML会被清理
message
中的标记(以及架构的
rawHtml
块中的标记)会渲染,但
<script>
标签、内联
on*
处理程序和
javascript:
URL会被剥离且永远不会执行。返回的
jsCallback
会被忽略——请将行为放在成功/下一步/错误回调中。

Keep it fast, or go async

保持快速,或转为异步

The submission waits for the process to finish, under a request timeout (about a minute). Heavy work done inline — slow API calls, large exports, multi-record syncs — will blow the timeout and fail the submit.
Go async when the work can be slow:
  • Embed
    async: true
    — the submission returns
    201
    + an execution ID immediately instead of waiting for the result (see
    references/advanced.md
    ).
  • Hand off to another process — kick off the heavy work with
    fcode.processes.run("process-identifier", options)
    (see
    fcode-javascript
    /
    fcode-python
    ) and return a quick acknowledgement (
    message
    /
    redirect
    ) rather than awaiting it inline.
Stay synchronous only when the request is genuinely fast, or when data must flow between steps. For passing data, don't block the submit — instead:
  • preRenderProcess
    computes server-side
    variables
    before the form renders (see
    references/advanced.md
    ).
  • Multi-step forms carry state forward via
    nextProcessId
    +
    variables
    (below).
提交会等待流程完成,受请求超时限制(约一分钟)。在同步流程中执行繁重工作——缓慢的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
    向前传递状态(见下文)。

Multi-step forms

多步骤表单

Each step is its own process. Return the next process's slug to advance:
js
return { nextProcessId: "collect-shipping-address" };
The field name is still
nextProcessId
and it accepts a slug or an id — use the slug, so the same chain works in every workspace.
The SDK then renders the form for
nextProcessId
. Each later step receives all previous steps' data and results in
fcode.context.parameters
under a
steps
array. Return a
variables
node alongside
nextProcessId
to pass state forward.
每个步骤都是独立的流程。返回下一个流程的slug以推进:
js
return { nextProcessId: "collect-shipping-address" };
字段名称仍为
nextProcessId
,它接受slug或ID——请使用slug,这样同一流程链在每个工作区都能正常工作。
SDK随后会渲染
nextProcessId
对应的表单。后续每个步骤都会在
fcode.context.parameters
steps
数组中接收所有先前步骤的数据和结果。在
nextProcessId
旁边返回
variables
节点以向前传递状态。

Automatic file uploads

自动文件上传

A file field is
"type": "string"
with a
"ui": { "ui:widget": "file" }
key inside the property:
json
{
  "properties": {
    "inputFile": { "type": "string", "ui": { "ui:widget": "file" } }
  }
}
(A root-level
ui
map keyed by field name — rjsf's
uiSchema
convention — is also merged, but per-property
ui
is the documented form; the root
ui
is mainly for form-level options like
ui:submitButtonOptions
. For secret inputs prefer
"isSensitive": true
on the property — it renders a password widget automatically; see
fcode-json-schema
.)
On submit the file is uploaded to Storage before the process starts, and the parameter arrives as an
fcode.storage://…
reference (an array if multiple files allowed). Strip the prefix to download:
js
const { context: { parameters } } = fcode;
const stream = await fcode.storage.download(
  parameters.inputFile.replace("fcode.storage://", "")
);
Uploaded files count toward storage limits — delete them at the end of the process if only needed transiently.
文件字段是
"type": "string"
,并在属性内部包含
"ui": { "ui:widget": "file" }
键:
json
{
  "properties": {
    "inputFile": { "type": "string", "ui": { "ui:widget": "file" } }
  }
}
(根级别的
ui
映射按字段名称键控——rjsf的
uiSchema
约定——也会被合并,但每个属性的
ui
是文档化的形式;根级
ui
主要用于表单级选项,如
ui:submitButtonOptions
。对于机密输入,优先在属性上设置
"isSensitive": true
——它会自动渲染密码小部件;见
fcode-json-schema
。)
提交时,文件会在流程启动前上传到存储,参数会以
fcode.storage://…
引用的形式传递(如果允许多个文件,则为数组)。去掉前缀即可下载:
js
const { context: { parameters } } = fcode;
const stream = await fcode.storage.download(
  parameters.inputFile.replace("fcode.storage://", "")
);
上传的文件会占用存储配额——如果仅临时需要,请在流程结束时删除它们。

Advanced

进阶内容

For styling and the two themes (including the f0 theme to use when embedding in a React app inside Factorial), initial/hidden values, async submission, custom headers, API-host override, variables replacement, pre-rendering current values into install/settings forms, reacting to user input from your own page (the React
onChange
prop, the
fcode-forms-*
DOM events), and modal rendering, read
references/advanced.md
.
关于样式和两种主题(包括在Factorial内部的React应用中嵌入时使用的f0主题)、初始/隐藏值、异步提交、自定义头、API主机覆盖、变量替换、将当前值预渲染到安装/设置表单、从您自己的页面响应用户输入(React的
onChange
属性、
fcode-forms-*
DOM事件)以及模态渲染,请阅读
references/advanced.md