Imported from idokraicer/optimate-make-cli (
.claude/skills/make-fixer/SKILL.md). Install upstream withnpx skills add idokraicer/optimate-make-cli --skill make-fixer. Copyright stays with the author.
Make.com Scenario Editor & Builder
You can fetch, edit, validate, and push Make.com scenario blueprints using the make-fixer CLI. You can also design new scenarios from scratch through collaborative brainstorming.
Bundled Reference Files — Load On Demand
This skill includes sub-files with deep reference material. Read them when needed:
-
FUNCTIONS_REFERENCE.md — Complete catalog of all Make.com inline functions (70+), date/time formatting/parsing tokens, filter operators, data types, and type coercion rules. Load this file whenever:
- The user asks about any Make.com function, expression syntax, or formula
- You need to write or verify an inline expression in a blueprint
- You need date formatting tokens, filter operator codes, or type coercion behavior
- You're unsure whether a function exists (this file is the canonical list — if it's not here, it doesn't exist)
-
BEST_PRACTICES.md — Error handling patterns, debugging workflows, testing strategies, naming conventions, architecture patterns (webhook chains, queue-based processing), production settings, AI integration patterns, cost optimization checklist, and data store design. Load this file whenever:
- Designing a new scenario from scratch
- Reviewing or improving an existing scenario's architecture
- Adding error handlers or debugging a failing scenario
- The user asks about Make.com best practices, patterns, or production readiness
Installation
The make-fixer CLI lives in a separate repo (github.com/idokraicer/make-fixer) and must be installed manually — it is NOT bundled with this plugin.
Before first use, check if make-fixer is available by running make-fixer --version. If the command is not found, walk the user through:
- Install Bun if missing:
curl -fsSL https://bun.sh/install | bash(then restart shell) - Clone + link the CLI:
git clone https://github.com/idokraicer/make-fixer.git ~/.make-fixer cd ~/.make-fixer && bun install && bun link - Verify:
make-fixer --version
If make-fixer: command not found persists after bun link, the user's ~/.bun/bin is not on PATH — point them to Bun's install docs.
Full setup steps (including how to get a Make.com API token) are in the plugin's README.md.
Setup
The API token is stored globally at ~/.make-fixer/.env and works from any directory.
To configure, run: make-fixer login --token <YOUR_TOKEN>
IMPORTANT: Always use --token <token> flag. Do NOT run bare make-fixer login — the interactive prompt does not work inside Claude Code.
Optionally set a custom base URL: make-fixer login --token <token> --base-url https://us1.make.com (default: https://eu1.make.com). Running login again updates the existing token.
If a command fails with "MAKE_API_TOKEN not found", ask the user for their token and run make-fixer login --token <token>.
CLI Reference
All commands accept -s <id-or-url> to specify a scenario. URLs auto-detect the zone.
| Command | Description | Key Flags |
|---|---|---|
login |
Save API token globally | --token <token> (required), --base-url <url> |
fetch |
Download blueprint to blueprints/<id>.json |
-s <id-or-url> |
analyze |
Quality report (no changes) | --local (use local file), --json, --var <name> (trace a variable) |
fix |
Auto-fix issues (uses Claude API) | --dry-run (preview only), --only <types>, --json |
validate |
Diff local vs remote blueprint | --json |
push |
Upload local blueprint to Make.com | --yes (skip confirmation) |
resume |
Generate builtin:Resume JSON for 429 retry pattern |
--errored <id>, --from <id>, --id <id>, --x, --y |
notes |
List or add scenario notes | --add, --module <ids>, --content <text> |
apps |
Search Make.com app catalog | [query], --limit <n> |
modules |
List modules for an app | <app-name>, --version <n> |
orgs |
List all organizations the token can access | --search <term>, --base-url <url>, --json |
teams |
List teams within an organization | -o <orgId> / --from-url <url>, --search <term>, --json |
scenarios |
List scenarios for a team or organization | -t <teamId> / -o <orgId> / --from-url <any-make-url>, --active, --folder, --search <term>, --limit, --json |
executions |
List recent executions for a scenario (or fetch one) | -s <id-or-url>, --id <executionId> (full detail), --failed, --status success|incomplete|error|failed, --limit, --from <ts>, --to <ts>, --json |
failures |
Scan an entire team/org for recently failed executions | -t <teamId> / -o <orgId> / --from-url <url>, --since 1h|24h|7d|30d, --limit <n> (per scenario), --include-paused, --json |
Useful patterns
# Trace a specific variable through the scenario
make-fixer analyze -s <id> --local --var myVariableName
# Preview auto-fixes without pushing
make-fixer fix -s <url> --dry-run
# Generate Resume module for 429 retry
make-fixer resume -s <id> --errored 5 --from 20
# Add documentation note to modules
make-fixer notes -s <id> --add --module 1,2,3 --content "Fetches customer data from CRM"
# Look up correct module type strings (ALWAYS do this — never guess)
make-fixer apps <search-query> && make-fixer modules <app-name>
# Discover what the token can see (use this when the user hasn't shared any URL)
make-fixer orgs # all orgs, with their zone
make-fixer teams -o <ORG_ID> # teams within an org
# List all scenarios in a team (derive teamId from ANY Make.com URL the user shared)
make-fixer scenarios --from-url "https://<zone>.make.com/<TEAM_ID>/scenarios/<SCENARIO_ID>/edit"
make-fixer scenarios --from-url "https://<zone>.make.com/organization/<ORG_ID>/dashboard"
make-fixer scenarios -o <ORG_ID> --search "webhook" # filter by name (case-insensitive)
# Recent executions for a scenario + drill into one
make-fixer executions -s "https://<zone>.make.com/<TEAM_ID>/scenarios/<SCENARIO_ID>/edit" --limit 10
make-fixer executions -s <SCENARIO_ID> --failed # only non-success runs
make-fixer executions -s <SCENARIO_ID> --id <EXECUTION_ID> # full JSON for one run
# Org/team-wide failure sweep (use this when the user asks "what's broken?")
make-fixer failures --from-url "https://<zone>.make.com/organization/<ORG_ID>/dashboard" --since 24h
make-fixer failures --from-url "https://<zone>.make.com/<TEAM_ID>/scenarios/<SCENARIO_ID>/edit" --since 7d
Status codes are confusing — read this before reasoning about errors
Make.com's execution status field doesn't match the labels in the UI:
| API status | API docs say | Make UI label | What you'll actually see |
|---|---|---|---|
| 1 | success | Success | The vast majority of runs |
| 2 | warning | Error | Almost every "error" in the UI is status=2 |
| 3 | error | Error | Rare — only fatal, unhandled crashes |
Always use --failed (or --status failed) when looking for problems, not --status error.
The failures command does this automatically. The status=2/3 distinction is rarely useful;
treat any non-1 execution as something the user wants to know about.
Discovering teamId / orgId from URLs
Any Make.com URL the user pastes contains the team or organization ID — never ask the user to look it up manually:
| URL shape | What you can extract |
|---|---|
https://<zone>.make.com/<TEAM_ID>/scenarios/<id>/edit |
teamId + scenarioId + zone |
https://<zone>.make.com/organization/<ORG_ID>/dashboard |
organizationId + zone |
https://<zone>.make.com/<TEAM_ID>/... (any team-scoped page) |
teamId + zone |
Pass any of these to make-fixer scenarios --from-url <url> and the CLI auto-parses them. Use -t <teamId> or -o <orgId> directly only if the user gives you a bare ID.
When the user hasn't shared a URL at all, run make-fixer orgs to list what the token can access, then make-fixer teams -o <orgId> to drill in. Avoid asking the user for IDs they'd have to dig out of the UI.
Workflow
Prefer the full Make.com URL whenever the user provides one. Passing a URL to -s is the most reliable form — the zone and scenario ID are auto-detected and no zone discovery is needed:
make-fixer fetch -s "https://<zone>.make.com/<TEAM_ID>/scenarios/<SCENARIO_ID>/edit"
Bare scenario IDs also work: the CLI first tries the configured zone, then auto-probes the other known zones (eu1, eu2, us1, us2) on 401/404 and caches the discovered zone per scenario in ~/.make-fixer/.zones.json. If you already have a URL in context, pass it to skip the discovery round-trips.
Determine Mode
Based on what the user provides, choose the right starting point:
- Existing scenario (ID or URL provided): Start at Phase 1 — Gather Context
- New scenario idea (description of what they want to build): Start at Phase 2 — Brainstorm & Clarify
- Vague request ("help me with my automations", "I need a Make scenario"): Start at Phase 2 — Brainstorm & Clarify
Phase 1: Gather Context (existing scenarios)
When the user provides a scenario ID or URL — immediately fetch and analyze:
make-fixer fetch -s <ID-or-URL> && make-fixer analyze -s <ID> --local
Then read the blueprint file (blueprints/<id>.json) to understand the scenario structure: what modules exist, how they connect, what the scenario does, and what issues were found.
Present a brief summary to the user:
- Scenario name and what it does (1-2 sentences)
- Module count and flow structure (linear, branching, etc.)
- Issues found by
analyze(if any) - Proactive improvement suggestions (see below)
Proactive Improvement Suggestions
After analyzing a scenario, always look for and suggest improvements — even if the user didn't ask. Present these as observations, not demands:
- Cost optimization: Modules that could be consolidated, redundant steps, logic that could be combined into fewer modules. Every module costs operations.
- Missing error handlers: Modules making external API calls without
onerrorhandlers - Fragile patterns: Hardcoded values that should use variables, missing
ifemptyfallbacks on fields that could be null - Naming: Modules with default names that don't describe what they do
- Missing documentation: Complex modules (HTTP, Code, routers, iterators) without sticky notes — fix with
make-fixer notes --add, NOT by editing the blueprint - Structure: Overly complex routing that could be simplified, unnecessary intermediate modules
- Reliability: Missing retry logic, no fallback paths for critical operations
- If-else opportunity: Routers where only one branch should run could be replaced with If-else + Merge (see below)
Frame suggestions as: "I also noticed a few things that could be improved — want me to address any of these along with your main request?"
Phase 2: Brainstorm & Clarify
Ask questions one at a time to understand what the user wants to achieve. Prefer multiple-choice questions when possible (using the AskUserQuestion tool), but open-ended is fine too.
For existing scenarios, focus on:
- What they want to change and why
- Which modules or parts of the flow are involved
- Whether they want to fix existing issues, add new functionality, or restructure
- Any constraints (e.g. "don't touch module X", "must keep the existing webhook")
- Whether they also want to address any of the proactive improvement suggestions
For new scenarios, focus on:
- What triggers the scenario? (webhook, schedule, watching a service, etc.)
- What's the end goal? What should happen when the scenario runs successfully?
- What services/APIs are involved? (CRM, email, spreadsheets, HTTP APIs, etc.)
- What data flows between steps? What inputs does each step need?
- Error handling requirements? What should happen when something fails?
- Volume & frequency? How often does this run? How many records per run?
- Edge cases? What if a field is empty? What if an API is down?
Use the analysis results and blueprint structure to ask informed questions. Reference specific modules by their name/ID when asking about existing scenarios.
Do NOT batch questions. One question per message. If a topic needs more exploration, break it into multiple questions.
Keep asking until you have a clear picture. Don't rush to propose changes. It's better to ask one more question than to propose the wrong thing.
Phase 3: Propose Approaches
Once you understand what needs to happen:
- Propose 2-3 approaches when there are meaningful alternatives
- Describe each approach briefly
- List trade-offs (cost in operations, complexity, reliability, maintainability)
- Lead with your recommendation and explain why
- For straightforward changes, present a single clear plan
- List the specific edits: which modules change, what gets added/removed, what fields are modified
- For new scenarios, describe the full flow: trigger → processing → output, with module types
- Always consider cost: can this be done with fewer modules? Can steps be combined?
Get explicit user approval before proceeding.
Phase 4: Edit and Validate
Only after the user approves your plan:
- Look up module types:
make-fixer apps <query>andmake-fixer modules <app>to find correct module type strings. Always look up — never guess module type strings from memory. - Edit the blueprint file directly using your Edit tool
- Validate changes:
make-fixer validate -s <id>— shows diff vs remote (added/removed/modified modules, issue count) - Present the validation results to the user
- If validation reveals issues, fix them and re-validate
Phase 5: Push
- Always ask the user for explicit confirmation before pushing
- Push when confirmed:
make-fixer push -s <id> --yes - Confirm success to the user
Module Documentation — Use make-fixer notes, NOT Blueprint Edits
The ONLY way to manage notes is the make-fixer notes command. This applies whenever the user (or your own proactive suggestion) involves:
- Documenting what a module does
- Adding/updating sticky notes or comments on modules
- Resolving an
analyzeissue of type "missing documentation" - Annotating routes, branches, or groups of modules
List existing notes (always do this first before adding — avoids duplicates):
make-fixer notes -s <id-or-url>
Add a note to one or more modules:
make-fixer notes -s <id> --add --module 1,2,3 --content "Fetches customer data from CRM and normalizes phone numbers"
--moduletakes comma-separated module IDs. A single note can attach to multiple modules (it spans them visually in the UI).--contentsupports HTML — use<br>for line breaks,<b>...</b>for bold, etc.- Notes are pushed to Make.com immediately — there is no separate
pushstep for notes, andvalidatedoes NOT diff notes. - The blueprint file on disk is irrelevant to notes; you do not need to fetch first to add a note, and adding a note does not change
blueprints/<id>.json.
When analyze flags missing documentation, the fix is make-fixer notes --add, never a blueprint edit. Confirm the plan with the user (which modules, what content) before running the command — same approval gate as any other change.
Anti-Patterns
"This Is Too Simple To Need Questions" — Even "just add an error handler" requires understanding: which modules? what retry settings? should it break or ignore? A quick question prevents wasted work. The questions can be short for simple tasks, but you MUST ask before editing.
"I'll just add all the modules" — More modules = more cost. Always look for ways to consolidate. Three similar HTTP calls might be replaceable with one parameterized call inside an iterator. Question every module: is this strictly necessary?
"The user said what they want, I can just build it" — The user described their goal, not the design. You need to understand the design before building. Ask about edge cases, error handling, and data flow before proposing a plan.
"I'll add documentation by editing the blueprint" — Notes are NOT in the blueprint JSON. There is no module field that controls sticky-note documentation. Use make-fixer notes --add for ALL module documentation — see the "Module Documentation" section above.
"I know the module type string" — Never assume module type strings from memory. Apps update their module names. Always run make-fixer apps <query> then make-fixer modules <app> to get the current, correct type strings.
Blueprint Structure
A blueprint is a JSON object:
{
"name": "Scenario Name",
"flow": [
{
"id": 1,
"module": "gateway:CustomWebHook",
"version": 1,
"mapper": { ... },
"metadata": {
"designer": { "x": 0, "y": 0, "name": "Custom Name" }
},
"onerror": [
{ "id": 2, "module": "builtin:Break", "mapper": { "retry": true, "count": 3, "interval": 60 } }
],
"routes": [
{ "flow": [ ... nested modules ... ] }
]
}
],
"metadata": {
"scenario": {
"sequential": false,
"autoCommit": true,
"maxErrors": 3,
"confidential": false,
"freshVariables": false,
"dataloss": false
}
}
}
Key fields
- id: Unique integer. Never reuse. New modules must use the next available ID (shown by
fetchandvalidatecommands). - module: Module type string like
app:ModuleName. Always look up withmake-fixer apps+make-fixer modules— never guess. - mapper: Module configuration (API URLs, field mappings, query parameters)
- parameters: Connection IDs and module-level settings (separate from mapper)
- metadata.designer.name: Custom display name in the Make.com UI
- onerror: Error handler array. Standard break handler:
[{ "id": N, "module": "builtin:Break", "version": 1, "mapper": { "retry": true, "count": 3, "interval": 1 } }]. Theintervalis in minutes (1 = 1 minute, 60 = 1 hour). Whenmapperis omitted, Make.com defaults tocount: 3, interval: 15. - routes: Array of route objects, each containing a
flowarray (forbuiltin:BasicRouter) - branches: Array of branch objects (for
builtin:BasicIfElse— see If-else & Merge section) - filter: Filter condition between modules. Format:
{ "name": "Filter Name", "conditions": [[{ "a": "{{1.field}}", "b": "value", "o": "text:equal" }]] }. Inner array = AND conditions, outer array = OR groups. See filter operators below.
Filter operators
The o value is always lowercase and (except existence operators) carries a type: prefix. Make's API silently stores a malformed code, but the condition then never matches — wrong casing/prefix is the #1 cause of "filters that don't fire". Make does not use camelCase, the numeric: prefix, or boolean:exist.
| Type | Operator codes |
|---|---|
Existence (omit b) |
exist, notexist |
Text (append :ci for case-insensitive) |
text:equal, text:notequal, text:contain, text:notcontain, text:startswith, text:endswith |
Numeric (number: prefix) |
number:equal, number:notequal, number:greater, number:greaterorequal, number:less, number:lessorequal |
| Date | date:before, date:after, date:between |
| Boolean | boolean:equal, boolean:notequal |
| Array | array:contain |
Case-insensitive text example: text:equal:ci, text:notequal:ci, text:contain:ci, text:startswith:ci, text:endswith:ci.
Do NOT use (silently never match): notExist, greaterOrEqual, text:startsWith, text:contains, numeric:greater, boolean:exist, or bare equal/notEqual/greater/less.
Regex ("matches pattern"): Make's UI exposes a regex text operator, but its exact o code is not in any authoritative source — confirm it by adding the filter once in the Make editor and exporting the blueprint to read the o value. Don't guess it.
Scenario settings (metadata.scenario)
The blueprint's metadata.scenario object controls execution behavior:
| Setting | Description |
|---|---|
sequential |
Process bundles one at a time (prevents race conditions in webhook scenarios) |
autoCommit |
Auto-commit after each cycle in multi-cycle runs |
autoCommitTriggerLast |
Auto-commit applies to trigger module as well |
maxErrors |
Consecutive errors before scenario is disabled (instant/webhook scenarios disable after 1st error regardless) |
confidential |
Hide execution data (GDPR/HIPAA — disables debugging) |
freshVariables |
Reset variables at start of each execution |
dataloss |
Allow data loss for uninterrupted throughput |
dlq |
Dead letter queue enabled |
Module types — builtin only
The only module types you can use from memory are the builtin and utility modules — these are stable platform primitives:
Flow control (builtin:*):
builtin:BasicRouter— router, splits into parallel routes (hasroutesarray,mapperis alwaysnull)builtin:BasicFeeder— iterator, splits arrays into individual bundles. Mapper:{"array": "{{...}}"}builtin:BasicAggregator— array aggregator, merges bundles back. Mapper:{}, Parameters:{"feeder": <iterator-module-id>}builtin:BasicRepeater— repeater, runs N times. Mapper:{"start": "1", "repeats": "5", "step": "1"}builtin:BasicIfElse— if-else conditional routing (see If-else & Merge section below)builtin:BasicMerge— merge if-else branches back together (see If-else & Merge section below)
Error handlers (builtin:*):
builtin:Break— pause and retry. Mapper:{"retry": true, "count": 3, "interval": 15}builtin:Resume— continue with fallback values. Mapper maps output fields from retry module.builtin:Ignore— skip and continue. Mapper:{}builtin:Commit— save progress and stop. Mapper:{}builtin:Rollback— revert ACID changes and stop. Mapper:{}
Utility (util:*):
util:SetVariables— set multiple variables. Mapper:{"variables": [{"name": "", "value": ""}], "scope": "roundtrip"}util:SetVariable2— set single variable. Mapper:{"name": "", "scope": "roundtrip", "value": ""}util:GetVariables— get multiple variables. Mapper:{"variables": ["varName"]}util:GetVariable2— get single variable. Mapper:{"name": ""}util:FunctionSleep— delay. Mapper:{"duration": "1"}(seconds, max 300)util:FunctionIncrement— counter. Parameters:{"reset": "run"}util:FunctionAggregator2— numeric aggregator (sum/avg). Parameters:{"fn": "sum", "feeder": 0}util:TextAggregator— text aggregator. Parameters:{"rowSeparator": "", "feeder": 0}util:AggregateAggregator— table aggregator. Parameters:{"columnSeparator": "", "rowSeparator": "", "feeder": 0}util:Switcher— switch/pattern matching. Mapper:{"input": "", "casesTable": [{"pattern": "", "output": ""}], "elseOutput": ""}
Other platform modules:
gateway:CustomWebHook— instant webhook triggergateway:WebhookRespond— send HTTP response back to webhook callerjson:ParseJSON— parse JSON string to collectionjson:AggregateToJSON— aggregate bundles into JSON string. Parameters:{"type": <data-structure-id>, "feeder": <source-module-id>}xml:ParseXML— parse XML string to collection. Mapper:{"xml": "{{...}}"}code:ExecuteCode— JavaScript code module (last resort — see Native-First section)placeholder:Placeholder— empty placeholder module (used in If-else Else routes)
HTTP modules have auth-specific variants — the type string changes based on authentication method. Always look up with make-fixer modules http to get the exact variant name (e.g., http:ActionSendData vs http:ActionSendDataBasicAuth vs http:ActionSendDataAPIKeyAuth).
Expression syntax
Expressions use {{...}} delimiters inside module mapper fields.
Critical rules:
- Arguments are separated by semicolons
;— NEVER use commas. Example:{{if(1.status = "active"; "Yes"; "No")}} - Module output references:
{{moduleId.fieldName}}— e.g.{{1.phone}},{{3.data.email}} - Nested data: use dot notation —
{{1.body.results.0.name}} - Array indexing starts at 1 (not 0) when using
get()—{{get(1.items; 1)}}returns first item - Functions can be nested:
{{upper(substring(1.name; 0; 1))}}— uppercase first letter nowandtimestampare variables, NOT functions — write{{now}}not{{now()}}- Comparison operators in expressions:
=,!=,<,>,<=,>= - Logical operators:
and,or,not - Arithmetic:
+,-,*,/,%
Variable references
Modules reference other modules' output with {{moduleId.fieldName}}:
{{1.phone}}— fieldphonefrom module #1{{ifempty(1.phone; 0121231234)}}— with fallback
ifempty fallback values
ifempty is used to prevent null/empty values from breaking API calls or queries. The fallback must:
- Match the data type of the field — an email field gets a fake email, a phone field gets a fake phone number, etc.
- Never actually match a real record — the purpose is to let the query run and return zero results.
- Never use quoted strings for numeric-like fields — quotes break most APIs. Write
0121231234not"0121231234". - Be a realistic-looking value — not random gibberish, not empty strings, not single characters.
Examples:
- Email field:
{{ifempty(6.email; nomatch@invalid.test)}} - Phone field:
{{ifempty(6.phone; 0121231234)}} - ID/UUID field: generate a random UUID for each
ifempty— e.g.{{ifempty(6.id; f47ac10b-58cc-4372-a567-0e02b2c3d479)}}. Never reuse the same UUID across differentifemptycalls.
Set/Get Variables
There are 4 variable modules — use them correctly:
Set Variable (util:SetVariable2) — sets a single variable:
{ "mapper": { "name": "myVar", "scope": "roundtrip", "value": "{{1.someField}}" } }
Set Multiple Variables (util:SetVariables) — sets multiple variables at once:
{ "mapper": { "variables": [{ "name": "x", "value": "{{1.field1}}" }, { "name": "y", "value": "{{1.field2}}" }], "scope": "roundtrip" } }
Get Variable (util:GetVariable2) — retrieves a single variable:
{ "mapper": { "name": "myVar" } }
Get Multiple Variables (util:GetVariables) — retrieves multiple variables. Variables are plain strings, NOT objects:
{ "mapper": { "variables": ["x", "y"] } }
Variable lifetime (scope):
"roundtrip"= One Cycle — persists for one cycle of the scenario"execution"= One Execution — persists for the entire scenario execution
Important behavior: Variables with "roundtrip" scope do NOT reset between iterator iterations. They persist until explicitly overwritten. If you need per-iteration isolation, use a different pattern (e.g., inline expressions or aggregators).
Referencing variable output in downstream modules:
util:GetVariables(multiple): outputs use variable names —{{moduleId.myVarName}}util:GetVariable2(single): output is always namedvalue—{{moduleId.value}}
Cost: Prefer multi-variable modules. util:SetVariables with 5 vars = 1 operation. Five util:SetVariable2 = 5 operations.
Variable naming: Case-sensitive ("myVar" and "myvar" are different). Literal strings only — expressions NOT supported in name fields. Typos between Set and Get cause silent null returns (no error thrown).
When to use Set/Get Variables vs inline expressions:
- Use variables when you need to pass data between different routes (after a Router)
- Use variables when a value is computed once but referenced many times across distant modules
- Use inline expressions (
{{1.field}}) when data flows linearly — direct module references are simpler, cheaper (no extra module), and clearer
Inline functions quick reference
For the complete function catalog (70+ functions with signatures, parameters, return types, date/time tokens, filter operators, data types, and type coercion rules), read FUNCTIONS_REFERENCE.md.
Most-used functions (see FUNCTIONS_REFERENCE.md for full list):
| Category | Key functions |
|---|---|
| Control flow | if, ifempty, switch, coalesce |
| Collections | get, pick, omit, keys, map, toArray, toCollection |
| Strings | contains, replace, split, join, substring, trim, lower, upper, length, toString, indexOf |
| Arrays | map, join, merge, flatten, sort, first, last, length, add, remove, deduplicate, slice |
| Math | round, ceil, floor, abs, sum, max, min, formatNumber, parseNumber |
| Dates | formatDate, parseDate, addDays, addHours, addMinutes, addMonths, setDate, setDay |
Variables (no parentheses): now, timestamp, random, pi, newline
Special values: true, false, null, emptystring (forces truly empty output — not "", not null), newline (literal line break — useful in replace(text; newline; "<br />"))
If-else & Merge (Open Beta)
The If-else module (builtin:BasicIfElse) and Merge module (builtin:BasicMerge) are a new pair for conditional routing where only one branch executes and routes can be reconnected.
If-else vs Router
If-else (builtin:BasicIfElse) |
Router (builtin:BasicRouter) |
|
|---|---|---|
| Execution | Runs only the first matching condition | Runs all matching routes |
| Merge back | Routes can be merged with builtin:BasicMerge |
Routes cannot be reconnected |
| Cost | Uses operations but no credits (free) | Uses operations and credits |
| Nesting | Cannot nest Router or another If-else inside | Can nest freely |
| Use when | Exactly one path should run based on conditions | Multiple paths may need to run in parallel |
If-else blueprint structure
{
"id": 71,
"module": "builtin:BasicIfElse",
"version": 1,
"mapper": null,
"filter": null,
"branches": [
{
"type": "condition",
"label": "Record Doesn't Exist",
"merge": true,
"conditions": [[{ "a": "{{13.recordId}}", "o": "notexist" }]],
"flow": [
{ "id": 38, "module": "...", ... }
]
},
{
"type": "else",
"label": "",
"merge": true,
"disabled": false,
"flow": [
{ "id": 72, "module": "placeholder:Placeholder", ... }
]
}
]
}
Key differences from Router:
- Uses
branchesarray (notroutes) - Each branch has
type("condition"or"else"),label,conditions, andmerge(whether it connects to a Merge module) - Conditions live on the branch object (not on filters of the first module in the flow)
- The last branch is always
"type": "else"— runs if no conditions matched - Empty branches use
placeholder:Placeholderas a "Do Nothing" module
Merge blueprint structure
{
"id": 73,
"module": "builtin:BasicMerge",
"version": 1,
"mapper": null,
"filter": null,
"filters": [null, null],
"outputs": [
{
"name": "outputFieldName",
"mappings": [
"{{38.someField}}",
"{{13.someField}}"
]
}
]
}
Key fields:
outputs— array of named output fields. Each output has anameandmappingsarray.mappings— one entry per branch (in order). Maps the value from whichever branch actually ran.filters— one filter per incoming branch (usuallynull).- Downstream modules reference merge output as
{{mergeModuleId.outputFieldName}}.
When to use If-else + Merge
- Conditional create-or-update: Check if record exists → create on one branch, skip on else → merge back to continue with the record ID regardless of which path ran.
- Conditional enrichment: Different API calls based on data type → merge results into a single output for downstream processing.
- Replace Router with If-else when only one branch should ever run (saves operations since Router runs all matching routes).
Rules
- Never reuse module IDs. Check the "Next ID" shown by
fetchorvalidate. - Always validate before pushing. Run
make-fixer validate -s <id>to check your changes. - Always ask the user before pushing. Show them what changed and get explicit confirmation.
- Preserve the
idSequencefield if present — it is server-managed. - Error handlers need their own unique IDs — they are separate modules in the
onerrorarray. - Position modules visually using
metadata.designer.xandmetadata.designer.y. Incrementxby ~300 for each subsequent module. - Always look up module type strings with
make-fixer apps+make-fixer modulesbefore adding app modules. Never guess from memory.
Native-First Problem Solving
Almost any data transformation, conditional logic, or output shaping can be achieved natively using a combination of inline functions, raw string bodies, and expression composition. The techniques below are your toolkit. Work through all of them before concluding that native is insufficient.
Native Techniques Checklist
Before proposing a Code module, confirm you have considered every technique below:
| Technique | What it unlocks |
|---|---|
| Raw string body | HTTP module body set to Raw accepts full {{...}} expressions, string concatenation (+), and conditionals. Unlocks patterns that structured/mapped field mode cannot express. |
String concatenation (+) |
Build values character by character — inject quotes, colons, commas, brackets, and entire JSON structures manually. "\"key\": \"" + {{1.value}} + "\"" |
emptystring |
Produces truly zero output — not "", not null, not whitespace. Use it to make entire lines, keys, or segments disappear. |
if + emptystring |
Conditionally include or entirely exclude any piece of output: JSON keys, URL parameters, headers, body segments, array elements. The core pattern for conditional structures. |
ifempty + coalesce |
Fallback chains across multiple possible source fields. coalesce(1.mobile; 1.phone; 1.altPhone; "no-phone") |
| Nested function composition | Functions nest to arbitrary depth. A single expression can parse, transform, format, and conditionally output a value. {{if(length(trim(1.name)) > 0; upper(substring(trim(1.name); 0; 1)) + lower(substring(trim(1.name); 1)); emptystring)}} |
switch |
Replaces multi-branch if/else logic natively. Cleaner than nested if for 3+ conditions. |
omit / pick |
Shape collections without touching individual keys. Pass through an entire object minus sensitive fields, or extract only the fields you need. |
map + join |
Dynamically build comma-separated lists, query strings, or structured text from arrays. {{join(map(1.items; "name"); ", ")}} |
toArray + join |
Serialize entire collections into structured strings. Convert key-value pairs into URL params, header strings, or custom formats. |
replace with regex |
Pattern-based transformations on strings — extract, reformat, strip, or restructure text without code. |
newline variable |
Literal line break for use in replace: replace(text; newline; "<br />"). Also useful for building multi-line raw bodies. |
Worked Example: Conditional JSON Keys
Problem: Build an HTTP request body where some JSON keys should only appear if their value exists. Structured mode always sends every mapped field (as null or ""), but the API rejects unknown or empty keys.
Naive reaction: "This requires a Code module to build the JSON dynamically."
Native solution: Use Raw body mode with if + emptystring + string concatenation.
{
"name": "{{1.name}}",
"email": "{{1.email}}"{{if(length(ifempty(1.phone; emptystring)) > 0; ",\n \"phone\": \"" + 1.phone + "\""; emptystring)}}{{if(length(ifempty(1.company; emptystring)) > 0; ",\n \"company\": \"" + 1.company + "\""; emptystring)}}
}
How it works:
nameandemailare always present (required fields)- For each optional field:
ifchecks whether the value has content - If yes → emits
,"phone": "value"(including the leading comma) - If no → emits
emptystring(truly nothing — no comma, no key, no trace) - The result is valid JSON with only the keys that have values
The pattern: {{if(length(ifempty(FIELD; emptystring)) > 0; ",\"KEY\": \"" + FIELD + "\""; emptystring)}}
This same pattern extends to:
- Conditional URL parameters:
{{if(1.page; "&page=" + 1.page; emptystring)}} - Conditional headers: include or exclude entire header lines
- Conditional array elements: build arrays with only populated values
- Conditional XML nodes: same
if+emptystringpattern works for any text format
When a Code Module IS Justified
Only suggest a Code module when all three conditions are checked and at least one applies:
- Untrusted data — The values being inserted could contain characters that break string-built structures (unescaped quotes, backslashes, newlines, unicode). Raw string concatenation with user-generated content risks producing malformed JSON/XML. A Code module with proper
JSON.stringify()is safer. - Looping/recursion — The logic requires iterating in ways Make.com's Iterator + Aggregator pattern cannot handle (e.g., recursive tree traversal, variable-depth nesting, complex accumulation across iterations).
- Unmaintainable complexity — The expression would exceed ~200 characters or nest more than 4 levels deep, making it unreadable and un-debuggable. At that point, a Code module with comments is more maintainable.
If none of these apply, the answer is native. Go back to the techniques checklist.
Common Operations
Add error handler to a module
Standard break handler (works for most scenarios including webhooks — Make.com auto-responds 200 Accepted to webhook callers):
"onerror": [{ "id": NEXT_ID, "module": "builtin:Break", "version": 1, "mapper": { "retry": true, "count": 3, "interval": 1 } }]
Only when the scenario uses gateway:WebhookRespond for custom responses — add a WebhookRespond + Commit in the error handler to return an error response to the caller. See BEST_PRACTICES.md for this pattern and the full error handler decision guide.
Rename a module
Set metadata.designer.name:
"metadata": { "designer": { "x": 300, "y": 0, "name": "Descriptive Name" } }
Add a new module to the flow
Insert into the flow array at the desired position with a unique ID.
Add a route
Add an object with a flow array to a router module's routes array. Filters go on the first module of each route's flow array — NOT on the router itself. The router's mapper is always null.
Add an If-else branch
Add an object to the branches array of a builtin:BasicIfElse module. Conditions live on the branch object, not on filters. The last branch must always be "type": "else".
Quoting strings inside expressions
Inside {{...}}, use single quotes to avoid JSON escaping issues:
{ "field": "{{ifempty(1.name; 'Unknown')}}" }
Cost Optimization Principles
- Every module run costs operations and credits. Always build scenarios with the minimum number of modules needed.
- Free modules (operations but no credits):
builtin:BasicIfElse,builtin:BasicMerge,scenario-service:NameExecution("Name a Run"). Use these freely. - Consolidate logic where possible. Prefer fewer modules doing more over many modules doing little.
- When proposing approaches, always include the operation cost comparison (e.g. "Approach A: 5 modules per run, Approach B: 3 modules per run").
- Actively question every module: is this strictly necessary? Can it be combined with another step?
- Prefer If-else over Router when only one branch should run — Router executes all matching routes (wasting operations on unused branches).
Handling Arguments
When invoked with arguments ($ARGUMENTS), determine the starting point:
URL or numeric ID? → Phase 1: make-fixer fetch -s $ARGUMENTS then analyze and summarize.
Description of what to build? → Phase 2: Begin brainstorming questions immediately.
Both ID and description? → Fetch first, then use the description to inform your clarifying questions.
In all cases, go through the workflow phases. Never skip to editing.