Skip to content

Getting started

This walks through one workflow from a file on disk to a running session: an expense claim that an employee submits and a manager reviews.

1. Install

bash
bun add @clipless/react

The package holds the components, the hooks and the clipless CLI.

2. Write the workflow

A workflow is one file. Stages are the people involved (each has an actor role), steps are pages within a stage, fields are the answers.

tsx
// expense-claim.tsx
import { Field, Stage, Step, Workflow, type WorkflowProps, useWorkflowNav } from '@clipless/react'

function Nav() {
  const { next, canGoNext, isSubmitting } = useWorkflowNav()
  return (
    <button type="button" disabled={!canGoNext || isSubmitting} onClick={next}>
      Continue
    </button>
  )
}

export default function ExpenseClaim(props: Partial<WorkflowProps>) {
  return (
    <Workflow id="expense_claim_v1" version="1.0.0" {...props}>
      <Stage name="claim" actor="role:employee" label="Your claim">
        <Step name="details">
          <Field name="description" type="string" label="What was it for?" required />
          <Field name="amount" type="number" label="Amount (USD)" required min={1} />
          <Field name="receipt" type="file" label="Receipt" accept=".pdf,image/*" required />
        </Step>
      </Stage>

      <Stage name="review" actor="role:manager" label="Manager review">
        <Step name="decision">
          <Field
            name="decision"
            type="select"
            label="Decision"
            required
            options={[{ value: 'Approve' }, { value: 'Reject' }]}
          />
        </Step>
      </Stage>

      <Nav />
    </Workflow>
  )
}

A <Field> without children renders a default input for its type. Give it children to use your own components — see Components.

next() moves to the next step, and on the last step submits the stage.

3. Run it in the browser

Without a token, <Workflow> runs locally: the whole engine runs in the browser, nothing is saved. It is how you build and try a workflow.

tsx
<ExpenseClaim actor={{ role: 'role:employee' }} />

Only the stage the current actor owns renders. To play the reviewer after submitting the claim, switch the actor on the store — onStoreReady hands it to you:

tsx
<ExpenseClaim
  actor={{ role: 'role:employee' }}
  onStoreReady={(store) => (window.store = store)}
/>
// later: store.setActor({ role: 'role:manager' })

The playground does this with an actor switcher, and shows the ledger and state beside the form. Run it locally with bun dev:playground.

4. Deploy it

Deploying reads the workflow out of your file and sends it to the engine.

bash
export CLIPLESS_URL=https://…          # the engine
export CLIPLESS_API_KEY=fd_test_…      # a secret key for your test environment
bunx clipless deploy expense-claim.tsx

Each deploy is kept: sessions already running stay on the version they started with.

5. Start a session from your backend

A session is one run of the workflow. Your backend starts it with the API key and gets a token for the first actor:

bash
curl -X POST "$CLIPLESS_URL/api/v1/workflows/expense_claim_v1/sessions" \
  -H "authorization: Bearer $CLIPLESS_API_KEY" \
  -H 'content-type: application/json' \
  -d '{ "role": "role:employee", "externalUserId": "user_42" }'
json
{ "session": "Xk2…", "token": "eyJ…", "created": true, "state": {  } }

externalUserId is your own id for the person; the engine only stores it.

6. Render with the token

Pass the token to the same component. <Workflow> now talks to the engine: the server validates, runs actions and routes; the browser renders.

tsx
<ExpenseClaim runtimeToken={token} serverUrl={CLIPLESS_URL} />

The token is scoped to one session and one role. Never send the API key to a browser.

7. Hand off to the next person

When the employee submits, the session moves to the manager's stage. Mint the manager a token and give it to them however your app does that — an email link, their dashboard:

bash
curl -X POST "$CLIPLESS_URL/api/v1/sessions/Xk2…/tokens" \
  -H "authorization: Bearer $CLIPLESS_API_KEY" \
  -H 'content-type: application/json' \
  -d '{ "role": "role:manager", "externalUserId": "mgr_7" }'

A browser that has the session open hears it move (another actor, a provider callback) and updates itself.

8. See what happened

bash
bunx clipless sessions expense_claim_v1          # what is in flight
bunx clipless session expense_claim_v1 Xk2…      # one session, event by event

9. Ship one bundle per role

The workflow file describes everyone's stages and every action. No browser needs all of it:

bash
bunx clipless build expense-claim.tsx --out dist/clipless

writes expense_claim_v1.employee.js and expense_claim_v1.manager.js — each with only that role's stages and none of the action settings. See CLI.

Next

  • Concepts — what a stage, an actor and the ledger are, precisely.
  • Actions — email the manager when a claim arrives, run a check, generate a PDF.
  • Loop-backs and rounds — let the manager send the claim back.