A node is one step of a flow. Each node has a type (what it does), field values (its parameters), settings (retries, timeout, what to do on error) and one or more output dots (handles). An edge leaves from an output dot and lands on the next node, so a node with true and false dots branches the flow.
Mooflair ships 81 built-in nodes in 9 domains, and you can add your own script nodes.
In this section
| Page | Nodes |
|---|---|
| Trigger | 5 |
| Flow control | 11 |
| Data | 14 |
| Browser | 16 |
| AI | 7 |
| Human | 3 |
| 𝕏 | 11 |
| Threads | 11 |
| 9 | |
| Shopee | 3 |
| Script nodes | your own code, same SDK |
Anatomy of a node
{
"id": "collect",
"type": "x/collect",
"name": "Collect posts",
"field": { "max": 20, "language": "id", "refreshAfter": 50 },
"setting": { "retry": { "count": 0, "waitMs": 1000, "backoff": "fixed" }, "error": { "mode": "stop", "fallback": null, "break": { "count": 3, "waitMs": 600000 } }, "timeoutMs": 600000, "disabled": false, "item": { "isOnce": false, "isAlways": false, "batch": { "size": 0, "waitMs": 0 } } },
"pin": null,
"position": { "x": 900, "y": 0 },
"note": "Keeps scrolling; refreshes every 50 posts"
}
| Key | Meaning |
|---|---|
id | Unique in the flow. Other nodes read this node's item as {{ nodes.<id>.output }} (the one linked to theirs), the node right after it as {{ item }}. |
type | The node type, <domain>/<name> (e.g. x/collect, flow/merge, script/1a2b3c4d). |
name | The label on the canvas. |
field | Parameter values. Any text value can hold expressions such as {{ nodes.loop.output.item.url }}. |
setting | The common settings below. |
pin | Pinned items ({ item: [{ dot, json }] }): the node isn't run and these are its output. See . |
position, note | Canvas only: where it sits and the note shown under it. |
An edge goes from an output dot to an input dot; a dot can have many edges (each gets the items):
{ "id": "e1", "source": { "nodeId": "collect", "handle": "next" }, "target": { "nodeId": "choose", "handle": "in" } }
Items
What travels along an edge is a list of items (like n8n), each { json } with its (a screenshot, a download) and a trail linking it to the items it came from. A trigger starts a run with one item. Most nodes run once per input item (run: "each"), their fields resolved against that item; Merge runs once with every item (run: "once"). A node returns its items, each on one of its dots; an empty list ends that branch. A node with several inputs (Merge's A and B) waits until each connected input has delivered. See .
Common settings
Every node has the same settings (Node details › Settings). A node definition can change the defaults, and so can a template.
| Setting | Default | Meaning |
|---|---|---|
retry.count | 0 | How many more times to try after a failure. |
retry.waitMs | 1000 | Wait between tries. |
retry.backoff | fixed | exponential: the wait doubles each try (waitMs, 2×, 4×…). |
error.mode | stop | What happens after the last try fails, for that item. See On error. |
error.fallback | null | With continue: the output the item goes on with (null: { "error": "<message>" }). |
error.break.count, error.break.waitMs | 3, 600000 (10 minutes) | With break: how many times the run is resumed by itself, and how long apart. |
timeoutMs | 60000 (1 minute) | The step fails once it takes longer. The step's own AbortSignal fires, so a waiting or scrolling node stops at its next sleep. |
disabled | false | Skipped: the run passes straight through to the first output dot. |
unique.isEnabled, unique.similarity, unique.table, unique.column, unique.value, unique.aiId | false, 0.8, empty… | Never the same twice: its value (empty: its text field) is compared to the rows its table keeps (and by an AI key, for the same idea in other words); too alike and it doesn't run. See No repeats. isEnabled may be an expression, e.g. {{ option.post.unique }}. |
limit.mode, limit.window, limit.key | manual, none, empty | manual: at most max runs in any everyMs for every window at once, rolling windows (15 an hour and 180 a day: [{ "max": 15, "everyMs": 3600000 }, { "max": 180, "everyMs": 86400000 }]), shared by every flow and run of the workspace; no window: no limit. account: none of its own, the applies. See Limit. Values may be expressions, e.g. {{ option.pace.limit.mode }} and {{ option.pace.limit.window }}. |
Limit
Any node can have a limit (Node details › Settings › Limit): manual, its own windows (several at once: 15 an hour and 180 a day), or account, none of its own (the account's limit on posts, replies and quotes waits for a free slot instead, see ). With windows set, it gets one more output dot, limited.
- Before the step runs, the engine checks its key against every window with mooflair ():
limit.key, or empty for<node type>:<account id>(x/post:6ae4…,threads/reply:6ae4…), so each account has its own. - No slot left: the step doesn't run; the item leaves through
limitedwith{ "count": 1, "nextAt": "2026-10-30T15:00:00.000Z" }:nextAtis when every window has room again. A until{{ nodes.<id>.output.nextAt }}holds the run for the next slot, or a Wait inlimitmode before the node waits until it may run at all (the posting templates do this, so nothing is written for a post that can't go). - It counts only once the step ran: a failure or a crash mid-step counts nothing. A rollback takes the count back. Two runs on the same key at the very same moment can both pass the check: it goes over once, and says so in the log.
Test runs only look: they never take a slot. Example: 𝕏 Post with limit.mode {{ option.pace.limit.mode }} and limit.window {{ option.pace.limit.window }}, a campaign setting its limit option to 1 per 3 hours, posts at most once every 3 hours; switched to Follow the account, only the account's own limit applies.
No repeats
With unique.isEnabled on, a node gets one more output dot, duplicate. Before the step runs, the engine asks mooflair () whether it would repeat itself:
- Its
unique.value(empty: itstextfield, the post, quote or reply it is about to send) is compared to what it already did: the last 1000 rows ofunique.table, theirunique.column(x/post/post›textfor 𝕏 posts and quotes,threads/post/thread›post,x/collect/postandthreads/collect/post›messagefor replies). The table keeps them as it does (per account, or per campaign group), so the history is everything the account already posted, from before this setting existed too. - Words in common over words in either (case, punctuation and links aside) at or above
unique.similarityis the same:0.8catches a rewrite of one or two words,0.9suits short replies,1only the same words. - With
unique.aiId(an AI key,{{ option.ai }}), when the words differ, the AI also reads the last 30 and answers whether the new one says the same thing in other words. - The same: the step doesn't run; the item leaves through
duplicatewith{ "text": "…", "same": "<the one it matched>" }.
Nothing is recorded by this setting: the node's own row is the record, written only once it really posted, so a crash leaves nothing behind. Test runs compare with test rows. The templates turn it on with the No repeats option (on by default) and send duplicate where they go when there's nothing to post.
On error
| Mode | In Node details | What happens |
|---|---|---|
stop | Stop the run | The run fails here. Its runs, if it has one. |
human | Ask a human | The run waits for you (Waiting for you, a notification under the bell). Fix it in the live view or change the node's simple values, then Retry runs the node again. |
output | Use the error dot | The item leaves through the node's error dot as { "error": "<message>" }. Only nodes with an error dot; others stop. |
continue | Continue with an output | The item goes on along the first dot with error.fallback (none: { "error": "<message>" }). |
ignore | Drop the item | The item is dropped and its branch ends there. The run still succeeds. |
commit | End the run as succeeded | The run ends now as Succeeded and keeps everything it did. Its other lanes stop. |
rollback | End the run and undo | The run ends Failed, and what it wrote in mooflair is undone, newest first: rows it added are deleted, rows it changed or deleted are put back, it counted go one back. Posts, replies, HTTP calls and other outside effects can't be undone. Nodes whose writes can be undone (isTransactional: Data table, Daily limit) say so in Node details. |
break | Save to resume later | The run ends Incomplete at this node. mooflair resumes it by itself after error.break.waitMs, up to error.break.count times, and you can Resume or Discard it on the . A resume is a new run that goes on from this node, the earlier nodes keeping their outputs. Its error flow doesn't run. With the flow setting Keep incomplete runs off, break fails the run instead. |
commit, rollback and break end the whole run from wherever they are, a called flow included.
How a rollback knows what to undo: every run's token carries a journal id, and each table write or cap count through the saves its undo step (FlowJournal, kept 90 days). When the run ends with rollback, the engine hands the journal id back and mooflair runs those steps. A resumed run has a journal of its own, so a rollback undoes only that attempt's writes.
Raised defaults (from the node definitions):
| Node | Default timeout |
|---|---|
x/collect | 10 minutes |
flow/run | 15 minutes |
x/post, threads/post, facebook/post (uploads) | 30 minutes |
x/reply, x/quote, threads/reply, threads/quote, facebook/reply, shopee/search, shopee/affiliate | 5 minutes |
x/timeline, every ai/* | 2 minutes |
flow/wait | 7 days |
human/approval | 1 day |
Nodes that need a plan
A node can need a plan feature (feature on its definition). Pro: HTTP request and every app node and polling trigger (integration), every script node (code), every file node with the Drive (file), the AI agent (ai.agent), knowledge nodes (ai.knowledge), the Chat and MCP triggers. Team: AI browse (ai.browse). Business: Command (system.command). The editor shows a lock on them (picker, canvas, Node details) and you can still add and save them, but a run whose flow, called flows or error flow uses one is refused with "Your plan doesn't include …". The Studio agent only suggests nodes your plan includes. See .
Effect nodes (⚡)
An effect node acts on the outside world: it posts, replies, sends, buys. In a test run effect nodes are simulated: they don't run, they leave through their first output dot with output { "dry": true }, and the log says Dry run: <name> was not executed. So you can test a whole flow safely. A real run asks you to confirm first. Effect nodes in the catalog below carry ⚡. Its kind is act (a like, a delete, a form or email sent) or publish (a post, reply or quote): only publishing counts toward an account's Posts per hour.
Field kinds
| Kind | Editor | Notes |
|---|---|---|
text, textarea, prompt | Text input / area | Expressions allowed. Typing {{ opens the value picker. |
number, boolean, select | Number, switch, dropdown | Press = (or "Use an expression") to switch to an expression. |
json | JSON editor | Objects and arrays, expressions allowed inside strings. |
select with isMultiple | Toggle buttons | The value is a list (a webhook's methods). |
curl | Paste box + Import | Parses a cURL command into the node's method, URL, headers and body. Holds no value. |
list | Repeating rows of sub-fields | e.g. If conditions, Set values. |
dataset.account | One of the flow's account options of that platform ({{ option.account.x }}) | The campaign (or the option's default) picks the account; it becomes a secret in the run payload, never sent to the page. |
dataset.ai | One of the flow's AI key options ({{ option.ai }}) | Same, for AI keys: the campaign (or the option's default) picks the key. |
dataset.post | One of the flow's post set options ({{ option.post.dataset }}) | Same, for : the campaign picks the set, the node gets its id and name and reaches its posts through the run API. |
drive | Text (an expression) | Drive file ids: a list ({{ nodes.next.output.media }}), one id, or a comma list. Before each step the engine copies every file into the run (from the engine holding it), so the node gets run files ready for page.setInputFiles (file.path(entry)). A file that was deleted fails the step and names it. The post nodes' Media is one. |
dataset.knowledge | Knowledge base picker | A you may use; the node reaches it through the run API with its id. |
dataset.credential | Credential picker | A of the field's types (credential: ["slack/token"]; empty: any that signs HTTP requests). The run gets it resolved: values plus auth.header / auth.query. |
table | Table picker | Dataset › Tables. |
Show a field only for a mode
A field can carry show: { <field id>: [values] } (n8n's display options). Node details shows it only when every listed field holds one of its values, and a hidden field isn't required. A credential field that is hidden isn't put in the run either:
{ "id": "credential", "kind": "dataset.credential", "credential": [], "required": true, "show": { "auth": ["credential"] } }
Wait uses it for its modes (show: { "mode": ["until"] }), Merge for its join options, HTTP request for its credential.
Tables, stacks and stages
A node definition can declare what a shows, with no dashboard code:
| Key | Meaning | Example |
|---|---|---|
table | Tables it owns (<type>/<name>), each { name, storage, dashboard }. storage: scope, row lifetime, columns. dashboard: how a row shows (view: title, text, link, image, quote source text, time, meta count paths like stat.view, hide labels not listed), label tones, row actions, metrics. See . | x/collect owns post and author |
stack | { path, edit, take, view }: the output list the engine keeps as a live stack (a tab in Up next). edit: viewers can reorder, remove and finish items. take: the output field holding the item it hands out (current until the node runs again, then done). view: how a card shows | x/collect: items (edit) · ai/choose: picks (read only) · flow/loop: items, take item |
stage | Shown in a campaign's stage overview (triggers, effect nodes and Run flow always are) | x/collect, shopee/report |
A row action is a node run with the row's columns as its fields (by name) and the row's account, beside the campaign's run:
{ "action": { "delete": { "node": "x/reply/delete", "confirm": true, "when": ["replied"], "done": "deleted" } } }
Catalog
Trigger
| Node | Type | What it does | Output dots |
|---|---|---|---|
flow/input | Starts a flow that other flows can call | next | |
trigger/form | Starts the published flow when its public form is sent | next | |
trigger/manual | Starts when you press Run | next | |
trigger/schedule | Runs the published flow every few minutes, hours or days | next | |
trigger/webhook | Starts the published flow when its URL is called | next | |
trigger/chat | Starts the flow for each chat message | next | |
trigger/mcp | Offers the workspace's tool flows to MCP clients | next |
Flow control
| Node | Type | What it does | Output dots |
|---|---|---|---|
flow/if | Takes the true or false path | true, false | |
flow/loop | Runs the steps once per item | item, done | |
flow/merge | Joins the items of inputs A and B (waits for both) | next | |
flow/return | Ends a callable flow and hands back its output | — | |
flow/run | Runs another flow and waits for its result | next, error | |
flow/stop | Fails the run with your message | — | |
flow/switch | Routes to the case that matches a value | case1, case2, case3, case4, fallback | |
flow/wait | Waits a time, until a moment, a random while, a call or a form | next, timeout | |
integration/telegram | Sends and manages Telegram messages through your bot | next | |
integration/discord | Posts to Discord through a webhook or your bot | next | |
integration/slack | Sends messages and reads channels in Slack | next | |
integration/notion | Creates and queries Notion pages and databases | next | |
integration/airtable | Lists, creates and updates Airtable records | next | |
integration/sheets | Reads and writes rows of a spreadsheet | next | |
integration/drive | Searches, downloads and uploads Drive files | next | |
integration/gmail | Sends, finds and reads Gmail messages | next | |
integration/calendar | Lists, creates and deletes calendar events | next | |
Data
| Node | Type | What it does | Output dots |
|---|---|---|---|
data/post | Hands out the next post of a post set, or files of a Drive folder, for this campaign | next, empty | |
data/aggregate | Combines many items into one value | next | |
data/date | Gets, formats, shifts or compares dates | next | |
data/filter | Keeps the items that match conditions | next, empty | |
data/http | Calls an API or webhook | next, error | |
data/limit | Keeps the first or last N items | next | |
data/random | Picks random items from a list | next, empty | |
data/set | Builds an object from values | next | |
data/sort | Sorts a list by a field | next | |
data/split | Turns text into a list | next | |
data/table | Finds, adds, changes or deletes rows in one of your tables | next, empty | |
data/totp | The account's current authenticator code | next, none | |
data/unique | Drops repeated items | next |
Browser
| Node | Type | What it does | Output dots |
|---|---|---|---|
browser/click | Clicks an element | next, notFound | |
browser/cookie | Reads, sets or clears cookies | next | |
browser/extract | Reads text, attributes or lists | next, notFound | |
browser/hover | Moves the mouse over an element | next, notFound | |
browser/key | Presses a key or shortcut | next | |
browser/navigate | Goes back, forward or reloads the tab | next | |
browser/open | Opens a page in the current tab | next, error | |
browser/screenshot | Captures the page or an element as a file | next | |
browser/scroll | Scrolls the page | next | |
browser/select | Picks an option in a dropdown | next, notFound | |
browser/tab/close | Closes the current tab | next | |
browser/tab/new | Opens a new tab | next | |
browser/tab/switch | Makes another open tab current | next, notFound | |
browser/type | Types into a field | next, notFound | |
browser/url | Waits until the address matches | matched, timeout | |
browser/wait | Waits for an element | found, timeout |
AI
| Node | Type | What it does | Output dots |
|---|---|---|---|
ai/choose | Picks the best items by your criteria | next, none | |
ai/classify | Puts text into one of your categories | next | |
ai/guard | Checks text against your rules | pass, fail | |
ai/json | Gets structured JSON from the AI | next, invalid | |
chat/respond | Sends a message to the chat, and can wait for the reply | next, timeout | |
ai/agent | Thinks, uses tools (flows, nodes, HTTP, MCP, code) and answers | next | |
ai/knowledge | Searches, adds to or removes from a knowledge base | next, empty | |
ai/embed | Turns text into a vector | next | |
ai/prompt | Asks the AI | next, error | |
ai/summarize | Shortens text to what matters | next | |
ai/write | Writes a reply, post or message | next |
Human
| Node | Type | What it does | Output dots |
|---|---|---|---|
human/approval | Lets you review and edit data | approve, reject | |
human/form | Asks a few questions and waits for the answers | next, timeout | |
human/intervention | Waits for you in the live view | next | |
human/notify | Tells you something: in mooflair, Discord or Slack | next |
𝕏
| Node | Type | What it does | Output dots |
|---|---|---|---|
x/collect | Scrolls and collects posts from the timeline | next, empty | |
x/health | Checks the 𝕏 page parts the 𝕏 nodes rely on still exist | ok, broken | |
x/reply ⚡ | Replies to a post | next, closed | |
x/reply/delete ⚡ | Deletes one of your replies and checks it's gone | next | |
x/signin | Signs in with an account (2FA included) | next | |
x/timeline | Opens For you, Following, a search or a profile | next | |
x/like ⚡ | Likes like a reader: a post on screen, the open post or a few of its replies | next | |
x/trend | Reads what people post now (For you, a List or a search) for an AI, with your last posts | next | |
x/post ⚡ | Posts your own tweet (removes the link preview) | next | |
x/quote ⚡ | Quote-posts a post with your text (removes the link preview) | next | |
x/prune ⚡ | Deletes your own old, barely seen replies or posts | next |
Threads
| Node | Type | What it does | Output dots |
|---|---|---|---|
threads/signin | Signs in with the account's Instagram login (2FA included) | next | |
threads/timeline | Opens For you, Following, a search or a profile | next | |
threads/collect | Scrolls and collects posts from the feed | next, empty | |
threads/like ⚡ | Likes like a reader: a post on screen, the open post or a few of its replies | next | |
threads/reply ⚡ | Replies to a post | next, closed | |
threads/reply/delete ⚡ | Deletes one of your replies and checks it's gone | next | |
threads/quote ⚡ | Quote-posts a post with your text | next | |
threads/trend | A snapshot of what the feed talks about now, with your last threads | next | |
threads/post ⚡ | Posts a hook and its replies; the last reply can wait for reach | next | |
threads/post/followup ⚡ | Posts the held-back last reply of threads that are due | next, none | |
threads/health | Checks the Threads page parts the Threads nodes rely on still exist | ok, broken |
| Node | Type | What it does | Output dots |
|---|---|---|---|
facebook/signin | Signs in with the account's login (2FA included), or waits for you | next | |
facebook/timeline | Opens your News Feed, a post search or groups | next | |
facebook/collect | Scrolls and collects posts from the feed, a search or a group | next, empty | |
facebook/like ⚡ | Likes like a reader: a post on screen, the open post or a few of its comments | next | |
facebook/reply ⚡ | Comments on a post | next, closed | |
facebook/reply/delete ⚡ | Deletes one of your comments and checks it's gone | next | |
facebook/trend | A snapshot of what the feed talks about now, with your last posts | next | |
facebook/post ⚡ | Posts text and media on your profile or in a group, and can share it to groups | next | |
facebook/health | Checks the Facebook page parts the Facebook nodes rely on still exist | ok, broken |
Shopee
| Node | Type | What it does | Output dots |
|---|---|---|---|
shopee/affiliate | Your affiliate link for a product from the search | next, error | |
shopee/report | Reads orders and commission per day and Sub ID from Shopee Affiliate | next, empty | |
shopee/search | Searches the Shopee Affiliate catalog (price, sold, commission) | next, empty | |
shopee/signin | Opens Shopee Affiliate; sign in once by scanning the QR code | next |
⚡ = effect node (simulated in test runs).
How a node runs
- The engine walks the graph from the start node (the trigger, or a called flow's Flow input) with one item, then follows the items along every edge of their dots (a branch finishes before the next starts, top to bottom on the canvas).
- For each input item (or once for all,
run: "once") it resolves the node's fields: expressions are replaced from that item (item), earlier nodes' linked items,vars,option,now(see ); dataset fields are filled from the payload's secrets. - It runs the node's compiled code with the (
item,items,index, …) and a stepAbortSignal(the run's pause/stop plus the node's timeout).- Where: a node whose definition has
page: true(browser, 𝕏, Threads, Shopee, Human intervention) runs in a Chrome browser (its Browser setting, else the one used last, elsedefault). Every other node (page: false: AI, data, flow, triggers, Approval, Notify; a script node unless its code usespageortabs) runs in the Chrome browser the run already uses, else in the owner's worker (no Chrome), so a flow without page nodes never starts Chrome ().
- Where: a node whose definition has
- The node returns
{ item: [{ dot, json }] }. Items past 1 MB in all are cut (the node's state says how many). Each item goes along every edge of its dot; no edge on that dot ends the path there. - In a test run an effect node isn't executed at all: it gives
{ "dry": true }on its first output dot, and the log saysDry run: <name> was not executed.
FAQ
Why does my node show a ⚠?
A required field is empty, or the node type no longer exists. Fill the field; a run won't start while an active node is missing one.
How do I make a step not stop the whole run when it fails?
Set On error to continue or output (and wire the error dot), or human to decide in the live view.
Can I test only part of a flow?
Yes. Right-click a node: Run this step or Run from here. Earlier outputs come from the last run, or from .
Threads icons
Threads names its nav icons with aria-label (Create, Profile) and a post's icons with an <svg><title> (Like, Unlike, Reply, Repost, More, Verified). The Threads nodes match both: svg[aria-label='Like'], svg:has(> title:text-is('Like')) in Playwright selectors, and the label or title in reads done inside the page. The Post node opens the composer from the nav's New thread (a role=dialog modal), else the floating Create (a role=menu popover), and works in whichever holds the text box. Threads Health checks them all.