Skip to main content

DevTool Setup

How to get the DevTool running against your own flows.

Prerequisites​

You need:

  • the @flow-state-dev/fsdev package installed (it provides the fsdev command)
  • @flow-state-dev/devtool installed (provides the pre-built UI assets)
  • At least one flow definition in a conventional location
pnpm add -D @flow-state-dev/fsdev @flow-state-dev/devtool

Project structure​

The CLI discovers flows from standard directories. A minimal project looks like this:

my-project/
├── src/
│ └── flows/
│ └── my-flow/
│ └── flow.ts ← exports a FlowInstance as default
├── package.json
└── .env.local ← optional, loaded automatically

The flow file should default-export a FlowInstance created by defineFlow:

import { defineFlow, handler } from "@flow-state-dev/core";

const echo = handler({
name: "echo",
execute: async (input) => input,
});

export default defineFlow({
kind: "my-flow",
actions: {
echo: { block: echo },
},
})();

Starting the server​

Run from your project root:

fsdev dev

Output looks like:

  DevTool server running at http://localhost:4200

Flows: my-flow
API: http://localhost:4200/api/flows
Data: .fsdev/data/

The DevTool opens in your browser. Work down the navigator: open your flow, create or open a session under it, then dispatch an action and read the request that comes back. A flow registered as a collection opens into its copies first, and the ID is what tells them apart — see Flow overview.

Custom flow directories​

If your flows live somewhere non-standard, use --flow-dir:

fsdev dev --flow-dir ./lib/workflows

This flag is repeatable. When specified, the default src/flows/ and flows/ discovery is skipped.

Model overrides​

During development you might want a faster or cheaper model. --model overrides the model for every generator that runs in the server's own process:

fsdev dev --model openai/gpt-5.4-mini

Background work handed to a queue runs in another process, under that process's own model configuration. See Model overrides.

Connecting to a secured flow​

Some flows authenticate every request with a bearer secret. Their principal resolver rejects anything that arrives without a valid token. DevTool has no token to send by default, so those flows look inert: you dispatch an action and nothing happens.

Declare a devtool block in your fsdev.config.ts:

export default createFlowState({
// ...flows, stores...
devtool: {
userId: "owner",
bearerToken: process.env.MY_FLOW_SECRET,
},
});

fsdev dev reads it and wires DevTool from it. userId becomes the session identity DevTool creates sessions and dispatches as. bearerToken is sent as Authorization: Bearer on every flow request. Use the identity the flow's resolver expects: a bearer flow that resolves to a fixed owner needs userId: "owner", so the session and the action agree on who is acting.

The wiring is local-only. The token is injected into the loopback page fsdev dev serves and nowhere else. fsdev serve and deploy paths ignore the devtool block.

For a one-off you can skip the config and set a bearer token ad hoc in the Settings sheet (the gear icon). A userId from the config wins over a saved Settings value on load.

Environment variables​

The CLI loads .env.local files automatically, walking up from your working directory. Put API keys and configuration there:

# .env.local
OPENAI_API_KEY=sk-...

Data persistence​

By default, session data persists to .fsdev/data/ relative to your working directory. Delete this directory to start fresh. The directory is created automatically on first use.

This is the no-config default. When your project ships an fsdev.config.ts, stores come from the app's own wiring instead, whatever profile that config declares. See App Configuration.

Monorepo support​

In monorepos, fsdev dev scans one level under packages/, examples/, apps/, and labs/ for flow directories. Run it from the monorepo root to discover flows across all packages.