Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

flowdeck

A human-in-the-loop control tower for n8n. Workflows pause, you decide, they resume.

n8n is very good at running things without you. It is much worse at the moment where somebody has to look at what it produced and say yes. The usual answers — an email with two links, a Slack button, a shared inbox — lose the context, lose the audit trail, and leave executions parked for days with nobody able to see them.

flowdeck is the missing screen: one live queue of every paused run, with the context attached, the payload inspectable, and the values editable before you let it continue.

n8n workflow
   |
   |-- HTTP Request  --> POST /api/hooks/approval   (sends $execution.resumeUrl)
   |-- Wait node     --> execution parks, costing nothing
   |                                                    ^
   |                                                    |
   |                          POST <resumeUrl> ---------+
   |                                    |
   `-- carries on ------------------ flowdeck  <-- you clicked Approve

Why it is worth a look

It closes the loop, not just opens it. The approval carries n8n's own resume URL, so a decision restarts the exact execution that parked itself — with your edits, your note, and your name attached.

You can change your mind about the details. An approval can declare editable fields. Lower the amount, rewrite the draft, correct the cost centre; the workflow reads your version, not the original.

Nothing is a black box. Every request, decision, expiry and failed resume lands in a live feed with timestamps. The history page shows who decided what and how long the workflow waited for them.

Failure is visible instead of silent. If n8n cannot be reached when you approve, the card does not pretend it worked — it goes red, keeps your decision, and offers a retry.

Running it locally

Requires Node 20 or newer.

npm install
cp .env.example .env                     # set FLOWDECK_INBOUND_SECRET
npm run dev                              # netlify dev on http://localhost:8888
npm run seed                             # queue four demo approvals

npm run seed needs no n8n at all. It queues realistic approvals whose resume URLs point at /api/demo/resume, a stand-in for a Wait node — so approving a seeded card really does complete the round trip and write the closing event.

Deploying to Netlify

  1. Push this repo and create a Netlify site from it. netlify.toml already configures the build, the publish directory, and the functions directory.
  2. Set FLOWDECK_INBOUND_SECRET (and ideally FLOWDECK_DASHBOARD_TOKEN) under Site configuration → Environment variables.
  3. Deploy. Netlify Blobs needs no provisioning — it is there the moment the site exists.

There is no second service to host. State lives in Blobs, the UI is static, and the API is seven small functions.

Environment variables

Variable Required Notes
FLOWDECK_INBOUND_SECRET yes n8n sends it as x-flowdeck-secret. Without it the hooks reject everything.
FLOWDECK_DASHBOARD_TOKEN recommended Gates the dashboard API. Blank means anyone with the URL can approve.
N8N_BASE_URL / N8N_API_KEY no Powers the Workflows page only.
DEFAULT_TTL_MINUTES no Expiry for requests that do not set ttlMinutes. Default 240.
MAX_EVENTS no Feed retention. Default 400.

Wiring up a workflow

Three nodes, and only the middle one is mandatory.

1. Ask. An HTTP Request node, POST to /api/hooks/approval, header x-flowdeck-secret, JSON body:

{
  "title": "Publish: {{ $json.headline }}",
  "summary": "{{ $json.draft }}",
  "workflow": "{{ $workflow.name }}",
  "executionId": "{{ $execution.id }}",
  "risk": "medium",
  "ttlMinutes": 120,
  "resumeUrl": "{{ $execution.resumeUrl }}",
  "payload": { "source": "{{ $json.link }}" },
  "fields": [
    { "key": "draft", "label": "Post text", "type": "textarea", "value": "{{ $json.draft }}" }
  ]
}

$execution.resumeUrl is the load-bearing field. It must be sent from inside the same execution that is about to wait.

2. Wait. A Wait node with Resume set to On webhook call. The execution stops there at no cost until a decision arrives.

3. Branch. After it resumes, the decision is on $json.body:

Field What it holds
decision "approve" or "reject"
approved boolean, for a one-condition IF node
note free text the approver typed
decidedBy the name set on the dashboard
edits only the fields that were actually changed

Read edits before your original values and the approver's changes are what ships.

Optionally POST to /api/hooks/event with kind of run.started, run.step, run.finished or run.failed to narrate the run in the live feed.

Ready-made workflows

workflows/ holds two importable examples. Use Import from File in n8n, then fill in the two values in the flowdeck config node at the top of each.

  • content-radar — reads a feed, drafts a social post with an LLM, and holds it for approval. Edit the draft on the card and that version is what publishes.
  • expense-guard — auto-approves anything under a threshold and escalates the rest, with the amount editable so you can part-approve.

How it is built

Piece Choice Why
Frontend React 18 + TypeScript + Vite, shadcn-ui on Tailwind Components are source in this repo, not a dependency you cannot patch
Backend Netlify Functions (v2) No second service to deploy or pay for
State Netlify Blobs Zero-config, persists across deploys, free tier
Live updates Polling every 3s with backoff See below

Why polling and not SSE. A free-tier function held open for a stream burns an invocation for its whole life and still gets cut at 60 seconds, so the best case is a reconnect storm. A 3 second poll of a single blob is cheaper, survives sleep/wake, needs no reconnection logic, and pauses itself when the tab is hidden.

Why one blob and not a database. Approvals arrive at human speed. A read-modify-write against one JSON blob is honest at that rate, and store.ts says so in a comment — it is the first thing to replace if this ever sees machine-rate traffic.

Project layout

netlify/
  functions/   hooks-approval, hooks-event, feed, approvals, decide,
               workflows, trigger, health, demo-resume
  lib/         settings + auth guards, blob store, validation
shared/
  types.ts     the contract between n8n, the functions, and the UI
src/
  components/  approval card, event feed, stat cards, payload viewer
  hooks/       use-feed (the polling engine), use-now
  pages/       dashboard, history, workflows, setup
workflows/     importable n8n examples
scripts/       generate-icons.mjs, seed-demo.mjs

Installing it as an app

flowdeck is a PWA — install it from the address bar, or Share → Add to Home Screen on iOS. Worth doing: an approval queue is exactly the thing you want one tap away, and the shell is cached so it opens instantly.

Known limits

  • Expiry is evaluated when someone reads the queue, not on a timer. There is no cron on the free tier, and an approval that is stale for a few seconds harms nobody.
  • Two people deciding the same card in the same second will both post a resume. n8n answers the second one with a 404, which the UI reports honestly.
  • There is no per-user login. The dashboard token is a shared secret, and decidedBy is a name you type — good enough for a small team, not an audit-grade identity.

About

A human-in-the-loop control tower for n8n. Workflows pause on a Wait node, you decide in a live queue, they resume with your edits.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages