@formio/appserver-core

中文文档
2.26.4发布于 2026/11/07OSL-3.0TypeScriptCommonJS近 30 天下载 5511

The Form.io Application Server is a performant run-time application server used to serve single project templates (including forms, resources, roles, etc) at a single deployed endpoint. It's goal is to enable rapid single tenant deployments where you wish

原始使用文档
文档目录5 节
  1. Form.io Application Server - Core
  2. Licensing
  3. Modules
  4. The module API
  5. Form routes

Form.io Application Server - Core

The Form.io Application Server is a performant run-time application server used to serve single project templates (including forms, resources, roles, etc) at a single deployed endpoint. It's goal is to enable rapid single tenant deployments where you wish to manage all Forms and Resources within a single location, but store the tenant data within their own database.

This library is the Enterprise Core for the Form.io Application Server. It is intended to be used alongside the @formio/appserver library.

For more information on how this works, please see https://github.com/formio/appserver

Licensing

An Enterprise project needs a license with the appserver option; the Universal Agent Gateway (UAG) features need the uag option, which includes appserver. The license is configured with the same environment variables as the Form.io Enterprise and PDF servers, so one license can run all of them.

A project on Form.io's hosted service (an https: endpoint on form.io or a subdomain of it, such as https://myproject.form.io or https://api.form.io/project/<id>) needs no license: it runs under Form.io's, as the Enterprise edition, and Agentic Formflow is allowed (reason hosted). The exemption does not apply when the appserver has a database of its own (MONGO), when TLS certificate verification is off (NODE_TLS_REJECT_UNAUTHORIZED=0), or while NODE_EXTRA_CA_CERTS, NODE_USE_SYSTEM_CA, --use-system-ca or --use-openssl-ca is set. A self-hosted Enterprise deployment always needs a license.

VariableConfigDescription
LICENSE_KEYA Form.io license key. A key issued for the Enterprise Server is validated with the Form.io license server; an offline license is verified locally.
(the server's own, such as UAG_LICENSE or APPSERVER_LICENSE)license, licenseNameThe server's own license key, validated the same way. licenseName is the variable it came from, which messages name it by.
LICENSE_REMOTElicenseRemoteSet to true when the keys are offline licenses, so the license server is never contacted, for either key.
LICENSE_SERVERlicenseServerThe license server that validates online keys: one of Form.io's (https://*.form.io). Defaults to https://license.form.io.
licenseTypeThe usage type the license server counts this server under, such as uagServer. Defaults to appServer. See below.
licenseFeatureThe feature the server needs to run, such as uag. Defaults to appserver. See below.

A feature is on when either key grants it for the project's endpoint, so a Form.io license with the uag option in LICENSE_KEY is enough on its own, and the server's own key can only add to it. LICENSE_KEY is only verified when the server's own key is missing, unusable, or lacks uag for the endpoint, so a deployment whose own key is enough never sends LICENSE_KEY to the license server. Each key is verified on its own: one that fails leaves the other's result intact. When neither licenses a feature, the reason names each key by its variable.

An online key is reported to the license server under the appServer usage type, one environment per project endpoint, so its scope must include appServer. A server that sets licenseType is reported under that type instead. A server that sets licenseFeature needs that feature rather than appserver to load a project. The UAG sets licenseType: 'uagServer' and licenseFeature: 'uag', so it runs on an online key the license server accepts as a uagServer (counted against the license's uagServers), or an offline license with the uag option, which serves the one project it is configured for; the appserver option alone does not run it. Its terms are checked again every 15 minutes. If the license server cannot be reached, the server keeps running on the terms it last confirmed for 12 hours from that confirmation; after that the licensed features turn off until it can be reached again.

Online keys are not validated while NODE_TLS_REJECT_UNAUTHORIZED=0, NODE_EXTRA_CA_CERTS, NODE_USE_SYSTEM_CA=1, --use-openssl-ca or --use-system-ca is set; use an offline license (LICENSE_REMOTE) there.

Once the 12 hours have run out, or after a restart, an Enterprise project will not load until the license server confirms the license.

Modules

server.use(module) adds a module: its config, actions, hooks, providers, processors, plugin types and plugins, and its module keys below. It also takes a list of modules, used in turn, in order: server.use([first, second]).

server.use({
    name: 'word-count', title: 'Word count', version: '1.0.0', apiVersion: 1,
    settings: (config, env) => ({ max: Number(config.wordCount?.max ?? env.WORD_COUNT_MAX ?? 1000) }),
    routes: {
        form: {
            'POST /words/count': {
                handler: (req, res, api) => {
                    const words = String(req.body?.text || '').split(/\s+/).filter(Boolean).length;
                    if (words > api.settings.max) throw api.errors.create(413, `At most ${api.settings.max} words.`, 'too_many_words');
                    return { words };
                }
            }
        }
    },
    components: [{ type: 'wordcount', script: new URL('./dist/wordcount.js', import.meta.url) }],
    initialize: (api) => api.log.info(`at most ${api.settings.max} words`),
    plugins: [/* ... */]
});
  • Identity. name (lowercase letters and digits, words joined by -), title, version and apiVersion, the module API version it is written against. A module with settings, routes, components, initialize, close or a function pluginTypes needs a name and an apiVersion; an API version the server does not provide stops startup. host names the server a module needs ('uag'): any other server refuses it at startup. A module whose name is already used is not used again; another version of it stops startup. A module named in config.modules.disabled is not used. use() takes an ES module namespace (require() of an ES module) by its default export.
  • settings(config, env) is called once, before the projects load, with the server's configuration and the environment. It returns a plain object (never a promise), which is api.settings, frozen; an error it throws stops startup, naming the module. Modules with module keys are used before router() or initialize(); one used later stops startup.
  • routes.form maps "<METHOD> /<path>" to { name?, access?, rateLimit?, handler(req, res, api) }, mounted on every form as formRoutes are (below). name (default: the module's name) is the operation a host rate limits by, and rateLimit: { limit, windowMs } a default the operator's setting overrides. Before the handler runs, the caller needs one of access on the form's submissions: by default ['create', 'update']; access: false leaves the check to the handler. req.form is the form (id, name, path, title, and definition, its JSON), req.auth the caller and req.signal aborts when the client goes away. The handler answers, or returns the JSON to answer with, or throws api.errors.create(status, message, code); any other error is answered 500, with no detail, and an error after the handler began its answer ends it with a failed event. Two modules may not define the same route (compared as Express matches it: case-insensitive, without a trailing slash), and a route may not use the form's own paths (/, /submission, /exists, /action, /actions, and those a host adds).
  • components: formio.js components, { type, script, style? } as file paths or file URLs. script is a classic browser script that registers the component (built, not an ES module): the server parses it at startup, and refuses one that does not parse, since browsers get every component in one script. It checks the files exist and that no two modules define a type, and keeps them in server.modules.components(); the host serves them.
  • pluginTypes may be a function of the module's API, (api) => [...], for plugin types that need it.
  • initialize(api) runs once, after the plugins have started, before any request; an error stops startup. close(api) runs when the server is destroyed (await server.destroy()), in the reverse order the modules were used.
  • Listing. A module with an apiVersion is listed in GET {project}/plugins as module:<name>, with its routes and component types, never its settings.

The module API

A module gets one api, version 1, in its routes, initialize, close and a function pluginTypes:

MemberWhat it does
apiVersion, name, settingsThe module's API version, name and resolved settings.
hostThe server running it: { name, version } (appserver, or the host's own name).
log.info, log.warn, log.errorLog lines prefixed with the module's name.
plugins.get(type, name), plugins.list(type), plugins.settings(type, name)The enabled plugins, and a plugin's operator settings.
requireAccess(auth, form, methods)Throws 403 unless the caller has one of methods on the form's submissions.
files.read(form, value, auth, { maxBytes, component, signal })A stored file's bytes, read with the caller's credentials and the component's storage check.
forms.componentAt(form, path), forms.componentByKey(form, key)A component of the stored form, by data path (row indices ignored) or key.
errors.create(status, message, code), errors.is(err)An error answered with that status, message and code.
stream(req, res, run, { heartbeatMs })Answers { result } with what run({ signal, progress }) returns, or, to a client that accepts text/event-stream, progress events and then done or failed.

A host server extends the module: a subclass overrides these.

OverrideWhat it sets
hostName, hostVersionThe server's name, which modules require with host, and its version (api.host).
moduleApiVersionsThe API versions the server provides ([1]).
createModuleApi(module)The API, one per module: { ...super.createModuleApi(module), ...more }. A host adds members, never removes them.
moduleClaims(definition)What else a module claims that no other may (a host's MCP tools, say), as labels; server.modules.claim(label, owner) records the server's own.
reservedRouteNamesRoute names (rate-limit operations) modules may not take.
moduleRouteHandlers(project, route, module){ before, wrap }: middleware before each module route (a caller check, a body limit, a request id), and a wrapper around its run (rate limits by route.name, the error shape).
moduleErrorBody(req, err)The body a failed route answers with, and a failed event carries ({ status, error, code }).
reservedFormPathsThe first path segments module routes may not use.

A plugin may name the module that defines its type with module (module: '@scope/package/modules/name'): when that type is missing, startup says to use that module.

Form routes

routes.form is the usual way for a module to add routes to every form. Underneath it, a module can add any Express router to every form with formRoutes: an Express router (or (req, res, next) middleware), or a list of them, mounted on each form's routes, with no access check, conflict check or host middleware.

const { Router } = require('express');

const routes = Router();
routes.post('/echo', (req, res) => res.json({ form: req.form.form.name, user: req.auth?.user?._id }));

server.use({ formRoutes: routes });
  • The routers are mounted on each form's routes, by its path (/{formPath}/echo) and by its id (/form/{formId}/echo), for every caller including administrators, after the form's own providers and before its submission routes. req.form is the form and req.auth the caller, already authenticated. The route checks the caller's access to what it does.
  • A portal team member below team admin reaches them by path with what creating a submission needs (team_write and up), as agent operations do; by id, only as an administrator.
  • A request a router does not answer (it calls next(), for a form it does not serve, say) continues to the next router, then to the form's own routes, as if the module were not there. Routers read the form from req.form on each request, so they always see the form as it is now.
  • Each module's routers are appended in use() order, after the rest of the module is applied: a module that fails adds none. They are fixed once router() has built the server's routes: adding more after refuses.
  • An error a route passes to next(err), or throws, goes to the server's error handling, as any other route's.
  • use() refuses formRoutes that are not routers or middleware (a function of the form, (form) => router, is not). Messages are written with debug, naming each module of a list as source[i]: run with DEBUG=formio:* to see them.

版本与发布记录

@formio/appserver-core 共发布 132 个版本,首次发布于 2023/04/27;过去 12 个月发布了 49 个版本。

版本发布日期距今
2.26.4latest2026/11/078 天前
2.26.32026/11/069 天前
2.26.22026/11/069 天前
2.26.12026/11/0610 天前
2.26.02026/11/0510 天前
2.25.22026/10/3116 天前

其他发布通道:dev → 2.0.0-dev.38,rc → 2.13.1。

关于 @formio/appserver-core 的常见问题

@formio/appserver-core 是什么?

The Form.io Application Server is a performant run-time application server used to serve single project templates (including forms, resources, roles, etc) at a single deployed endpoint. It's goal is to enable rapid single tenant deployments where you wish

如何安装 @formio/appserver-core?

在项目目录运行 npm install @formio/appserver-core,或使用 pnpm add / yarn add / bun add。

@formio/appserver-core 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@formio/appserver-core 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@formio/appserver-core 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@formio/appserver-core 可以商用吗?

@formio/appserver-core 使用 OSL-3.0 许可。声明的许可为 OSL-3.0,请阅读许可原文确认使用条件。以上为概括,不构成法律意见,请以许可原文为准。

@formio/appserver-core 的下载量有多少?

根据 npm 官方下载统计,@formio/appserver-core 在 2026/10/16 – 2026/11/14 这 30 天内被下载 5,511 次,日均约 184 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@formio/appserver-core 还在维护吗?

最新版本 2.26.4 发布于 2026/11/07(8 天前);至今共发布 132 个版本,其中过去 12 个月发布了 49 个;首次发布于 2023/04/27。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。