The complete guide to FlowForge
FlowForge builds automations that run on your own computer — no cloud account, no vendor database, no data leaving the machine. This manual covers every trigger, every action, and every safeguard, from your first workflow to the execution model underneath.
Opens your browser's print dialog — choose Save as PDF as the destination. The PDF is generated from this page, so it is always the current version.
Getting started
What FlowForge is
FlowForge is a workflow automation studio that runs entirely on your own computer. You describe things that should happen automatically — “when a form is submitted, save the lead and create a follow-up task”, “every weekday at 8:30, generate my checklist”, “when an email arrives from billing, classify it and alert me if it’s urgent” — and FlowForge does them, in the background, whether or not the window is open.
| FlowForge | Typical cloud automation tool | |
|---|---|---|
| Where workflows run | Your machine | Vendor’s servers |
| Where your data sits | A SQLite file in your user folder | Vendor’s database |
| Where credentials sit | Your OS keychain, encrypted | Vendor’s vault |
| Account required | No | Yes |
| Works offline | Yes, except steps that call the internet | No |
| AI steps | Your own Claude key, or a fully local model | Vendor’s AI, vendor’s terms |
There is exactly one thing FlowForge sends to a server that isn’t yours: a subscription check — “is this licence key valid?”. No workflow content, no run data, no credentials, no telemetry.
What it is not
FlowForge is a single-user desktop application. There is no team workspace, no shared cloud state, no multi-user permissions, and no hosted webhook endpoint on the public internet. Its webhook server listens on 127.0.0.1 — reachable from your own machine, not from the outside world.
The core idea in one sentence. A workflow is one trigger (“when this happens…”) followed by action steps (“…do these things, in order”), and each step can read the output of every step before it.
Install, first launch, and activation
System requirements
- macOS — Apple silicon or Intel, current macOS release. The primary platform.
- Windows — 64-bit Windows 10 or later, via the NSIS installer.
No runtime to install, no database to configure, and no compiler required — FlowForge uses SQLite through Node’s built-in driver, so installation never builds native code.
Installing
macOS. Open the .dmg and drag FlowForge to Applications. The app is signed and notarized by Apple, so it opens without a Gatekeeper warning.
Windows. Run the setup executable. It lets you choose the install directory and creates Desktop and Start Menu shortcuts. It installs for the current user, so no administrator password is needed.
First launch
- FlowForge creates its database and seeds it so nothing is empty — five tables (Leads, Clients, Tickets, Content Calendar, Agent Tasks), sample records, three demo workflows, and some run history.
- A six-slide welcome tour appears. Skip it if you like — replay it any time from the ? button in the sidebar footer, or Help & Manual → Replay welcome tour.
- The background trigger runtime starts and an icon appears in the menu bar (macOS) or system tray (Windows).
Activating your subscription
- Subscribe on the pricing page — the Subscribe button in the app opens it too.
- After payment you’ll see a key on the welcome page. The key is your subscription ID and starts with
sub_— for examplesub_01abcdefghijklmnopqrstuvwx. - Paste it into the activation screen and press Activate.
Once activated, FlowForge re-verifies your subscription online roughly every 72 hours. If the check can’t reach the server, it keeps working for a 7-day grace window and shows a small “Reconnecting to verify your subscription…” pill — your automations don’t stop because your Wi-Fi did. Past the grace window, runs are refused until the check succeeds.
Where activation is enforced: at the single point every run passes through — background triggers, manual runs, retries, crash recovery, and AI-assistant calls alike. There is no code path that runs a workflow around it.
The interface
FlowForge has a fixed left sidebar and one main panel.
| Page | What it’s for |
|---|---|
| Dashboard | Live status: automations running, total runs, success rate, open tasks, recent runs, quick actions. |
| Workflows | Every automation. Search, filter by On/Off/Draft, create, import, duplicate, export, delete, toggle live. |
| Run History | Every execution, with status, duration, and per-step input, output, logs, and errors. Retry failures here. |
| Templates | 14 ready-made workflows. One click creates a fully wired copy you can edit. |
| Tables | Your local data store. Workflows read and write it. |
| Tasks | To-do items created by the Create Task action. |
| Connections | API keys and OAuth logins, encrypted into your OS keychain. |
| Help & Manual | The in-app quick reference, with a live step index and your machine’s MCP URL. |
The sidebar footer
- ☾ / ☀
- Switch between light and dark theme.
- 🔔 Activity
- Notifications your workflows created. A badge shows unread count.
- 📁 Exports
- Open your FlowForge Exports folder in Finder or Explorer.
- ? Tour
- Replay the welcome tour.
Build your first workflow
Three minutes, nothing external required.
- Create it. Workflows → New workflow. The editor opens with an empty canvas and the title selected. Type a name — it saves as you type.
- Choose a trigger. Pick Manual Run. Manual runs only when you press Run, so nothing happens by surprise while you build.
- Add an action. Add an action → Create Task. Its configuration panel opens on the right.
- Configure it. Set Task title to
My first automated taskand Due in (days) to1. Changes save automatically about a third of a second after you stop typing. - Test just this step. Press Test this step. FlowForge runs only this step, using sample data for anything upstream, and shows you its real output.
- Add a second action. Click the + below the first step and add Send Notification. Title:
Task created. Message:Created at {{now}}. - Run the whole thing. Press Run. A result window shows every step, its status, its duration, and — when you click a step — its exact input and output.
- See the evidence. Check Tasks, click 🔔 Activity, and open Run History.
- Go live (optional). Flip the Live switch. For a Manual trigger this only adds the workflow to the menu-bar Run Workflow menu; for Schedule or Webhook triggers, this is what starts the background watcher. Turning a workflow on also saves a version snapshot automatically.
Templates and Create with AI
Two shortcuts to a working workflow.
Templates
Templates ships 14 complete, pre-wired workflows. Every card tells you two things before you commit: Output — what should exist after it runs — and Status — whether it works entirely locally or needs a connection you set up first. Use this template gives you a real, editable copy, not a locked preset.
| Template | Trigger | What it does | Needs |
|---|---|---|---|
| Lead Capture | Form submitted | Saves the lead to Leads, creates a follow-up task, notifies you | Nothing |
| Lead Follow-Up Workflow | Form submitted | Builds a follow-up angle, creates two tasks, updates the lead’s status | Nothing |
| Client Onboarding | New record | Welcome-packet and kickoff tasks, plus a simulated channel post | Nothing |
| Support Ticket Routing | Webhook | AI classifies category and priority, logs to Tickets, routes | AI connection |
| Support Inbox Triage | Email received | Classifies each email, auto-acknowledges the sender, logs it, escalates only urgent mail | IMAP + AI + SMTP |
| Content Calendar Automation | Weekly schedule | Generates the week’s content checklist and logs the plan | Nothing |
| Content Campaign Builder | Manual | Turns one product angle into tasks, a calendar row, and a markdown brief | Nothing |
| Finance Reminders | Daily schedule | Invoice and chase-payments tasks each morning | Nothing |
| Product Launch Checklist | Manual | Launch tasks plus an exported launch brief | Nothing |
| App Store Promo Workflow | Manual | Drafts store-style copy and review tasks; submits nothing | Nothing |
| Daily Marketing Heartbeat | Daily schedule | One proof point, one post, one follow-up, every weekday | Nothing |
| Screenshot-to-Campaign | File imported | Turns a dropped screenshot into a campaign checklist and brief | Nothing |
| NBS GDP Analysis Workflow | Manual | Downloads official statistics files, extracts GDP tables from the Excel workbook, exports a memo, JSON, and CSV | Internet |
| AI-Agent Task Routing | Webhook | AI triages an inbound request and assigns it to a queue | AI connection |
The marketing templates are deliberately honest: they create tasks, table rows, notifications, and local files. None of them claims to have published to a social network, an app store, or a CRM. If you want that, wire the connector step yourself.
Create with AI
On the Workflows page, click ✦ Create with AI and describe what you want in plain English:
"When someone submits my contact form, save them to the Leads table,
have AI rate how urgent the message is, and notify me if it's high priority."
FlowForge drafts the entire workflow — trigger, steps, tokens wired between them, branch nesting and all.
- Claude (cloud) — uses an Anthropic API key you store under Connections. Your description goes to Anthropic under your own account.
- Ollama (local) — uses a model on your own machine. Your description never leaves the computer. Requires Ollama with a model pulled (
ollama pull llama3.2).
The draft always arrives as a draft. It is never turned on automatically. Review each step, pick your tables and connections (the AI deliberately leaves those blank), test the steps, then flip it live. Under the hood the AI can only produce a workflow description in a constrained format, which is validated exactly like an imported JSON file — it has no direct write access to your database.
Building workflows
Anatomy of a workflow
- Exactly one trigger. It sits at the top and defines when the workflow starts.
- Any number of actions, running top to bottom.
- The trigger is step 1, the first action is step 2, and so on. This numbering is what
{{steps.2.…}}tokens refer to.
Workflow status
- Draft
- Never turned on. Nothing watches for its trigger.
- Off
- Turned on at some point, now paused. Nothing watches for its trigger.
- On (shown as Live)
- The background runtime is actively watching for its trigger.
You can always press Run manually regardless of status. The Live switch controls only the automatic firing.
The workflow editor
The canvas
Steps are drawn as a vertical flow. Each card shows its number, its icon, the step type, your name for it, and a one-line summary of its configuration (“Runs daily”, “Path /hooks/tickets”, “Save to Leads”).
- Click a step to open its configuration panel on the right.
- Click a + connector between steps to insert a new step at exactly that point.
- Drag a step card to reorder it. Dragging an If or Loop step carries its entire nested block along.
- The trash icon deletes that step, with confirmation.
The configuration panel
- Step name — free text, purely for your own readability.
- Configuration — fields generated from the step’s definition. Fields appear and disappear based on other fields.
- If this step fails — Stop the workflow (default) or Continue anyway. Not shown for If and Filter steps.
- Insert data from earlier steps — clickable token chips for everything available at this point.
- Test this step — run just this step and see its output.
Top bar controls
- Live / Off switch
- Turn background triggering on or off. Turning it on also captures a version snapshot.
- ⟲ History
- Version snapshots for this workflow — take one now, or restore an earlier one.
- Export
- View the workflow’s portable JSON, copy it, or save it to a file.
- Run
- Execute the whole workflow now and show the result.
Testing a step vs. running the workflow
| Test this step | Run | |
|---|---|---|
| Scope | One step | Whole workflow |
| Upstream data | Simulated by executing prior steps, ignoring their failures | Real, from the actual run |
| Recorded in Run History | No | Yes |
| Real side effects | Yes | Yes |
| Approval steps | Not supported | Supported |
Testing is real. “Test this step” is not a dry run. A Send Email test sends a real email; an HTTP Request test makes a real request. Only the upstream data is simulated.
Passing data between steps: tokens
This is the single most important concept in FlowForge. Every step produces an output — a small object of named fields. Later steps read those fields using {{token}} syntax inside any text field.
| Token | Reads | Example |
|---|---|---|
{{trigger.field}} | The trigger’s output | {{trigger.email}} |
{{steps.N.field}} | Step number N’s output | {{steps.2.category}} |
{{loop.…}} | The current loop iteration | {{loop.item}} |
{{now}} | The moment the run started, ISO 8601 | 2026-08-11T09:00:00.000Z |
The autocomplete picker
Type {{ in any token-aware field and a dropdown appears listing every token available at that point, drawn from the trigger’s fields and each earlier step’s output shape. Keep typing to filter; Enter inserts the top match; Escape dismisses.
Dotted paths go as deep as you need
{{steps.3.data.results}} the "results" array inside step 3's "data" object
{{steps.3.data.results.0.id}} the id of the first result
{{trigger.attachments}} a whole array
A path that doesn’t exist resolves to an empty string rather than an error.
Whole values vs. text
- A field containing exactly one token returns the raw value — an object, an array, a number. This is how Export File’s Data to export can be
{{trigger}}and write the entire payload, or how a Loop’s list source receives a real array. - A token mixed with other text returns a string, with objects rendered as JSON.
Source: {{steps.4}} → the object itself
Source: Report for {{steps.4}} → the string 'Report for {"a":1}'
Tokens work in key/value fields too
Set fields, Record fields, Output fields, and Headers are key/value editors. The values support tokens — put {{trigger.email}} in the value box and it resolves at run time.
Control flow: Filter, If, Loop
Filter — “only continue if…”
A Filter step evaluates a condition. If it passes, the workflow continues. If it fails, the run stops there and is recorded with status Filtered. This is not a failure — it’s the normal way to say “don’t bother with the rest of this”.
If / Branch — “do different things”
An If step evaluates the same kind of condition but never stops the run. Under it you get + Yes branch and + No branch buttons. Steps added there run only when that branch is taken. Ifs can nest inside other Ifs — nested steps are indented on the canvas.
Conditions, shared by Filter and If
| Operator | Passes when |
|---|---|
exists / is not empty | The field has a value that isn’t blank whitespace |
equals | String comparison is exactly equal |
does not equal | String comparison differs |
contains | Case-insensitive substring match |
greater than / less than | Numeric comparison |
The Compare value box appears for every operator except exists — which needs no value, since it only asks whether the field is non-empty.
Loop / For Each — “do it once per item”
A Loop step iterates over a list. Point its List to loop over at a token that resolves to an array, then use + Add to loop body to add the steps that repeat.
{{loop.item}}- The current item
{{loop.index}}- Zero-based position
{{loop.count}}- How many items will be processed
Max iterations is a safety cap — default 100, hard maximum 1000. The loop’s own output is { count, total }.
- A Filter that fails skips the rest of that iteration only and moves to the next item.
- A failing step stops the whole loop and fails the run at that item, unless the step is set to continue on error.
- Run History records one entry per step per iteration, labelled
Step name (item 2/5).
Not supported inside a loop yet: nested If or Loop steps, and Approval steps. The editor blocks adding them and the engine reports a clear error if one arrives via import.
When steps fail
Per-step failure policy
- Stop the workflow (default) — the run is marked Failed, and all later steps are recorded as Skipped.
- Continue anyway — the step is recorded as failed, but the run continues. Later steps can read what went wrong via
{{steps.N.failed}}and{{steps.N.error}}.
Use Continue anyway for genuinely optional steps — a nice-to-have notification shouldn’t sink an otherwise successful run.
Automatic retries
The HTTP Request step has a Max attempts field. Set it above 1 and FlowForge retries on transient problems only: network errors and connection resets, HTTP 5xx, HTTP 429, and request timeouts. A 404 or a 401 is not retried. Backoff is exponential with jitter, starting around 500 ms and capping at 30 s.
Retrying a run
From Run History, open a failed run and press Retry failed steps. Steps that already succeeded replay from the journal — their recorded output is reused and they are not executed again. An email that was already sent is not sent twice. Only the failed step and everything after it actually runs.
Versions and rollback
FlowForge snapshots a workflow’s complete definition — its settings and every step — as a version. Versions are captured:
- Automatically, every time you turn a workflow on.
- Manually, via ⟲ History → Snapshot now.
- Automatically before applying an AI fix.
- Automatically before restoring an older version, so a rollback is itself undoable.
Identical content doesn’t create a duplicate. Before restoring, FlowForge recomputes the snapshot’s SHA-256 hash and compares it to the stored one. If the stored snapshot has been modified in the database, the restore is refused rather than silently loading tampered content.
Step reference
FlowForge has 30 step types: 7 triggers and 23 actions. Every field below is a real field in the configuration panel, and every glyph and colour matches what you see on the editor canvas.
Triggers — how a workflow starts
Runs only when you press Run, choose it from the menu-bar Run Workflow menu, or call it from an AI assistant over MCP. No configuration.
Runs on a repeating schedule while the workflow is Live.
| Frequency | Every 15 minutes · Every hour · Every day · Every week |
| At time | 24-hour HH:MM. Used by daily and weekly; ignored by shorter intervals. |
| Catch up missed runs | If FlowForge was closed or the Mac asleep when a run was due, run once on next launch or wake. |
Weekly runs on Monday at the configured time. Hourly fires at the top of the hour; every-15-minutes fires at :00, :15, :30, :45. Catch-up fires once per workflow per wake, not once per missed occurrence — a laptop closed over a long weekend produces one catch-up run, not three.
Fires when a record is added to a table you choose — including records added by another workflow’s Save to Table step. Required field: Watch table. When testing, the table’s most recent record is used as sample data.
Fires when JSON is POSTed to a local endpoint.
| Webhook path | e.g. /hooks/incoming. A path not starting with /hooks/ gets that prefix added. |
| Sample payload (JSON) | Stand-in data used when you press Run or Test. Does not affect live behaviour. |
Live URL: http://127.0.0.1:47250<path> — select the trigger step and the editor shows the exact URL under Post to this URL, click to copy. See Webhooks in detail.
Fires when a file appears in a watched folder. Fields: Watch folder (e.g. ~/Downloads) and File type (CSV · JSON · PDF · Any).
Watches the folder itself, not its subfolders. Files already present when the watcher starts are ignored — only newly added files fire. Each file is de-duplicated by path and modification time, so re-saving the same file fires again but re-scanning doesn’t.
Represents a form submission. Fields: Form name (a label for your own reference) and Sample submission (JSON) (the data the workflow receives).
This trigger has no live listener in version 1.0 — there is no hosted form. Use it for manual runs and template design, or use Webhook Received to accept real submissions from a form service.
Watches an IMAP mailbox and fires once per new message.
| IMAP server | Gmail imap.gmail.com · iCloud imap.mail.me.com · Outlook outlook.office365.com |
| Port / Use TLS | 993 (TLS) or 143 (STARTTLS) |
| Email / username | Usually your full address |
| Password | A stored Connection. For Gmail/iCloud, create an app password first. |
| Mailbox / folder | Default INBOX |
| From / Subject contains | Optional filters |
| Check every | 1 · 5 · 15 minutes |
The first poll after you turn the workflow on establishes a baseline and fires nothing — otherwise turning it on would process your entire inbox. See Email in & out.
Actions — what a workflow does
Posts to FlowForge’s Activity centre. Fields: Title (required, tokens ✓) and Message (tokens ✓).
Adds a to-do to the Tasks page. Fields: Task title (required, tokens ✓), Notes (tokens ✓), Due in (days).
Finds one record in a table and updates its fields.
| Table | Required |
| Match on field | Column name, e.g. email |
| Match value | What to look for. Tokens ✓ |
| Set fields | Key/value pairs; values support tokens |
Fails if no record matches. Set If this step fails to Continue anyway if a miss is acceptable.
Map to new fields — build an output object from key/value pairs, values being tokens or literals. The workhorse for renaming, combining, or freezing values for later steps.
Format text — take a Source text string and apply one formatter: UPPERCASE, lowercase, Title Case, or Trim whitespace.
Stops the run unless a condition passes. See Control flow.
Routes down a Yes or No path. See Control flow.
Repeats its body once per list item. See Control flow.
Makes no network request. Returns a realistic, deterministic fake response. Useful for designing and demoing a workflow’s shape before wiring the real service. Fields: Method, URL (required, tokens ✓), Request body (tokens ✓).
A real HTTP call to an external API, behind the SSRF guard.
| Method | GET · POST · PUT · PATCH · DELETE |
| URL | Required. Tokens ✓ |
| Headers | Key/value; values support tokens |
| Request body | Tokens ✓ |
| Connection | A stored credential — its auth header is merged in by the main process. The secret never appears in your config, logs, or exports. |
| Timeout (ms) | Default 30000 |
| Max attempts | Retries on network errors, 5xx, and 429. Default 1. |
| Allow local network | Permit private/LAN addresses. Cloud metadata endpoints are blocked regardless. |
JSON responses are parsed automatically. Responses are capped at 10 MB; up to 5 redirects are followed, each re-checked against the guard, and credentials are stripped on a cross-origin redirect.
Downloads a file into your FlowForge Exports folder. Fields: Source URL (required, tokens ✓), File name (required, tokens ✓), Output folder inside FlowForge Exports (tokens ✓), Max bytes (default 15,000,000).
A purpose-built extractor for the Seychelles National Bureau of Statistics quarterly national accounts workbook. It reads the official .xlsx, pulls constant-price GDP, sector year-on-year growth, quarter-on-quarter movement, and point contributions, and composes a markdown memo. Fields: Workbook path (required, tokens ✓ — usually {{steps.2.path}} from a Download File step), Period year / quarter / comparison year.
Appends a new record to a table. Fields: Table (required) and Record fields (key/value; values support tokens).
Writes data to a file in FlowForge Exports.
| Format | JSON · CSV · Markdown · Text |
| File name | Tokens ✓ |
| Data to export | A single token like {{trigger}} or {{steps.4}} exports that whole object |
JSON is pretty-printed. CSV turns an array of objects into rows, with columns as the union of all keys, properly quoted and escaped. Markdown/Text writes a string as-is, anything else as formatted JSON.
Sends a real outbound webhook, behind the same SSRF guard as HTTP Request. Fields: Method (POST/PUT), Destination URL (required, tokens ✓), Payload (JSON) (tokens ✓), Allow local network.
Classify, extract, summarize, or draft — with Claude using your own API key, or a fully local Ollama model that never leaves your machine. See The AI Task step for the full treatment, including schema-typed output.
Sends through your own SMTP account.
| To / Subject / Body | To and Subject required. Tokens ✓ |
| Format | Plain text · HTML |
| SMTP server | Gmail smtp.gmail.com · iCloud smtp.mail.me.com · Outlook smtp-mail.outlook.com |
| Port | 587 (STARTTLS) or 465 (SSL) |
| Username / Password | Required. Password is a stored Connection. |
| From | Optional; defaults to your username |
Pauses the run until you approve or reject it — from a notification or Run History. The run survives app restarts while it waits. Field: What needs approval? (tokens ✓). See Approvals.
Calls a tool on any Model Context Protocol server as a workflow step.
| Server command | Required. The stdio command that starts the server. Never token-interpolated — workflow data cannot choose what program runs on your machine. |
| Tool name | Required. Run once with a wrong name to see the list of tools the server offers. |
| Tool arguments (JSON) | Tokens ✓ |
| Timeout (ms) | Default 60000 |
| Re-approve changed tool | Accept a changed tool definition |
Rug-pull guard. The first time a tool is used, its definition (name, description, input schema) is hashed and pinned. If the server later changes that definition — the classic MCP rug-pull attack, where a trusted tool quietly becomes something else — the step fails until you review it and turn on Re-approve changed tool.
Runs JavaScript in a locked-down QuickJS sandbox. Fields: JavaScript and Timeout (ms) (default 5000).
inputis{ trigger, steps }— the same data your tokens see.returna value to produce output. It appears as{{steps.N.output}}.console.log(…)writes to the step’s logs.
Nothing else exists. No require, no process, no fetch, no file system, no timers, no network. Memory is capped at 64 MB and the deadline uses a monotonic clock, so changing the system time cannot extend it. Every execution is audited.
const items = input.steps['2'].data.results ?? []
console.log(`Processing ${items.length} items`)
return {
count: items.length,
emails: items.map(r => r.email).filter(Boolean),
}
Connector actions
Connectors are small, self-contained integrations. They never touch raw secrets or the network directly — FlowForge hands them a guarded HTTP client with authentication already merged in.
Fields: Incoming webhook URL (required — create at api.slack.com → Your Apps → Incoming Webhooks) and Message (required, tokens ✓). No Connection needed — the webhook URL is the credential.
Fields: Channel webhook URL (required — Server Settings → Integrations → Webhooks → New Webhook) and Message (required, tokens ✓). No Connection needed.
Fields: Repository (required, owner/repo), Issue title (required, tokens ✓), Issue body (tokens ✓), and a bearer Connection holding a personal access token with Issues write access.
Fields: Bot token connection (required — create a bot with @BotFather, store its token as an API key connection), Chat ID (required, tokens ✓), Message (required, tokens ✓).
Telegram requires the token in the URL rather than a header, so the connector fetches it from the vault at send time — it is still never stored in your workflow config.
Data and workspace
Tables
Tables is a lightweight local data store. Think of it as a spreadsheet your workflows can read and write.
New table, give it a name, then define columns. Each column has a name and a type: text, number, boolean, or date. Add record opens a form built from your columns — booleans get a dropdown, dates get a date picker, numbers get a numeric field.
How workflows use tables
- Save to Table
- Appends a record. Also fires any New Record trigger watching that table.
- Update Record
- Finds one record by a field match and updates it.
- New Record trigger
- Starts a workflow whenever a record is added.
Records are schemaless underneath: a Save to Table step writing a field your table doesn’t have a column for still stores the value — it just won’t get its own column in the table view.
A fresh install includes Leads, Clients, Tickets, Content Calendar, and Agent Tasks. Templates refer to these by name, so they work immediately. Deleting a table deletes all of its records and cannot be undone.
Tasks and Activity
The Tasks page collects everything the Create Task action produces. Filter by Open, Done, or All; click the circle to toggle a task done. Each task shows which workflow created it and when. Overdue open tasks are flagged.
The 🔔 button in the sidebar footer opens Activity — every notification created by the Send Notification action, newest first, with a badge for unread ones.
Separately, FlowForge sends native OS notifications for things that need attention when the window isn’t focused: a run has paused for approval (with an Approve button on macOS), a menu-bar run has finished, an update is available, and — on Windows — a one-time notice explaining that automations keep running in the tray.
Connections and credentials
Connections is where every secret lives: API keys, OAuth tokens, SMTP and IMAP passwords, bot tokens.
API key — a single secret string. Give the connection a Name (what you’ll see in dropdowns) and a Service (a free-form label), then paste the secret.
OAuth2 — a full authorization-code flow with PKCE. Provide the Authorization URL, Token URL, Client ID, and optional Scopes. FlowForge opens your normal browser to authorize; the tokens come back over a local loopback callback and are stored encrypted.
What each step needs
| Step | Connection type |
|---|---|
| AI Task (Claude), Create with AI, Fix with AI | API key holding your Anthropic key |
| Send Email | API key holding your SMTP password (app password for Gmail/iCloud) |
| Email Received trigger | API key holding your IMAP password, or an OAuth2 connection |
| HTTP Request | Any — its auth header is merged into the request |
| GitHub: Create Issue | bearer holding a personal access token |
| Telegram: Send Message | API key holding the bot token |
| Slack / Discord | None — the webhook URL is the credential |
The guarantees
- Secrets never leave the main process. The app window only receives a connection’s name, service, type, and non-secret metadata. There is no way to read a secret back out, by design.
- Secrets are encrypted at rest using your operating system’s keychain (Keychain on macOS, DPAPI on Windows).
- Secrets are redacted from output — masked to
••••••in run logs, step outputs, error messages, and MCP responses, including the JSON-escaped form. - If the keychain is unavailable, FlowForge refuses to create connections rather than fall back to plain text.
The exports folder
Every file FlowForge writes goes to one place:
macOS ~/Documents/FlowForge Exports/
Windows C:\Users\<you>\Documents\FlowForge Exports\
The 📁 button in the sidebar footer opens it. Export File writes here. Download File writes here, optionally into a subfolder you name. Filenames are sanitized, and paths cannot escape the exports directory.
Exported workflow JSON is different — the Export button in the editor opens a save dialog, so those files go wherever you choose.
Running for real
Going live: the background runtime
Turning a workflow On hands its trigger to the background runtime, which binds it to a real source:
| Trigger | Bound to |
|---|---|
| Schedule | A cron job |
| Webhook | A path on the local HTTP server |
| File Imported | A folder watcher |
| New Record | The in-process record event |
| Email Received | A polling timer (1, 5, or 15 minutes) |
| Manual, Form Submitted | Nothing — these have no background source |
Every edit reconciles automatically. Change a schedule’s time and the cron job is rebound immediately; turn a workflow off and its watcher stops.
Closing the window doesn’t stop anything
FlowForge is an automation engine, so closing the window keeps it running. The menu-bar icon (macOS) or tray icon (Windows) is your proof it’s alive, and your control panel:
- Open FlowForge
- Bring the window back.
- Check for Updates…
- Shows the current version.
- Run Workflow ▸
- Run any Manual-trigger workflow directly — up to 12 listed. A notification reports the result.
- Start at Login
- Launch FlowForge automatically when you log in.
- Quit FlowForge
- Actually stop everything.
Sleep, wake, and missed runs
On launch and on wake from sleep, FlowForge checks whether any Live schedule was due while it wasn’t watching. If so — and if that trigger has Catch up missed runs enabled — it fires one catch-up run per workflow. A long weekend produces one run, not seventy-two.
One run at a time, per workflow
Runs of the same workflow are queued and executed one after another. Two webhooks arriving simultaneously produce two runs in sequence, never two overlapping executions of the same steps. Different workflows run independently.
Run History
Every execution is recorded — manual runs, background triggers, retries, and MCP calls alike. Each row shows the workflow, its trigger type, when it started, how long it took, and a status pill.
Run statuses
- Success
- Every step completed.
- Failed
- A step failed and the workflow stopped.
- Filtered
- A Filter condition wasn’t met — a normal, intended stop.
- Paused
- Waiting at an approval step.
- Running
- Currently executing.
Run detail
Click a run for the full timeline. Every step shows its status and duration; expand one to see the error (if it failed), the logs it wrote, the exact { trigger, steps } snapshot it saw as input, and exactly what it produced as output.
This is what makes debugging tractable: you can see the precise data a step received, not a reconstruction. Secrets are redacted throughout.
Approvals
The Wait for Approval step is a human-in-the-loop gate: put it anywhere in a workflow and the run stops there until you decide.
- The run reaches the approval step and pauses.
- FlowForge posts an in-app notification and a native OS notification. On macOS the notification carries an Approve button you can click without opening the app.
- In Run History, the paused run shows a banner with your approval message and Approve / Reject buttons.
- Approve resumes the run exactly where it stopped — earlier steps are not re-executed. Reject fails the step and stops the run.
A paused run survives quitting and restarting FlowForge. The pause is durable state in the database, not something held in memory.
Rejection is deliberately an in-app action only, so dismissing or clearing a notification can never accidentally reject a run.
When to use one: before anything you’d want to eyeball first — an outbound email drafted by AI, a webhook that posts publicly, an issue filed in a real repository. FlowForge also tells you when it thinks you need one.
Webhooks in detail
The Webhook Received trigger runs a small HTTP server on your machine.
POST http://127.0.0.1:47250/hooks/<your-path>
Content-Type: application/json
The server binds to 127.0.0.1 — the loopback interface — so it is reachable from your own machine only. It is not exposed to the internet. To accept webhooks from an external service, put a tunnel (ngrok, Cloudflare Tunnel, Tailscale Funnel) in front of it, and understand that you are then responsible for what reaches the endpoint.
Requirements
| Requirement | Failure response |
|---|---|
| A Live workflow bound to that path | 404 |
| Method POST | 405 for GET/DELETE |
Content-Type: application/json | 415 |
| Body under 1 MB | 413 |
| Valid JSON body | 400 |
| Within rate limit | 429 |
| Valid signature, if a secret is configured | 401 |
On success the server responds 202 with {"runId": "..."}.
Rate limiting and deduplication
Each path gets a token bucket: 10 requests of burst, refilling at 1 per second. Excess requests get 429.
Every accepted body is hashed. A byte-identical payload to the same path within the 30-day dedup window is answered 202 {"deduped": true} and does not fire a second run. This makes it safe for a sender to retry after a network hiccup. The dedup key is recorded only after the run is durably created, so a crash mid-enqueue lets a retry succeed rather than silently swallowing the event.
Signature verification
If the trigger’s configuration carries a secret, FlowForge requires an HMAC-SHA256 signature of the raw request body, hex-encoded, in the header x-flowforge-signature. Comparison is constant-time. A missing or wrong signature is rejected with 401.
The step’s configuration form does not currently expose a Secret field. To enable signature verification today, add "secret": "…" to the webhook trigger’s config by editing the workflow’s exported JSON and re-importing it.
Testing a webhook workflow
curl -X POST http://127.0.0.1:47250/hooks/tickets \
-H 'Content-Type: application/json' \
-d '{"subject":"Test","message":"Hello","customer":"Me"}'
Vary the payload between tests — an identical body will be deduplicated.
Email: receiving and sending
Receiving (IMAP)
Setup, using Gmail as the example:
- Turn on 2-Step Verification on your Google account.
- Create an App Password (Google Account → Security → App passwords). Gmail will not accept your normal password over IMAP.
- In FlowForge, Connections → New connection, type API key, name it “Gmail IMAP”, and paste the app password.
- Add an Email Received trigger: server
imap.gmail.com, port993, TLS on, your full address as username, the connection you just created as password, mailboxINBOX. - Optionally narrow it with From contains or Subject contains, then turn the workflow On.
| Provider | IMAP server | Port | SMTP server | Port |
|---|---|---|---|---|
| Gmail | imap.gmail.com | 993 | smtp.gmail.com | 587 |
| iCloud | imap.mail.me.com | 993 | smtp.mail.me.com | 587 |
| Outlook / M365 | outlook.office365.com | 993 | smtp-mail.outlook.com | 587 |
How polling behaves
- The first poll after going live establishes a baseline and fires nothing. Existing mail is not processed.
- Each subsequent poll fetches messages newer than the last one seen and fires one run each.
- Messages are de-duplicated by Message-ID, so the same mail never fires twice.
- If a poll takes longer than the interval, the next one is skipped rather than overlapping.
- If the mailbox is recreated, FlowForge re-baselines instead of replaying the whole folder.
- If a run can’t be recorded, the read position is not advanced — the next poll retries rather than dropping mail.
The mailbox password is resolved in the main process and never appears in the trigger’s output data.
Sending (SMTP)
Same idea: store the password as an API key connection, then configure the step. Port 465 uses implicit SSL; 587 uses STARTTLS. From defaults to your username if left blank.
Test this step on a Send Email step sends a real email.
AI in FlowForge
The AI Task step
The ✦ AI Task action puts a language model inside a workflow — to classify, extract, summarize, or draft.
Choosing a provider
Claude (cloud, your API key). Uses your own Anthropic API key from a Connection. You choose the model from a dropdown. Your input data goes to Anthropic under your own account and billing.
Ollama (local, fully private). Uses a model running on your own machine at http://127.0.0.1:11434. Nothing leaves your computer. Type any model you’ve pulled — llama3.2, qwen3:4b. Small local models handle classification and extraction well; drafting long prose benefits from a larger model.
ollama pull llama3.2
The output schema — the important part
Leave it blank and the step outputs { text: "…" }, readable as {{steps.N.text}}.
Fill it in with a JSON Schema and something much more useful happens: the reply is constrained to match your schema, and the parsed object becomes the step’s output. Each property turns into its own token.
{
"type": "object",
"properties": {
"category": { "type": "string", "enum": ["billing", "technical", "sales", "general"] },
"priority": { "type": "string", "enum": ["low", "medium", "high"] }
},
"required": ["category", "priority"],
"additionalProperties": false
}
With that schema on step 2, later steps can use {{steps.2.category}} and {{steps.2.priority}} directly — in an If condition, a table field, a notification title. This is what makes AI steps routable rather than just descriptive. Both providers support it.
Errors you might see
| Message | Fix |
|---|---|
| AI Task needs an Anthropic API key | Create an API key Connection and select it, or switch to Ollama |
| Anthropic rejected the API key | The key is wrong or revoked — check the Connection |
| Could not reach Ollama | Ollama isn’t installed or isn’t running |
| Ollama error 404 | The model isn’t pulled — ollama pull <model> |
| Claude declined this request | A safety refusal — rephrase the instruction or input |
| …did not return valid JSON for the output schema | Simplify the schema, or try a more capable model |
Create with AI and Fix with AI
Create with AI
Workflows → ✦ Create with AI, describe the automation, and get a draft workflow with steps and tokens already wired. It arrives as a draft, with tables and connections deliberately left for you to choose.
Fix with AI
When a run fails, open it in Run History and press ✦ Fix with AI.
- FlowForge sends the failing step’s configuration, its error, its (redacted) input, and its logs to the model, along with the step type’s field definitions.
- The model returns either a proposed configuration change with a plain-language explanation, or “this isn’t fixable by changing the config” — for a bad API key, a missing password, a service outage, or a rate limit, it says so instead of guessing.
- You see a diff: only the configuration keys that would change, old value struck through above the new one.
- Apply fix & retry run takes a version snapshot first, applies the change, and retries the run.
Because a snapshot is taken automatically, an AI fix is always one click in ⟲ History away from being undone. Nothing is ever applied without your review.
MCP: connecting AI assistants
FlowForge speaks the Model Context Protocol in both directions.
Your workflows as tools (FlowForge as a server)
FlowForge exposes every workflow as a callable tool to any MCP-capable assistant — Claude Desktop, Cursor, agent frameworks. Ask your assistant to “run my Lead Capture workflow” and it does, locally, with your approvals, redaction, and run history intact.
Find your URL in Help & Manual → Connect AI assistants. It looks like:
http://127.0.0.1:8474/mcp/<long-token>
That token is a password. It exists because an unauthenticated local MCP server would let any program on your machine — or a malicious web page using DNS rebinding — drive your workflows. Treat the URL accordingly. Rejected requests are written to the audit log.
Claude Desktop
{
"flowforge": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://127.0.0.1:8474/mcp/<your-token>"]
}
}
Hermes agent — ~/.hermes/config.yaml
mcp_servers:
flowforge:
url: http://127.0.0.1:8474/mcp/<your-token>
enabled: true
Your assistant sees a list_workflows tool plus one run_<workflow_name> tool per workflow, each accepting an optional triggerData object. The tool list is rebuilt on each request, so it always reflects your current workflows. FlowForge must be running; the window can be closed.
Calling other people’s tools (FlowForge as a client)
The MCP Tool Call action goes the other way — any MCP server becomes a workflow step. Filesystem access, search, databases, anything with a server. See its reference entry for the rug-pull guard.
Power user
Where your data lives
Everything is a file on your machine. Nothing is synced anywhere.
| What | macOS | Windows |
|---|---|---|
| Database workflows, steps, runs, tables, tasks, versions, audit log, encrypted connections | ~/Library/Application Support/FlowForge/flowforge.db | %APPDATA%\FlowForge\flowforge.db |
| Exports | ~/Documents/FlowForge Exports/ | %USERPROFILE%\Documents\FlowForge Exports\ |
| Credential encryption keys | macOS Keychain | Windows DPAPI |
Backing up is copying flowforge.db while FlowForge is quit. Moving to a new machine is copying that file across — except your connections, whose secrets are bound to the original machine’s keychain and must be re-entered.
Exporting workflows as JSON is the portable path. Exports never contain secrets, only references to connection names.
If the database can’t be opened at launch — locked by another copy, or a full disk — FlowForge shows you exactly where the file is and quits, rather than opening a window where nothing works.
Security model
FlowForge is a local app that talks to the internet on your behalf, so it takes a defensive posture in several places. See also the Security overview.
Credential isolation and redaction
Secrets live in the main process only, encrypted with the OS keychain. The app window never receives them and cannot request them. Steps reference credentials by connection id; authentication headers are merged into outbound requests at the last moment. Every secret value decrypted during a session is tracked and masked to •••••• in run logs, step outputs, error text, and MCP responses.
SSRF guard on every outbound request
- Hostnames are resolved once, every returned address is validated, and the socket is pinned to a vetted IP. A second DNS resolution at connect time — the DNS rebinding attack — is impossible.
- Blocked by default: loopback, private ranges, link-local, carrier-grade NAT, IPv6 ULA. Turn on Allow local network on a step to reach your LAN.
- Always blocked: the cloud metadata endpoint
169.254.169.254, and unspecified/invalid addresses. - Evasive encodings — decimal, octal, hex, short-form dotted quads, IPv4-mapped IPv6 — are normalized before classification.
- Only
http:andhttps:are permitted. - Up to 5 redirects, each re-checked. A cross-origin redirect strips all caller-supplied headers except a safe allowlist, so a redirect can’t smuggle your API key to another host.
- Responses are capped at 10 MB.
Code sandbox
Run Code executes in QuickJS (WebAssembly) with no host globals whatsoever. Memory is capped at 64 MB. The timeout uses a monotonic clock, so moving the system clock backwards cannot extend it. Every execution is audited.
Tamper-evident audit log
Security-relevant events are appended to a hash chain: each entry’s SHA-256 covers the previous entry’s hash plus the entry’s own canonical content. Editing or reordering any entry breaks the chain. Connections → Verify audit log re-hashes everything and tells you the exact sequence number where the chain breaks, if it does.
Logged events include: connection created, licence activated, workflow imported, version captured, sandboxed code executed, MCP tool pinned or re-approved, workflow run via MCP, MCP request rejected, approval decided, AI workflow generated, AI fix proposed. Secrets are never written to the audit log.
The risky-data-flow warning
The editor watches for a specific dangerous shape — sometimes called the “lethal trifecta” — and warns you at edit time:
- Untrusted input: a Webhook, Form, File Import, or Email trigger; or the output of an HTTP Request or MCP Tool Call.
- Private data: reading or writing your tables, or any step carrying a credential.
- Egress: HTTP Request, Trigger Webhook, Send Email, Slack, Discord, GitHub, Telegram, MCP Tool Call, or a cloud AI Task.
When all three co-occur in that order without a Wait for Approval step between them, a banner appears explaining which steps triggered it. The reasoning: a malicious payload in the untrusted input could steer what the sending step transmits. FlowForge can’t judge intent, but it can see the shape and tell you.
Process hardening
The app window runs with context isolation and sandboxing on, Node integration off, and webviews disabled. Navigation is locked to the app’s own origin. Every message the window sends to the main process is schema-validated before it reaches the database.
Execution semantics
For anyone who needs to reason precisely about what runs, when, and how many times.
The journal
A run is durable from the moment it starts. Its row is written before any step executes, and each step’s result is persisted as it completes — not batched at the end. A run interrupted by a crash or a quit is marked interrupted and resumed on next launch, from the journal. Completed steps are not re-executed. Retry and resume-after-approval use the same mechanism.
At-least-once, not exactly-once
Steps have at-least-once semantics. If FlowForge crashes in the window between a step performing its side effect and its result being written, that step re-executes on recovery. This is the honest trade-off of a durable local engine: an event is never silently lost, but a crash at exactly the wrong instant can duplicate one action.
Trigger sources compensate where they can: webhooks deduplicate identical payloads for 30 days; file imports deduplicate by path plus modification time; email deduplicates by Message-ID. In each case the dedup key is recorded only after the run is durable.
Replay rules
- The trigger does not re-fire — its journaled output is reused.
- A step journaled as success or skipped replays from the journal.
- A step journaled as failed, or with no journal entry, executes.
- An If step restores which branch it took, so branch skipping stays consistent.
- A Loop replays at loop granularity: the loop is treated as complete and each body step’s most recent journaled output is restored.
- A paused approval step is never replayed — the resume executes it, reads your decision, and proceeds.
Ordering, retries, and retention
- Runs of the same workflow are serialized — never two at once. Different workflows are independent.
- Beyond HTTP Request’s Max attempts field, the engine supports a per-step retry policy read from a step’s
retryconfiguration object. There is no form field for it yet; it can be set by editing exported workflow JSON. Default: 1 attempt, 30-second timeout. - Nothing is auto-deleted except deduplication keys, pruned after 30 days on launch. Run history, versions, and the audit log grow until you clear them.
Subscription and licensing
- The licence key is your subscription ID and begins with
sub_. - Re-validated online roughly every 72 hours.
- If the check fails (offline, server down), a 7-day grace window keeps everything working.
- After grace, runs are refused until validation succeeds.
- Cancellation or refund revokes access at the next check.
- The only thing transmitted is the key. No workflow content, no run data, no credentials, no telemetry.
- Activation is recorded in the audit log; the key itself never is.
- The grace clock is protected against being reset by moving the system clock backwards.
Reference
Troubleshooting
A run failed
Open it in Run History and expand the failing step. You get the exact error, the input the step received, and its logs. Fix the configuration and press Retry failed steps — successful steps replay from the journal rather than re-executing. If the cause isn’t obvious, try ✦ Fix with AI.
My scheduled workflow didn’t run
- Is the workflow On? Draft and Off workflows are not watched.
- Was FlowForge running? Check the menu-bar/tray icon. Turn on Start at Login.
- Was the Mac asleep? Enable Catch up missed runs on the trigger.
- Weekly schedules fire on Monday, and At time is ignored for the 15-minute and hourly intervals.
My webhook does nothing
- The workflow must be On.
- Post to
http://127.0.0.1:47250/hooks/<path>— POST only,Content-Type: application/json. - A path not starting with
/hooks/gets that prefix added. - Identical payloads are deduplicated — vary the body between tests.
The AI step says it needs a key
Create a Connection of type API key holding your Anthropic key, then select it in the step’s Anthropic API key field. Or switch the provider to Ollama and run a model locally.
Ollama errors
Install from ollama.com, make sure it’s running, and pull the model you named: ollama pull llama3.2. A 404 from Ollama almost always means the model isn’t pulled.
Connections are disabled
Your system keychain is unavailable, so FlowForge refuses to store secrets rather than write them in plain text. On macOS, check that your login keychain is unlocked.
Email trigger fires nothing
The first poll after going live is a baseline and intentionally fires nothing. Send yourself a test message afterwards. Also confirm you used an app password (Gmail and iCloud reject regular passwords over IMAP), and that your filters aren’t excluding the message.
My table step fails with “No table selected”
Templates reference tables by name. If you renamed or deleted the seeded table, the reference no longer resolves — the dropdown shows <name> (missing). Pick a real table.
“No record where …” from Update Record
Update Record fails when nothing matches. Either the record doesn’t exist yet (use Save to Table first) or the match field/value is wrong. If a miss is acceptable, set If this step fails to Continue anyway.
Loop fails with “Loop source must resolve to a list”
The List to loop over token isn’t resolving to an array. Test the upstream step and inspect its output to find the correct path — usually something like {{steps.2.data.results}}, not {{steps.2.data}}.
FlowForge won’t start — “could not open its database”
Another copy of FlowForge is running, or the disk is full. The dialog names the exact file path. Quit any other copy and try again.
Still stuck? Email hello@ffstudios.app.
Known limitations
An honest list of what version 1.0 does not do, so you don’t lose time discovering it.
| Limitation | Workaround |
|---|---|
| Loops can’t contain nested If or Loop steps. | Move the nested logic out of the loop, or use Run Code for per-item branching. |
| Approval steps can’t be used inside a loop. | Approve once before the loop, or restructure. |
| Webhook signature secret has no form field. | Add "secret": "…" to the trigger config via exported JSON, then re-import. |
| Form Submitted has no live listener. | Use Webhook Received with a real form service, or run manually. |
| Call Mock API makes no real request. | Use HTTP Request for real calls. |
| Per-step retry policy has no form field. | Edit the step’s retry object via exported JSON. |
| Records are not editable from the Tables UI — add and delete only. | Use an Update Record step, or delete and re-add. |
| No team/multi-user features. Single machine, single user by design. | Share workflows as exported JSON. |
| Extract NBS QNA GDP targets one specific statistical workbook. | Use Run Code or HTTP Request for other data sources. |
Token reference card
{{trigger.<field>}} a field from the trigger's output
{{trigger}} the entire trigger payload (as a whole value)
{{steps.<N>.<field>}} a field from step N's output
{{steps.<N>}} step N's entire output object
{{steps.<N>.a.b.0.c}} nested — objects and array indices both work
{{loop.item}} current item (loop body only)
{{loop.index}} 0-based position (loop body only)
{{loop.count}} items being processed (loop body only)
{{now}} run start time, ISO 8601
{{steps.<N>.failed}} true when step N failed with "continue on error"
{{steps.<N>.error}} that step's error message
- Step numbering
- The trigger is 1; the first action is 2.
- Type rule
- A field containing exactly one token yields the raw value (object, array, number). A token mixed with any other text yields a string.
- Autocomplete
- Type
{{in any token-aware field. Enter accepts the top match, Escape dismisses.
Glossary
- Action
- A step that does something. Everything after the trigger.
- Audit log
- A tamper-evident, hash-chained record of security-relevant events. Verify it from Connections.
- Branch
- The Yes or No path under an If step. Steps in an untaken branch are recorded as skipped.
- Connection
- A stored credential, encrypted with your OS keychain. Steps reference it by name; the secret never reaches the app window.
- Dedup window
- The 30-day period during which an identical webhook payload, file, or email message won’t fire a second run.
- Filtered
- A run status meaning a Filter condition wasn’t met and the run stopped intentionally. Not a failure.
- Grace window
- The 7 days FlowForge keeps working when it can’t reach the licence server.
- Journal
- The durable per-step record written as a run executes. What makes resume, retry, and crash recovery possible without re-running completed work.
- Live
- A workflow whose status is On, meaning the background runtime is watching for its trigger.
- MCP
- Model Context Protocol — the standard FlowForge uses to expose workflows as tools to AI assistants, and to call other tools as workflow steps.
- Piece / connector
- A small built-in integration (Slack, Discord, GitHub, Telegram) that receives a pre-authenticated, guarded HTTP client rather than raw credentials.
- Redaction
- Automatic masking of known secret values to
••••••in logs, outputs, errors, and MCP responses. - Run
- One execution of a workflow, containing one step run per step.
- SSRF guard
- The network protection that resolves and pins IP addresses before connecting, blocking loopback, private, and cloud-metadata addresses unless explicitly allowed.
- Step
- One node in a workflow: the trigger, or an action.
- Token
- A
{{…}}placeholder that reads data from the trigger or an earlier step at run time. - Trigger
- The single step that defines when a workflow starts.
- Version
- A hash-verified snapshot of a workflow’s complete definition, captured on every turn-on and restorable at any time.
- Workflow
- One trigger plus its ordered actions. The unit you build, run, export, and turn on.