Flow files
Getting started
The easiest way to make a flow is the chat in the side panel. Say what you want. It writes the steps. When it looks right, download the markdown file and send it to your team. They import it in Flows.
You can also have an agent write the file. Point it at this page so it uses the right keys.
This guide is that file format: a .md with a recipe between --- lines.
Minimal example
Makers of today's top Product Hunt launch.
---
type: flow // must be flow
format: 1 // leave this as 1
id: ph-top-makers-today // import updates this id
name: "Today's top PH makers" // card title
description: Makers of today's top Product Hunt launch. // card subtitle
start: "https://www.producthunt.com/" // first URL
steps:
- type: open // go to Product Hunt
label: Open Product Hunt today
url: "https://www.producthunt.com/"
- type: visit_links // open matching hrefs on this page
label: Open the top product
urlIncludes: "/products/|/posts/" // homepage product cards
limit: 1 // top product only
page:
- type: click // runs on that product page
label: Expand Launch Team
text: Show more
- type: extract // add people from this page
label: Add Launch Team makers
collect: dom
instruction: Launch Team makers of this product only. Skip Hunter-only.
heal:
expectMinPeople: 1 // fail the run if nobody is added
success: Makers from today's top Product Hunt launch.
notes: Homepage cards are /products/ or /posts/. Launch Team starts collapsed.
---Import the file in Flows. The card title comes from name. The subtitle comes from description.
What goes in the file
Every file needs type: flow, format: 1, id, name, start, and a steps list. Each step needs type and label. Unknown keys fail import.
start is the first URL. {{id}} pulls a value you type when the flow runs.
steps is the recipe. Most people jobs are a short list: get to the right pages, add people, drop the misses, then read X.
heal is for people scrapes. It says what a good run looks like. Skip it when you only click (upvote, and so on).
Ready-made files you can import are on Templates.
How it works
Write steps in this order. Keep the list short.
- Browse: get to the right page
- Add: put people on the list
- Filter: drop people who fail a check
- Enrich: fill fields from a profile
- Act: click a button, like upvote
One Add, then Filter while you peek, then Enrich. Do not hide a scrape plan in instruction. That field is who to keep on the page.
Browse
Get onto the page the rest of the recipe needs.
opengoes to a URL. First real step in most files.clickclicks a button or link (text: Show more, or aselector).browselooks around like a person. Use it before an action so the click does not fire on a cold load.scrolljumps the viewport to load more. Preferpaginateon X Latest instead of chaining this to collect people.visit_linkswithout apageextract opens matching hrefs and stops there.
- type: open
label: Open Product Hunt today
url: "https://www.producthunt.com/"wait is a dead pause. Prefer browse when the job should look human.
Add
Put people on the list from a page you can already see, or from pages you open on purpose.
extractreads the current page.collect: domis the default. Addinstructionso it knows who counts (Launch Team makers, tweet authors).paginatewalks several X Latest viewports and adds authors as it goes.visit_linkssnapshots the current page, keeps hrefs that matchurlIncludes, opens the firstlimitunique pages, then runspageon each tab.
- type: visit_links
label: Add makers from top products
urlIncludes: "/products/|/posts/"
limit: 8
page:
- type: click
label: Expand Launch Team
text: Show more
- type: extract
label: Add Launch Team makers
collect: dom
instruction: Launch Team makers of this product only. Skip Hunter-only.limit on visit_links is pages, not people. Do not copy that 8 onto visit_profiles.
Work that should run on every opened page belongs under page. A sibling step after visit_links runs once, on whatever tab is left.
Filter
Drop people who fail a check. The list updates as they fail.
keep+urlIncludeskeeps a person if their url, citation, or profile links match (twitter.com|x.com).keep: can_dmkeeps people with Message next to Follow on x.com.keep: matchis a judgment call. It needsinstruction("complaints", "worth a reply").
Put keep under visit_profiles.page when the check needs a peek (Links on a PH profile, Message on X). A top-level keep only uses data already on the person.
- type: visit_profiles
label: Keep people with X
urlIncludes: producthunt.com/@
page:
- type: extract
label: Read Links
collect: dom
- type: keep
label: Keep if Twitter or X is listed
urlIncludes: twitter.com|x.comhas_x is an old alias. Write urlIncludes: twitter.com|x.com instead.
Enrich
Open each collected person and fill the card (photo, bio, followers, website).
- type: visit_profiles
label: Read the X profile
urlIncludes: x.com
page:
- type: extract
label: Read the X profile
collect: domNo keep means everyone stays. urlIncludes here selects people you already have, not hrefs on the current page.
Do not add wait or browse on every x.com profile. The peek already waits for the photo.
Act
Click a button or link on the page. Point at it with the words on screen (text: Show more) or a CSS selector. Reddit upvote is script: reddit-upvote.
- type: click
label: Upvote
script: reddit-upvote
text: upvoteAction recipes often skip people entirely. Omit heal when you do not collect anyone.
Repeat over inputs
inputs are values you type each run. each runs the whole steps list once per list item. {{id}} substitutes into start and into step url, text, selector, and instruction.
inputs:
- id: link
type: list
itemType: url
label: Reddit links
placeholder: "https://www.reddit.com/r/…"
each: link
start: "{{link}}"typeurltextnumberselectlist
each is not a step. Do not put it on a step. page is the nest for links and people.
Repeat over links and people
page is a short playbook that runs on every opened href or person. One level only. Allowed inside page: click, extract, browse, wait, scroll, keep.
urlIncludes always matches the list that step walks.
| Step | What it matches |
|---|---|
visit_links | Hrefs on the current page |
visit_profiles | Collected person.url / citationUrl |
keep | Those person fields after the peek |
Homepage cards on Product Hunt are /products/ or /posts/. PH @ urls appear after you extract makers. visit_links with urlIncludes: producthunt.com/@ on the homepage finds nothing. visit_profiles with urlIncludes: /products/ also finds nothing. That step does not read the page.
A people scrape
Today's Product Hunt makers you can DM.
---
type: flow
format: 1
id: ph-makers-i-can-dm
name: Today's PH makers I can DM
description: Makers of today's top PH launches who have X and open DMs.
start: "https://www.producthunt.com/"
steps:
- type: open
label: Open Product Hunt today
url: "https://www.producthunt.com/"
- type: visit_links
label: Add makers from top products
urlIncludes: "/products/|/posts/"
limit: 8
page:
- type: click
label: Expand Launch Team
text: Show more
- type: extract
label: Add Launch Team makers
collect: dom
instruction: Launch Team makers of this product only. Skip Hunter-only.
- type: visit_profiles
label: Keep people with X
urlIncludes: producthunt.com/@
page:
- type: extract
label: Read Links
collect: dom
- type: keep
label: Keep if Twitter or X is listed
urlIncludes: twitter.com|x.com
- type: visit_profiles
label: Keep people I can DM
urlIncludes: x.com
page:
- type: extract
label: Read the X profile
collect: dom
- type: keep
label: Keep if I can DM
keep: can_dm
heal:
expectMinPeople: 2
success: Makers from today's top launches who have X and a Message button.
notes: Homepage cards are /products/ or /posts/. X lives under Links on /@ profiles.
---Browse the homepage, Add makers from the top product pages, Filter to people with X, then Filter again to open DMs. The second visit_profiles is also Enrich: the peek fills the X card.
An action
Upvote one or more Reddit links. No people, no heal.
---
type: flow
format: 1
id: reddit-upvote
name: Upvote a Reddit link
description: Paste Reddit posts or comments. Open each, then upvote.
start: "{{link}}"
each: link
inputs:
- id: link
type: list
itemType: url
label: Reddit links
placeholder: "https://www.reddit.com/r/…"
steps:
- type: open
label: Open the post
url: "{{link}}"
rewrite: reddit-thread
- type: browse
label: Look around
script: human
ms: 30000
ready: shreddit-post
- type: click
label: Upvote
script: reddit-upvote
text: upvote
---rewrite: reddit-thread opens the post page and keeps the comment id.
Before you import
- Every step has
typeandlabel. format: 1.descriptionis card copy (140 characters). Notheal.success.- Loops use
pageoreach, not a siblingclickyou meant to run N times. urlIncludesmatches the list that step walks.- People scrapes have
heal.expectMinPeople. - No
waitorbrowseon every x.com profile. - Do not invent fields. Unknown keys fail import.
Find tasks
For a task discovery flow, put find_tasks on each candidate page. Give the AI the action, product context, fit criteria, and time window. Results retain an exact destination and source evidence without creating a contact. Saved runs add Tasks automatically; builder tests remain previews.
- type: visit_links
label: Read candidate threads
urlIncludes: /comments/
limit: 5
page:
- type: find_tasks
label: Find useful replies
instruction: Find comment tasks on questions from the past 24 hours asking how to find first SaaS customers. Keep only a clear opportunity to offer a practical daily outreach routine. Preserve the direct thread URL and quote the request. Skip ads, resolved questions, and uncertain ages. Do not post anything.
limit: 3Choose and verify the search URL before this step. Omit people heal thresholds. Inspect tasks in the preview, including their destination, evidence, and suggested approach, before saving the recipe.
Reference
A flow file is a .md with a recipe between --- lines. Import also accepts JSON with the same keys. Unknown keys fail import.
The file
---
type: flow
format: 1
id: my-flow
name: Short title
description: One or two lines for the card. 140 characters max.
start: "https://example.com/"
steps:
- type: open
label: Open the page
url: "https://example.com/"
---| Key | Required | What it is |
|---|---|---|
type | yes | Must be flow. |
format | yes on new files | Must be 1. |
id | yes to update | Import upserts this id. |
name | yes | Card title. |
description | yes for people jobs | Card copy. Not heal.success. |
start | yes | First URL, or {{token}}. |
inputs | no | Values you type each run. |
each | no | Input id. Run steps once per list item. |
brief | no | Saved objective, product, audience, approach, frequency, search window and useful volume for task discovery. |
steps | yes | Ordered recipe. |
heal | people scrapes | What a good run looks like. |
description can also sit in the body after the closing ---. Frontmatter wins.
Flow context
The builder starts with up to three short questions for missing context. It can follow up on important gaps or a corrected goal, then proceeds directly when it has enough information. The optional brief stores those answers. objective is required when a brief is present; product, audience, value, approach, frequency, searchWindow and volume are optional strings. Export and import preserve the brief. Each execution snapshots it and supplies it to candidate screening and task suggestions without changing the saved step instructions.
For promotion flows, record the actual product benefit and preferred approach. Suggestions should connect that benefit to the observed request, offer useful help first, and avoid unsupported product claims.
Ask about cadence when it changes discovery. One-off searches can cover all time; recurring fresh-opportunity searches usually use a rolling 24-hour window for daily runs or seven days for weekly runs. Explicit age limits and evergreen research goals take precedence. Store relative windows so they apply at each run, and reflect them in both source filters and review criteria. A requested volume guides coverage, never padding. Frequency in the brief is context, not an enabled schedule.
Inputs
inputs:
- id: link
type: list
itemType: url
label: Reddit links
placeholder: "https://www.reddit.com/r/…"
each: link
start: "{{link}}"typeurltextnumberselectlist
itemType on a listurltext
options on select: a string array.
{{id}} substitutes into start and step url / text / selector / instruction.
Heal
heal:
expectMinPeople: 2
success: What a good people scrape looks like.
notes: What to try if it drifted.Required for people scrapes. Omit for action recipes. Never use success as the card description.
Steps
Every step needs type and label. Omit empty keys. One level of page only. page steps cannot nest another page.
Allowed page typesclickfind_tasksextractbrowsewaitscrollkeep
find_tasks
Find concrete tasks on the current page using AI. Use this for opportunity discovery. A task includes an action title, kind, exact target URL, relevance context, suggested approach, and a source excerpt. It does not require a person or perform the action.
instruction(required): desired action, product context, relevance criteria, time window, and exclusions. This runs again on every saved run.limit(optional): maximum tasks per inspected page, 1-20. Default 10.- Allowed at the top level or in
visit_links.pageandvisit_profiles.page. - Requires a configured AI provider. Destinations must appear in the snapshot and source excerpts must match visible text. Failures never fall back to unfiltered people.
- Omit
heal. Empty relevant results are valid. Blocked or unreadable pages fail visibly.
Tests preview tasks. Saved runs add tasks automatically. Imports deduplicate by action kind and destination, across flows and including completed/cancelled tasks. To deliberately repeat an action at the same destination, create a manual task or a follow-up.
open
Go to a URL.
| Key | What it is |
|---|---|
url | Full https URL, or {{token}} from an input. |
rewrite | reddit-thread: open the post page, keep the comment id. |
First real step after an optional interpret.
wait
Dead pause. Default 800ms. Prefer browse when the job should look human.
| Key | What it is |
|---|---|
ms | Milliseconds. |
browse
Look around like a person (inertia swipes, reading pauses).
| Key | What it is |
|---|---|
script | human. |
ms | How long (default 30000). |
times | Optional swipe cap. |
ready | CSS selector that means the page is ready. |
selector | Optional target to work toward. |
scroll
Fast viewport jumps to load more. Use paginate on X Latest instead of chaining this to collect people.
| Key | What it is |
|---|---|
times | Viewports (1 to 8). |
click
Click a button or link.
| Key | What it is |
|---|---|
text | Visible label (PH: Show more). |
selector | CSS selector. |
script | reddit-upvote for Reddit votes. |
A top-level click runs once. Under visit_links.page it runs on every opened href.
extract
Add people from the current page, or peek the current profile when nested under visit_profiles.page.
| Key | What it is |
|---|---|
collect | dom (default) or ai. Prefer dom. |
instruction | Who to keep on the page. Required for ai. |
urlIncludes | On a lone extract: only keep people whose url matches. |
paginate
Add people across several X Latest viewports.
| Key | What it is |
|---|---|
times | Pages (3 to 8). |
limit | Stop after this many people. |
collect | Usually dom. |
visit_links
Map over hrefs on the current page.
- Snapshot the page.
- Keep hrefs that match
urlIncludes, firstlimitunique pages. - Open each href.
- Run
pageon that tab.
| Key | What it is | |
|---|---|---|
urlIncludes | Href needles, `\ | ` separated. Page snapshot only. |
limit | Pages to open, not people. 5 to 8 for top products. | |
page | Per-href playbook. |
Old shorthand: text / collect / instruction if page is omitted. Prefer page.
visit_profiles
Map over collected people.
- Take people whose
url/citationUrlmatchurlIncludes. - Open each person’s url.
- Run
page(extract peeks, keep drops as you go).
| Key | What it is |
|---|---|
urlIncludes | Person urls, not the current page. |
page | Per-person playbook. |
limit | Omit. Default is everyone (cap 80). |
Old shorthand: keep (can_dm, has_x) if page is omitted. Prefer page keep.
keep
Filter people already on the list.
| Key | What it is |
|---|---|
urlIncludes | Keep if person url, citationUrl, or profile links match. |
keep | can_dm: Message next to Follow on x.com. match: needs instruction. has_x: old alias for twitter/x urlIncludes. |
Put keep on visit_profiles.page when the check needs a peek. A top-level keep only uses data already on the person.
interpret
Optional first step. Reads a prompt from the trigger URL and fills inputs. Needs instruction. Do not use as the default.
Named values
A few keys take a fixed word. Use text, selector, or urlIncludes when a visible label or URL substring is enough.
| Key | Values |
|---|---|
script on browse | human |
script on click | reddit-upvote |
rewrite on open | reddit-thread |
keep on keep | can_dm, match (has_x is an old alias) |
collect | dom, ai |
Task candidate screening
When visit_links contains find_tasks in page, it screens up to 60 unique matching destinations in one AI call before opening up to limit promising pages (default 12). The criteria come from nested find_tasks instructions. Short titles are candidates, not verified tasks. Full-page review produces structured task JSON or a short skip reason. The step details show screening and review outcomes without raw profile links. Existing people recipes retain their first-link behavior.