# 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:

| Route | What the user gets | Best for |
|---|---|---|
| **Page nodes** | A real web page with layout, navigation, and access control | Forms, dashboards, internal tools |
| **Chat gateway** | A chat interface — widget, H5/PWA, or a messaging channel | Conversational assistants |
| **API / MCP** | An endpoint another system calls | Programmatic 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.

| Node | Role |
|---|---|
| `ui/page_root` | The page container: layout template, theme, and the assembly point |
| `ui/header` · `ui/footer` | Top and bottom regions |
| `ui/top_nav` | Navigation bar |
| `ui/left_sidebar` · `ui/right_sidebar` | Side regions |
| `ui/content` | The main content area |
| `ui/route` | Maps a URL path to a page |
| `ui/rbac_guard` | Access 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](/docs/sdk-gateway-model) and [SDK quickstart](/docs/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:

```bash
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

- [Gateway model](/docs/sdk-gateway-model) — apps, channels, and sessions.
- [SDK quickstart](/docs/sdk-quickstart) — embedding a chat UI.
- [REST API overview](/docs/api-rest-overview) — the endpoints behind all of this.
