Publishing a Workflow as an App

Applies to: ChengOS v0.1.0+ | Last verified: 2026-08-12 | Source: chengflow-ui/src/features/apps, crates/cheng-nodes/src/nodes/builtin/ui, crates/cheng-api/src/rest/handlers/ui_routes

A workflow that only runs in the editor is a tool for you. Publishing turns it into something other people use — a page with a URL, a chat interface, or an endpoint another system calls.

Three ways to publish

Pick by who the audience is:

RouteWhat the user getsBest for
Page nodesA real web page with layout, navigation, and access controlForms, dashboards, internal tools
Chat gatewayA chat interface — widget, H5/PWA, or a messaging channelConversational assistants
API / MCPAn endpoint another system callsProgrammatic integration

Make the workflow publishable

Two things first, in this order.

Set the state to active. A new workflow is draft. Draft is for building; active declares it ready to be executed and referenced by other things. Publishing a draft is the most common reason a published app appears to do nothing.

Decide the entry point. Which node receives input from the outside determines what shape of publication makes sense — io/input_text for a form, chat/input for a conversation, GatewayReceiverNode for external app messages.

Building a page

Nine ui/* nodes compose a page. They are a static page skeleton generator, not a runtime page engine: they collect configuration and output a serializable PageLayout JSON. All runtime work — loading page config, applying operational overrides, running data loaders — is the frontend's job.

That split is why pages are fast: validation happens entirely at build time, at zero runtime cost.

NodeRole
ui/page_rootThe page container: layout template, theme, and the assembly point
ui/header · ui/footerTop and bottom regions
ui/top_navNavigation bar
ui/left_sidebar · ui/right_sidebarSide regions
ui/contentThe main content area
ui/routeMaps a URL path to a page
ui/rbac_guardAccess control on a route

Connect your region nodes into ui/page_root, then a ui/route to give it a path. Published routes resolve through GET /api/v1/routes/by-path.

ui/page_root validates in three layers, all at build time: field formats (hex colours, URLs, enum values), region dependencies (a layout template that conflicts with the regions you actually supplied), and deep semantic checks (duplicate menu keys, colliding modal ids). A page that would render broken fails to build instead.

Access control

ui/rbac_guard outputs a GuardResult that connects to a ui/route node's guards port. Rules can check a role, check a permission, match a user attribute, or evaluate a dynamic expression, and a guard declares its own fail action — so "not permitted" can redirect rather than dead-end.

Guards are evaluated server-side. A guard is an access control decision, not a UI hint: hiding a control in the browser is not protection, and the guard is what actually stops the request.

Publishing as a chat interface

Rather than building pages, bind the workflow to a channel and let the gateway render the chat UI. A channel carries a channelId (which app the message came from) and a boundWorkflowId (which workflow runs) — bound at channel-configuration time, never inferred. See the gateway model and SDK quickstart.

This is the route for WhatsApp, Telegram, Slack, DingTalk, and WeCom, all of which have first-party adapters, as well as for an embedded web widget.

Publishing as an API

Any active workflow is already executable over REST:

curl -X POST http://127.0.0.1:19225/api/v1/executions \
  -H 'Authorization: Bearer <token>' \
  -H 'X-Workspace-Id: <workspace-uuid>' \
  -H 'Content-Type: application/json' \
  -d '{"workflow_id": "<uuid>", "inputs": {"user_input": "hello"}}'

Subscribe to /ws/executions for live results, or poll GET /api/v1/executions/:id. With MCP_ENABLED=true workflows are also exposed as MCP tools, so an external agent can call your workflow the way your agent calls anything else.

Templates

Marking a workflow as a template makes it a starting point for others (PUT /api/v1/workflows/:id/template). GET /api/v1/workflows/templates is public and unauthenticated, which is what lets a template gallery load before sign-in.

Next steps