
Make Module Configuring
- 215 installs
- 75 repo stars
- Updated July 21, 2026
- integromat/make-skills
Configure individual Make scenario modules by assigning connections, mapping data, and wiring webhooks, data stores, and IML expressions.
About
Covers configuring individual Make scenario modules: assigning connections, filling parameters, mapping data, and wiring webhooks, data stores, keys, and IML expressions. A developer uses it after deciding a scenario's module composition, to wire each module up correctly and validate it.
- Five-phase config workflow: read interface, resolve components, run RPCs, fill params/mapper, validate
- Cardinal rules on connection selection, component creation order, and omitting unwritten fields on updates
Make Module Configuring by the numbers
- 215 all-time installs (skills.sh)
- +12 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #577 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/integromat/make-skills --skill make-module-configuringAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 215 |
|---|---|
| repo stars | ★ 75 |
| Last updated | July 21, 2026 |
| Repository | integromat/make-skills ↗ |
What it does
Configure individual Make scenario modules by assigning connections, mapping data, and wiring webhooks, data stores, and IML expressions.
Files
Make Module Configuration
This skill covers configuring individual modules within a Make scenario. Once a scenario's module composition is decided (see make-scenario-building), each module must be configured: connections assigned, parameters filled, data mapped from upstream modules, and special components (webhooks, data stores, keys) wired up.
Known Make module id: the Make Code module is "module": "code:ExecuteCode".
Quick Routing
Read the reference file that matches the current task:
| Task | Reference |
|---|---|
| Configuring any module (start here) | General Principles — 5-phase workflow: read interface, resolve components, run RPCs, fill params/mapper, validate |
| Setting up or assigning a connection | Connections — credential request flow, scope checking, Extract Blueprint Components |
| Creating or assigning a webhook | Webhooks — custom vs branded, data structure definition |
| Creating or assigning a data store | Data Stores — requires data structure first |
| Defining a data structure (schema) | Data Structures — field types, nested structures |
| Provisioning keys or certificates | Keys — SSH, PEM/PFX via credential requests |
| Writing IML expressions | IML Expressions — functions, variables, operators, backtick rule |
| Mapping data between modules | Mapping — module ID references, output schema discovery |
| Adding filter conditions | Filtering — operators, AND/OR grouping, placement rules |
| Configuring an aggregator | Aggregators — feeder/target, variants, configuration order exception |
| Configuring an AI agent module | AI Agents — tools array, AI-decided fields, restore metadata |
Cardinal Rules
These apply to every module configuration. Violating any of them is the most common cause of broken scenarios.
1. Read the interface first. Call app-module_get with outputFormat: "instructions" before configuring any module. Never guess parameter names, types, or structures.
2. Validate every module. Call validate_module_configuration after assembling each module's config. Do not proceed if validation returns errors — no exceptions.
3. Component creation order. Data structures, then webhooks, then connections, then keys, then data stores (dependencies flow left to right). Connections and keys require credential requests (user completes auth); webhooks, data stores, and data structures can be created directly via MCP.
4. Configure left to right. Work upstream to downstream so output schemas are available for mapping. Exception: array aggregators need their target module configured first — see Aggregators.
5. Connection selection is interactive. Always present all matching connections to the user and let them choose. Never auto-select, even if only one match exists. See Connections.
6. Omit unwritten fields on updates. On update, upsert, and patch modules, omit any field that should be left alone from the mapper — never include it with an empty string "". An empty-string mapping overwrites the target record's existing value, looks identical to "unmapped" in the visual editor, and is not caught by validate_module_configuration. See Mapping → Field Omission on Updates and Upserts.
Official Documentation
Related Skills
- make-scenario-building — Which modules to use and how to compose them into flows (routing, branching, filtering, iterations, aggregations, error handling)
- make-mcp-reference — MCP server configuration, scopes, access control
Aggregators
What It Is
An aggregator collapses multiple bundles into a single bundle. It collects items from a loop (iterator or multi-bundle source) and produces one combined output — an array, concatenated text, a sum, or a table. Aggregators require special configuration beyond the standard parameters and mapper.
When It's Needed
- Collecting iterated items back into a single array
- Joining text from multiple bundles
- Summing, averaging, or counting numeric values across bundles
- Building a table from multiple rows
Aggregator Types
| Need | Module | Package |
|---|---|---|
| Collect items into array | BasicAggregator | builtin |
| Join text with separator | TextAggregator | util |
| Sum/avg/count numbers | FunctionAggregator2 | util |
| Build table (rows + columns) | AggregateAggregator | util |
Configuration Structure
Aggregators use three domains in the blueprint:
Parameters
| Field | Type | Description |
|---|---|---|
feeder | integer | Required. Module ID of the data source that starts the loop (an Iterator, or any module that produces multiple bundles). |
target | string | Optional. Points to a downstream module's data structure for structured mapping. Format: moduleId.path (e.g., 5.items). Empty when using multiselect variant. |
Mapper
What goes in the mapper depends on the variant (see below).
Flags
| Flag | Type | Description |
|---|---|---|
groupBy | text/IML | Expression for grouping items into separate aggregations. When set, produces one aggregated bundle per unique group value. |
stopIfEmpty | boolean | Whether to stop processing if the aggregation produces no items. |
Two Variants
Variant A: Without Target (Multiselect)
Used when the aggregator has no downstream target structure defined. The mapper contains direct field references from the feeder module:
{
"parameters": {
"feeder": 2
},
"mapper": {
"email": "{{2.email}}",
"name": "{{2.name}}",
"id": "{{2.id}}"
},
"flags": {
"stopIfEmpty": false
}
}Each mapper entry selects a field from the feeder's output to include in the aggregated array. The keys become the field names in the resulting array items.
Variant B: With Target (Structured Mapping)
Used when the aggregator maps into the data structure expected by a downstream module. The target parameter points to the downstream module and path:
{
"parameters": {
"feeder": 1,
"target": "5.headers"
},
"mapper": {
"name": "{{1.headerName}}",
"value": "{{1.headerValue}}"
},
"flags": {
"stopIfEmpty": true,
"groupBy": "{{1.category}}"
}
}The mapper structure matches the target module's expected input schema at the specified path. The aggregator produces output that fits directly into the downstream module's field.
Configuration Order Exception
Aggregators are the exception to the "configure left to right" rule:
1. First: Configure the target module (after the aggregator) to the extent possible without aggregated data — so its input schema is known. 2. Then: Configure the aggregator — set the feeder, target (if applicable), and mapper fields. 3. Finally: Return to the target module to complete any mapping that depends on the aggregated output.
This back-and-forth is necessary because the aggregator's target variant needs to know the downstream module's expected structure.
Gotchas
- Feeder is required. Every aggregator must specify which module feeds it. Without a feeder, the aggregator doesn't know which loop to collect from.
- Validator-vs-blueprint mismatch on `feeder`.
validate_module_configurationrejectsparameters.feederas"Unknown field 'feeder'"even though it's required for the blueprint to deploy. This is a known mismatch — the per-module schema returned byapp-module_getdoesn't exposefeeder, but the blueprint orchestration layer requires it. Workaround: skip the per-module validator for aggregators, OR run it withoutfeeder(just to validaterowSeparator,target, mapper, etc.), then re-addfeederbefore deploying. Always verify withvalidate_blueprint_schema(whole-blueprint validator) — it acceptsfeedercorrectly, andscenarios_createwill too. - Don't confuse feeder with source module ID in mapper. The
feederparameter identifies the loop source. The mapper references (e.g.,{{2.email}}) use the module ID of the module whose output fields are being collected — these are
often the same module, but not always.
- Empty aggregations. If the feeder produces zero bundles, the aggregator produces nothing by default. Use
stopIfEmpty: trueto halt the scenario, or handle the empty case downstream. - groupBy creates multiple outputs. When
groupByis set, the aggregator produces one bundle per unique group value instead of one single bundle. Downstream modules execute once per group. - Target must exist first. When using the target variant, the downstream module must be in the blueprint and its input schema must be resolvable before the aggregator can be configured.
- Data accessibility after aggregation. Bundles from the source module and any modules between the source and the aggregator are not outputted by the aggregator — their items are not accessible by downstream modules. To preserve intermediate data post-aggregation, explicitly include it in the aggregator's configuration fields (e.g., "Aggregated fields" in Array aggregator).
- Archive aggregator.
Archive > Create an archiveis also an aggregator — it collects files and outputs a ZIP file.
Official Documentation
See also: Mapping for how mapper references work, IML Expressions for expressions in groupBy and mapper values, General Principles for the overall configuration workflow.
Blueprint Example
{
"name": "AGGREGATION",
"flow": [
{
"id": 3,
"module": "builtin:BasicRepeater",
"version": 1,
"parameters": {},
"mapper": {
"start": "1",
"repeats": "10",
"step": "1"
},
"metadata": {
"designer": {
"x": 0,
"y": 0
},
"restore": {},
"expect": [
{
"name": "start",
"type": "number",
"label": "Initial value",
"required": true
},
{
"name": "repeats",
"type": "number",
"label": "Repeats",
"validate": {
"min": 0,
"max": 10000
},
"required": true
},
{
"name": "step",
"type": "number",
"label": "Step",
"required": true
}
]
}
},
{
"id": 6,
"module": "util:FunctionIncrement",
"version": 1,
"parameters": {
"reset": "scenario"
},
"mapper": {},
"metadata": {
"designer": {
"x": 300,
"y": 0
},
"restore": {
"parameters": {
"reset": {
"label": "Never"
}
}
},
"parameters": [
{
"name": "reset",
"type": "select",
"label": "Reset a value",
"required": true,
"validate": {
"enum": [
"run",
"execution",
"scenario"
]
}
}
]
}
},
{
"id": 8,
"module": "builtin:BasicAggregator",
"version": 1,
"parameters": {
"feeder": 3
},
"mapper": {
"i": "{{6.i}}"
},
"metadata": {
"designer": {
"x": 600,
"y": 0
},
"restore": {
"extra": {
"feeder": {
"label": "Repeater [3]"
},
"target": {
"label": "Custom"
}
}
}
}
},
{
"id": 9,
"module": "util:SetVariable2",
"version": 1,
"parameters": {},
"mapper": {
"name": "array",
"scope": "roundtrip",
"value": "{{8.array}}"
},
"metadata": {
"designer": {
"x": 900,
"y": 0
},
"restore": {
"expect": {
"scope": {
"label": "One cycle"
}
}
},
"expect": [
{
"name": "name",
"type": "text",
"label": "Variable name",
"required": true
},
{
"name": "scope",
"type": "select",
"label": "Variable lifetime",
"required": true,
"validate": {
"enum": [
"roundtrip",
"execution"
]
}
},
{
"name": "value",
"type": "any",
"label": "Variable value"
}
],
"interface": [
{
"name": "array",
"label": "array",
"type": "any"
}
]
}
}
],
"metadata": {
"instant": false,
"version": 1,
"scenario": {
"roundtrips": 1,
"maxErrors": 3,
"autoCommit": true,
"autoCommitTriggerLast": true,
"sequential": false,
"slots": null,
"confidential": false,
"dataloss": false,
"dlq": false,
"freshVariables": false
},
"designer": {
"orphans": []
},
"zone": "eu1.make.com",
"notes": []
}
}{
"name": "TEXT AGGREGATION",
"flow": [
{
"id": 2,
"module": "util:BasicTrigger",
"version": 1,
"parameters": {
"values": [
{
"spec": [
{
"name": "text",
"value": "a"
}
]
},
{
"spec": [
{
"name": "text",
"value": "b"
}
]
},
{
"spec": [
{
"name": "text",
"value": "c"
}
]
}
]
},
"mapper": {},
"metadata": {
"designer": {
"x": 0,
"y": 0
},
"restore": {
"parameters": {
"values": {
"items": [
{
"spec": {
"mode": "chose",
"items": [
null
]
}
},
{
"spec": {
"mode": "chose",
"items": [
null
]
}
},
{
"spec": {
"mode": "chose",
"items": [
null
]
}
}
]
}
}
},
"parameters": [
{
"name": "values",
"type": "array",
"label": "Bundles",
"required": true,
"spec": [
{
"name": "spec",
"label": "Items",
"type": "array",
"required": true,
"spec": [
{
"name": "name",
"label": "Name",
"required": true,
"type": "text"
},
{
"name": "value",
"label": "Value",
"required": true,
"type": "text"
}
]
}
]
}
],
"interface": [
{
"name": "text",
"label": "text",
"type": "text"
}
]
}
},
{
"id": 4,
"module": "util:TextAggregator",
"version": 1,
"parameters": {
"rowSeparator": "",
"feeder": 2
},
"mapper": {
"value": "{{2.text}}"
},
"metadata": {
"designer": {
"x": 300,
"y": 0
},
"restore": {
"parameters": {
"rowSeparator": {
"label": "Empty"
}
},
"extra": {
"feeder": {
"label": "Tools - Basic trigger [2]"
}
}
},
"parameters": [
{
"name": "rowSeparator",
"type": "select",
"label": "Row separator",
"validate": {
"enum": [
"\n",
"\t",
"other"
]
}
}
],
"expect": [
{
"name": "value",
"type": "text",
"label": "Text"
}
]
}
},
{
"id": 5,
"module": "util:SetVariables",
"version": 1,
"parameters": {},
"mapper": {
"variables": [
{
"name": "sentence",
"value": "{{4.text}}"
}
],
"scope": "roundtrip"
},
"metadata": {
"designer": {
"x": 600,
"y": 0
},
"restore": {
"expect": {
"variables": {
"items": [
null
]
},
"scope": {
"label": "One cycle"
}
}
},
"expect": [
{
"name": "variables",
"type": "array",
"label": "Variables",
"spec": [
{
"name": "name",
"label": "Variable name",
"type": "text",
"required": true
},
{
"name": "value",
"label": "Variable value",
"type": "any"
}
]
},
{
"name": "scope",
"type": "select",
"label": "Variable lifetime",
"required": true,
"validate": {
"enum": [
"roundtrip",
"execution"
]
}
}
],
"interface": [
{
"name": "sentence",
"label": "sentence",
"type": "any"
}
]
}
}
],
"metadata": {
"instant": false,
"version": 1,
"scenario": {
"roundtrips": 1,
"maxErrors": 3,
"autoCommit": true,
"autoCommitTriggerLast": true,
"sequential": false,
"slots": null,
"confidential": false,
"dataloss": false,
"dlq": false,
"freshVariables": false
},
"designer": {
"orphans": []
},
"zone": "eu1.make.com",
"notes": []
}
}AI Agent Module Configuration
Module: ai-local-agent:RunLocalAIAgent, version 0.
Additional Agent Capabilities
Beyond the mapper fields below, AI agents support:
- Context/Knowledge: Upload external knowledge files (TXT, PDF, DOCX, CSV, MD, JSON) to enhance the agent. Limits: 20MB per file, 50 files per team (250 Enterprise), 100 files per org (500 Enterprise), 20 files per agent. Files are chunked, vectorized, and stored in Make's RAG vector database.
- MCP Integration: Agents can connect to MCP servers for additional tools via a dedicated MCP section in configuration.
- Output files: Agents can generate output files in PDF, DOCX, TXT, and CSV formats.
These are configured in the Make UI, not via blueprint mapper fields.
Agent Module Mapper Fields
| Field | Type | Description |
|---|---|---|
defaultModel | select | LLM model to use (e.g., "gpt-5.4"). Resolve options via RPC. |
systemPrompt | text | Agent instructions — role, goals, constraints, step-by-step behavior. |
message | text | Input to the agent (typically mapped from upstream module output). Required. |
files | array | Input files — each with fileName (filename) and data (buffer). Supported input formats: JPG, PNG, GIF, PDF. |
threadId | text | Conversation ID for multi-turn interactions. |
modelConfig | collection | Model settings: tokenLimit, recursionLimit (steps per call), iterationsFromHistoryCount (max conversation history). |
timeout | number | Step timeout in seconds (120–600). |
outputType | select | Response format: "text" or "make-schema". |
Connection
Supported AI providers: OpenAI, Gemini, Anthropic Claude, and Make AI Provider (requires no external provider account). AI agents are available on all plans using Make AI Provider; custom provider connections require paid plans.
The agent module uses makeConnectionId in parameters (not __IMTCONN__). This is the AI provider connection.
"parameters": {
"makeConnectionId": 14613
},
"metadata": {
"restore": {
"parameters": {
"makeConnectionId": {
"label": "Maiak Token",
"data": {
"scoped": "true",
"connection": "openai-gpt-3"
}
}
}
}
}Tools Array
Tools live in a top-level tools property on the agent module object — not in parameters or mapper. Each tool is an object with:
name— tool display namedescription— what the tool does (the AI reads this to decide when to call it)flow— array of modules that execute when the tool is called
{
"id": 2,
"module": "ai-local-agent:RunLocalAIAgent",
"parameters": { ... },
"mapper": { ... },
"tools": [
{
"name": "Get current weather",
"description": "Returns current weather information for a specified location.",
"flow": [ ... ]
}
]
}Fixed vs AI-Decided Fields in Tool Modules
When configuring a module inside a tool's flow, some fields are fixed (hardcoded) and others are AI-decided (the agent fills them at runtime).
Fixed fields
Set directly in the mapper to a constant value or expression. See Mapping for details:
"mapper": {
"type": "name"
}AI-decided fields
Use the pattern {{agentModuleId.fieldName}}:
"mapper": {
"city": "{{2.city}}"
}Where:
agentModuleId= theidof the AI agent module in the scenario flow (e.g.,2)fieldName= the exact `name` from the module'sexpectschema
The restore.expect Object with extra
Every AI-decided field should have guidance in metadata.restore.expect.<fieldName>.extra. Without it, the agent has no hints about format or constraints.
| Property | Purpose |
|---|---|
aiHelp | Short hint about expected format/values (e.g., "Enter e.g. London, UK.") |
aiInstruction | Detailed instruction guiding the AI's decision for this field |
Full path: metadata.restore.expect.<fieldName>.extra.aiHelp / .aiInstruction
"metadata": {
"restore": {
"expect": {
"city": {
"extra": {
"aiHelp": "Enter e.g. London, UK.",
"aiInstruction": "Use the city name from the user's input message."
}
}
}
}
}Other restore.expect Properties
- `label` — human-readable label for select/dropdown fields (e.g.,
"label": "cities"for atypefield with value"name") - `mode: "chose"` — marks optional collection/array fields that were explicitly shown but left empty (e.g.,
"files": { "mode": "chose" })
Tool Module Connections
Tool modules that need connections use __IMTCONN__ in parameters (same as normal modules), with the restore object for label/data:
{
"id": 4,
"module": "discord:createMessage",
"parameters": {
"__IMTCONN__": 11867
},
"metadata": {
"restore": {
"parameters": {
"__IMTCONN__": {
"label": "DomiZ's Discord (Make (team1278341651986911253))",
"data": {
"scoped": "true",
"connection": "discord"
}
}
}
}
}
}Annotated Example: Weather Tool
This tool has one fixed field (type) and one AI-decided field (city):
{
"name": "Get current weather",
"description": "Returns current weather information for a specified location.",
"flow": [
{
"id": 3,
"module": "weather:ActionGetCurrentWeather",
"version": 1,
"parameters": {},
"mapper": {
"type": "name",
"city": "{{2.city}}"
},
"metadata": {
"restore": {
"expect": {
"type": {
"label": "cities"
},
"city": {
"extra": {
"aiHelp": "Enter e.g. London, UK."
}
}
}
},
"expect": [
{
"name": "type",
"type": "select",
"label": "I want to enter a location by",
"required": true,
"validate": {
"enum": ["name", "coords"]
}
},
{
"name": "city",
"type": "text",
"label": "City",
"required": true
}
]
}
}
]
}Breakdown:
"type": "name"— fixed: always look up by city name.restore.expect.type.labelstores the human label"cities"."city": "{{2.city}}"— AI-decided: agent module2fills this at runtime.restore.expect.city.extra.aiHelptells the AI the expected format.
Tool Discovery
Use app_modules_list with usage: "tool" to find modules compatible as agent tools. Not all modules support tool usage.
Gotchas
- `makeConnectionId` not `__IMTCONN__` for the agent module itself. Tool modules inside
flowuse__IMTCONN__as normal. - Every AI-decided field should have `restore.expect.<field>.extra.aiHelp` — without it the agent has no guidance on format/constraints.
- `restore.expect.<field>.mode: "chose"` marks optional collection/array fields that were explicitly shown but left empty.
- `restore.expect.<field>.label` stores the human-readable label for select/dropdown fields.
- Tool discovery: pass
usage: "tool"toapp_modules_listto filter for tool-compatible modules. - AI provider is locked at creation. Cannot be changed after the agent is created — must create a new agent to switch providers.
- Deleting an agent breaks dependent modules. All
Run an agentmodules referencing the deleted agent will fail. Check active scenarios before deleting.
Official Documentation
- Make AI Agents (New)
- Introduction to AI Agents
- Create Your First AI Agent
- Sales Outreach AI Agent Use Case
- Create AI Agents for Different Triggers
- Knowledge
- Make AI Agents (New) App
- Make AI Agents (New) Best Practices
Full Blueprint Example
Complete blueprint with a webhook trigger, AI agent with two tools (weather + Discord), and a webhook response. The Discord tool's module spec is trimmed to essential fields — in practice, retrieve the full spec via app-module_get.
See examples/ai-agent-full-blueprint.json for the complete blueprint.
Connections
What It Is
A connection is Make's way of authenticating a module with an external service. Before a module can interact with an app (e.g., Google Sheets, Slack, Stripe), it needs a connection that provides the credentials and permissions. Connection IDs are stored in the module's parameters domain (static, immutable at runtime).
When It's Needed
- Every module that interacts with an external service requires a connection.
- The module interface (from
app-module_getwith instructions format) specifies which connection type(s) the module accepts. - Some apps support multiple connection types (e.g., an email app may accept SMTP, Google, or Outlook connections). When multiple types are available, ask the user which one to use.
The Connection Provisioning Workflow
Connections cannot be created directly by the agent — they involve OAuth authorization flows, API key entry, or other sensitive credential handling that the user must complete. The agent's role is to orchestrate the process.
Step 1: Extract Blueprint Components
After laying out all modules in the scenario (unconfigured), call the Extract Blueprint Components tool with the blueprint. This returns:
- A list of all connections needed across the scenario
- The connection type for each (matching what the module interface specifies)
- Required OAuth scopes for each connection (critical for OAuth-based services)
This is the authoritative source for what connections the scenario needs and what scopes they require.
Step 2: Check for Existing Connections
For each required connection, check whether a compatible connection already exists in the user's team:
- Use
connections_listto find existing connections of the same type. - Scope verification (OAuth): If the Extract Blueprint Components output specifies required scopes, verify that the existing connection has all of them. A connection with insufficient scopes will cause 403/permission errors at runtime.
IMPORTANT — Always ask the user. Even if only one matching connection exists, present all options and let the user choose. Do not auto-select. The user may have multiple accounts or prefer a fresh connection.
Decision tree:
| Situation | Action |
|---|---|
| Existing connection(s) with sufficient scopes | List ALL matches with name, ID, and metadata (email/workspace). Include "Create a new connection" as the last option. Ask the user which to use. |
| Existing connection with insufficient scopes | Expand scopes (see Step 3a) or create new |
| No existing connection of the right type | Create new via credential request (see Step 3b) |
| Multiple connection types accepted by module | Ask user which type they want |
Step 3a: Expand Connection Scopes
If an existing connection lacks required scopes, use the scope expansion tool to request the user to reauthorize with additional permissions. The user goes through the OAuth consent screen again and grants the missing scopes.
Alternatively, offer to create a brand new connection with all required scopes from the start.
Step 3b: Create via Credential Request
For connections that don't exist yet:
1. Create a credential request via credential_requests_create. Provide:
- The connection type (from Extract Blueprint Components)
- Required scopes (for OAuth connections)
- The user will receive a URL to complete the authorization flow
2. Wait for user completion. The user clicks through the OAuth flow, enters API keys, or completes whatever authentication the service requires. The agent does not handle connection inner fields (API keys, custom domains, tokens) — the credential request flow covers all of that.
3. Retrieve the result. Once the user confirms completion, call credential_requests_get to verify the credential request status and obtain the connection ID that was created.
4. Store the connection ID. This ID goes into the module's parameters when configuring the module.
Step 4: Assign to Modules
When configuring each module, place the connection ID in the parameters domain under the field name specified by the module interface (commonly __IMTCONN__, but check the schema — some modules use different field names like account).
Multiple modules using the same app typically share one connection.
Multiple Connection Types Per App
Some apps accept several connection types. Examples:
- Email apps — SMTP, Google (OAuth), Outlook (OAuth)
- AI/LLM apps — OpenAI, Anthropic, Azure OpenAI, and other provider connections
- HTTP modules — various auth methods (no auth, Basic, OAuth, API key header)
When the Extract Blueprint Components output shows multiple connection type options for a module, present them to the user and let them choose. Do not assume which type to use.
Dynamic Connections (Enterprise only): A variable that contains multiple connections, allowing users to choose which connection a module uses at runtime via scenario inputs. Useful for organizations where different team members hold separate credentials for the same service.
Gotchas
- Never create connections directly. Always use credential requests. The agent should never ask the user for API keys, tokens, or passwords directly — the credential request flow handles credential entry securely.
- Scope mismatches cause runtime failures. A connection that authenticates successfully but lacks a required scope will fail with 403/permission errors when the module tries to perform a scoped operation. Always verify scopes match what Extract Blueprint Components specifies.
- One connection per auth context. If the user needs to access different accounts of the same service (e.g., two Google accounts), separate connections are needed.
- Connection field names vary. Don't assume the parameter name is always
__IMTCONN__. Check the module interface for the exact field name. - Connections are team-level resources. They're shared across all scenarios in a team, not scoped to a single scenario.
- Connection type ≠ app name for filtering. When calling
connections_listwith atypefilter, use theaccountNamevalue (e.g.,"google"for Google Sheets/Calendar/Drive,"google-email"for Gmail — not"google-sheets"or"google"-for-Gmail). Gmail (google-email) is a separate connection type from the generic Google connection. Check theaccountNamefield in existing connection objects to determine the correct filter value. - OAuth 2.0 connections may need periodic reauthorization. Official docs note that OAuth 2.0 services grant access for a limited time, requiring periodic reauthorization. Reauthorization failures can stem from browser blocks, expired tokens, or permission changes.
- Editing replaces credentials entirely. When editing a connection, all credentials must be re-provided as Make does not retain original connection data. If the account or auth method has changed, a new connection must be created instead.
- Deleting a connection with webhooks requires deleting webhooks first. If a webhook uses the connection, it must be deleted before the connection can be removed.
- Some apps don't support credential requests. Apps like
ai-tools(Make AI Toolkit) have no module-level credential types — callingcredential_requests_createwill fail with "no modules with credentials". For these apps, the connection must be created by the user directly in the Make scenario designer. Inform the user and provide the scenario URL so they can configure it manually. - Connection `userId` is the bot, not the human. OAuth connection metadata includes a
userIdfield that identifies the authenticated bot or service account — never the human user. Do not use it as a message recipient, file owner, or target identity. Resolve actual user/channel/resource targets via the module's RPCs (see General Principles — Gotchas).
Official Documentation
See also: Keys for cryptographic key provisioning (same credential request flow), General Principles for the full module configuration workflow.
Data Stores
What It Is
A data store is Make's built-in persistent key-value storage. It lets scenarios store, retrieve, update, and delete records across runs. Every data store is backed by a data structure (schema) that defines its fields. Data store IDs are stored in the module's parameters domain.
When It's Needed
- A module performs data store operations (add, get, update, delete, search records)
- The scenario needs persistent state across runs (counters, deduplication, caching, lookup tables)
- The Extract Blueprint Components output indicates modules that need a data store
Type-Less Components
Like data structures, data stores are type-less in Extract Blueprint Components. The tool simply reports that certain modules need a data store, without specifying which one. The agent must determine:
- What data the store should hold (based on the use case)
- Whether modules can share a data store or need separate ones
- Ask the user when the purpose isn't clear from context
Provisioning Workflow
Data stores can be created directly via MCP — no credential requests needed. But a data structure must exist first.
Step 1: Identify Required Data Stores
Call Extract Blueprint Components with the unconfigured blueprint. The output lists which modules need a data store.
Step 2: Ensure Data Structure Exists
Every data store requires a data structure as its schema. Before creating a data store:
- Check if a suitable data structure already exists (
data-structures_list) - Or create a new one via
data-structures_create(see Data Structures)
Step 3: Check Existing or Create New
- Use
data-stores_listto see existing data stores in the team. - Ask the user whether to reuse an existing store or create a new one.
- To create a new data store, call
data-stores_createwith: - A descriptive name
- The data structure ID (from Step 2)
- Storage size
Step 4: Assign to Modules
Place the data store ID in the module's parameters under the field name specified by the module interface.
Gotchas
- Data structure must exist first. Attempting to create a data store without a data structure ID will fail. Always create the structure before the store.
- Type-less means agent decides. Extract Blueprint Components won't tell the agent what the store should contain. Infer from the use case or ask the user.
- Shared data stores. Multiple modules in the same scenario (or across scenarios) may share a data store — e.g., one module writes records and another reads them. Use the same data store ID for both.
- Data stores are team-level resources. They're shared across all scenarios in a team.
- Storage limits. Data stores have a maximum size. Plan sizing based on expected record volume and data types.
Official Documentation
See also: Data Structures for creating the required schema, General Principles for the overall workflow, Connections for the credential request pattern (data stores use direct creation instead).
Data Structures
What It Is
A data structure is a schema definition in Make — it describes the shape of data (field names, types, constraints) that a module, data store, or webhook expects. Data structures define what fields are available for mapping and validation.
When It's Needed
- A module requires structured input or produces structured output (the module interface specifies this)
- Creating a data store (every data store is backed by a data structure)
- Defining the expected payload for a custom webhook
- The Extract Blueprint Components output indicates modules that need a data structure
Type-Less Components
Unlike connections, keys, and webhooks — which have specific types (e.g., "Google OAuth connection", "SSH key", "Slack webhook") — data structures are type-less. Extract Blueprint Components simply reports that certain modules need a data structure, without specifying what kind.
This means the agent must determine:
- What fields the data structure should contain (based on the use case and what data flows through the module)
- Whether modules can share a data structure (if multiple modules need the same schema) or need separate ones
- When in doubt, ask the user to clarify the expected data shape
Provisioning Workflow
Data structures can be created directly via MCP — no credential requests needed.
Step 1: Identify Required Data Structures
Call Extract Blueprint Components with the unconfigured blueprint. The output lists which modules need a data structure.
Step 2: Check Existing or Create New
- Use
data-structures_listto see existing data structures in the team. - Ask the user whether to reuse an existing structure or create a new one.
- To create a new data structure, call
data-structures_createwith the field specification. The tool's input schema describes the exact format for field definitions.
Step 3: Design the Field Specification
When creating a new data structure, define the fields based on the data that will flow through the module. Each field has:
- name — field identifier
- type — the data type (check the
data-structures_createtool's input schema for the full list of supported types) - label — human-readable display name
- required — whether the field must have a value
- default — default value when not provided
Design guidelines:
- Keep field names short and descriptive
- Choose the most specific type available (e.g.,
integerovernumberfor whole numbers,emailovertextfor email addresses) - Mark fields as required only when data integrity demands it
- Use
collectiontype for nested objects andarrayfor lists
Step 4: Assign to Modules
Place the data structure ID in the module's parameters under the field name specified by the module interface.
Priority in Component Creation
Data structures should be created before other components that depend on them:
- Before data stores — every data store requires a data structure as its schema
- Before custom webhooks — if the webhook needs a predefined payload structure
Updating Data Structures
Updating a data structure affects all data stores, webhooks, and modules that use it:
- Adding new optional fields is safe
- Adding required fields without defaults breaks existing records
- Changing field types may cause data loss
- Removing fields hides data from the interface but doesn't delete it
Gotchas
- Type-less means agent decides. Extract Blueprint Components won't tell the agent what fields the structure should have. The agent must infer from the use case or ask the user.
- Shared vs separate. Multiple modules may need a data structure, but that doesn't mean they should share one. A data store and a webhook in the same scenario likely need different structures.
- Data structures are team-level resources. They're shared across scenarios in a team, so naming should be descriptive enough to avoid confusion.
- Schema changes propagate. Modifying a data structure affects every component using it. Plan changes carefully.
Official Documentation
See also: Data Stores for creating data stores (which require a data structure), Webhooks for custom webhook data structures, General Principles for the overall workflow.
{
"name": "LOCAL AGENT",
"flow": [
{
"id": 1,
"module": "gateway:CustomWebHook",
"version": 1,
"parameters": {
"hook": 12421,
"maxResults": 1
},
"mapper": {},
"metadata": {
"designer": {
"x": -415,
"y": 0
},
"restore": {
"parameters": {
"hook": {
"label": "A",
"data": {
"editable": "true"
}
}
}
},
"parameters": [
{
"name": "hook",
"type": "hook:gateway-webhook",
"label": "Webhook",
"required": true
},
{
"name": "maxResults",
"type": "number",
"label": "Maximum number of results"
}
]
}
},
{
"id": 2,
"module": "ai-local-agent:RunLocalAIAgent",
"version": 0,
"parameters": {
"makeConnectionId": 14613
},
"mapper": {
"defaultModel": "gpt-5.4",
"systemPrompt": "You're a versatile Agent",
"message": "{{1.message}}",
"files": [],
"threadId": "",
"modelConfig": {
"recursionLimit": 300,
"iterationsFromHistoryCount": "10"
},
"timeout": "",
"outputType": "text"
},
"metadata": {
"designer": {
"x": -115,
"y": 0
},
"restore": {
"parameters": {
"makeConnectionId": {
"label": "Maiak Token",
"data": {
"scoped": "true",
"connection": "openai-gpt-3"
}
}
},
"expect": {
"defaultModel": {
"mode": "chose",
"nested": [],
"label": "GPT-5.4The frontier model for broad, general-purpose work and coding, delivering higher-quality outputs for complex tasks"
},
"files": {
"mode": "chose"
},
"outputType": {
"label": "Text"
}
}
},
"parameters": [
{
"name": "makeConnectionId",
"type": "account:ai-provider,openai-gpt-3,anthropic-claude,gemini-ai-q9zyjp,ai-agent-foundry-openai,ai-agent-foundry-non-openai,mistral-ai,cohere,groq,ai-agent-xai,amazon-bedrock,ai-agent-openai-compatible",
"label": "Connection",
"required": true
}
],
"expect": [
{
"name": "defaultModel",
"type": "select",
"label": "Model",
"required": true
},
{
"name": "systemPrompt",
"type": "text",
"label": "Instructions"
},
{
"name": "message",
"type": "text",
"label": "Input",
"required": true
},
{
"name": "files",
"type": "array",
"label": "Input files",
"spec": [
{
"name": "fileName",
"type": "filename",
"label": "File name",
"semantic": "file:name"
},
{
"name": "data",
"type": "buffer",
"label": "Data",
"semantic": "file:data"
}
]
},
{
"name": "threadId",
"type": "text",
"label": "Conversation ID"
},
{
"name": "modelConfig",
"type": "collection",
"label": "Model configuration",
"spec": [
{
"name": "tokenLimit",
"type": "hidden",
"label": "Max output tokens"
},
{
"name": "recursionLimit",
"type": "hidden",
"label": "Steps per agent call"
},
{
"name": "iterationsFromHistoryCount",
"type": "number",
"label": "Maximum conversation history"
}
]
},
{
"name": "timeout",
"type": "number",
"label": "Step timeout",
"validate": {
"min": 120,
"max": 600
}
},
{
"name": "outputType",
"type": "select",
"label": "Response format",
"required": true,
"validate": {
"enum": [
"text",
"make-schema"
]
}
}
]
},
"tools": [
{
"name": "Get current weather",
"description": "Returns current weather information for a specified location.",
"flow": [
{
"id": 3,
"module": "weather:ActionGetCurrentWeather",
"version": 1,
"parameters": {},
"mapper": {
"type": "name",
"city": "{{2.city}}"
},
"metadata": {
"designer": {
"x": 185,
"y": 250
},
"restore": {
"expect": {
"type": {
"label": "cities"
},
"city": {
"extra": {
"aiHelp": "Enter e.g. London, UK."
}
}
}
},
"expect": [
{
"name": "type",
"type": "select",
"label": "I want to enter a location by",
"required": true,
"validate": {
"enum": [
"name",
"coords"
]
}
},
{
"name": "city",
"type": "text",
"label": "City",
"required": true
}
]
}
}
]
},
{
"name": "Send a Message",
"description": "Sends a message to a specified channel, thread, or guild member.",
"flow": [
{
"id": 4,
"module": "discord:createMessage",
"version": 2,
"parameters": {
"__IMTCONN__": 11867
},
"mapper": {
"select": "channel",
"content": "{{2.content}}",
"message_reference": {},
"channelId": "1224642245806788641"
},
"metadata": {
"designer": {
"x": 185,
"y": 450
},
"restore": {
"parameters": {
"__IMTCONN__": {
"label": "DomiZ's Discord (Make (team1278341651986911253))",
"data": {
"scoped": "true",
"connection": "discord"
}
}
},
"expect": {
"select": {
"label": "Send a Message to a Channel"
},
"tts": {
"mode": "chose"
},
"embeds": {
"mode": "chose"
},
"sticker_ids": {
"mode": "chose"
},
"components": {
"mode": "chose"
},
"files": {
"mode": "chose"
},
"channelId": {
"mode": "chose",
"label": "general"
}
}
},
"parameters": [
{
"name": "__IMTCONN__",
"type": "account:discord",
"label": "Connection",
"required": true
}
],
"expect": [
{
"name": "select",
"type": "select",
"label": "Choose a Method",
"required": true,
"validate": {
"enum": [
"channel",
"thread",
"user"
]
}
},
{
"name": "content",
"type": "text",
"label": "Message"
},
{
"name": "tts",
"type": "boolean",
"label": "Is TTS message"
},
{
"name": "embeds",
"type": "array",
"label": "Embeds",
"spec": [
{
"name": "title",
"type": "text",
"label": "Title"
},
{
"name": "type",
"type": "text",
"label": "Type"
},
{
"name": "description",
"type": "text",
"label": "Description"
},
{
"name": "url",
"type": "url",
"label": "URL"
},
{
"name": "timestamp",
"type": "date",
"label": "Timestamp"
},
{
"name": "color",
"type": "uinteger",
"label": "Color"
},
{
"name": "footer",
"spec": [
{
"name": "text",
"type": "text",
"label": "Text"
},
{
"name": "icon_url",
"type": "url",
"label": "Icon URL"
},
{
"name": "proxy_icon_url",
"type": "url",
"label": "Proxy Icon URL"
}
],
"type": "collection",
"label": "Footer"
},
{
"name": "image",
"spec": [
{
"name": "url",
"type": "url",
"label": "URL"
},
{
"name": "proxy_url",
"type": "url",
"label": "Proxy URL"
},
{
"name": "height",
"type": "uinteger",
"label": "Height"
},
{
"name": "width",
"type": "uinteger",
"label": "Width"
}
],
"type": "collection",
"label": "Image"
},
{
"name": "thumbnail",
"spec": [
{
"name": "url",
"type": "url",
"label": "URL"
},
{
"name": "proxy_url",
"type": "url",
"label": "Proxy URL"
},
{
"name": "height",
"type": "uinteger",
"label": "Height"
},
{
"name": "width",
"type": "uinteger",
"label": "Width"
}
],
"type": "collection",
"label": "Thumbnail"
},
{
"name": "video",
"spec": [
{
"name": "url",
"type": "url",
"label": "URL"
},
{
"name": "height",
"type": "uinteger",
"label": "Height"
},
{
"name": "width",
"type": "uinteger",
"label": "Width"
}
],
"type": "collection",
"label": "Video"
},
{
"name": "provider",
"spec": [
{
"name": "name",
"type": "text",
"label": "Name"
},
{
"name": "url",
"type": "url",
"label": "URL"
}
],
"type": "collection",
"label": "Provider"
},
{
"name": "author",
"spec": [
{
"name": "name",
"type": "text",
"label": "Name"
},
{
"name": "url",
"type": "url",
"label": "URL"
},
{
"name": "icon_url",
"type": "url",
"label": "Icon URL"
},
{
"name": "proxy_icon_url",
"type": "url",
"label": "Proxy Icon URL"
}
],
"type": "collection",
"label": "Author"
},
{
"name": "fields",
"spec": [
{
"name": "name",
"type": "text",
"label": "Name"
},
{
"name": "value",
"type": "text",
"label": "Value"
},
{
"name": "inline",
"type": "boolean",
"label": "Inline Flag"
}
],
"type": "array",
"label": "Fields"
}
]
},
{
"name": "sticker_ids",
"type": "array",
"label": "Stickers",
"spec": [
{
"name": "id",
"type": "text",
"label": "Sticker ID",
"required": true
}
]
},
{
"name": "components",
"type": "array",
"label": "Components",
"spec": [
{
"name": "type",
"type": "select",
"label": "Component Type",
"options": [
{
"label": "A New Row",
"value": 1,
"nested": [
{
"name": "components",
"spec": [
{
"name": "type",
"type": "select",
"label": "Row Component Type",
"options": [
{
"label": "Button",
"value": 2,
"nested": [
{
"name": "style",
"type": "select",
"label": "Style",
"options": [
{
"label": "Primary (blurple)",
"value": 1,
"nested": [
{
"help": "A developer-defined identifier for the button, max 100 characters.",
"name": "custom_id",
"type": "text",
"label": "Custom ID",
"required": true
}
]
},
{
"label": "Secondary (grey)",
"value": 2,
"nested": [
{
"help": "A developer-defined identifier for the button, max 100 characters.",
"name": "custom_id",
"type": "text",
"label": "Custom ID",
"required": true
}
]
},
{
"label": "Success (green)",
"value": 3,
"nested": [
{
"help": "A developer-defined identifier for the button, max 100 characters.",
"name": "custom_id",
"type": "text",
"label": "Custom ID",
"required": true
}
]
},
{
"label": "Danger (red)",
"value": 4,
"nested": [
{
"help": "A developer-defined identifier for the button, max 100 characters.",
"name": "custom_id",
"type": "text",
"label": "Custom ID",
"required": true
}
]
},
{
"label": "Link (grey)",
"value": 5,
"nested": [
{
"help": "A URL for link-style buttons.",
"name": "url",
"type": "url",
"label": "URL",
"required": true
}
]
}
],
"required": true
},
{
"help": "Text that appears on the button, max 80 characters.",
"name": "label",
"type": "text",
"label": "Label"
},
{
"name": "disabled",
"type": "boolean",
"label": "Disabled"
}
]
},
{
"label": "Select Menu",
"value": 3,
"nested": [
{
"help": "A developer-defined identifier for the button, max 100 characters.",
"name": "custom_id",
"type": "text",
"label": "Custom ID",
"required": true
},
{
"name": "options",
"spec": [
{
"help": "The user-facing name of the option, max 100 characters.",
"name": "label",
"type": "text",
"label": "Label",
"required": true
},
{
"help": "The dev-defined value of the option, max 100 characters.",
"name": "value",
"type": "text",
"label": "Value",
"required": true
},
{
"help": "An additional description of the option, max 100 characters.",
"name": "description",
"type": "text",
"label": "Description"
},
{
"name": "default",
"type": "boolean",
"label": "Default"
}
],
"type": "array",
"label": "Options",
"required": true,
"validate": {
"maxItems": 25
}
},
{
"help": "Custom placeholder text if nothing is selected, max 150 characters.",
"name": "placeholder",
"type": "text",
"label": "Placeholder"
},
{
"help": "The minimum number of items that must be chosen; default 1, min 0, max 2.",
"name": "min_values",
"type": "uinteger",
"label": "Minimum values"
},
{
"help": "The maximum number of items that must be chosen; default 1, max 25.",
"name": "max_values",
"type": "uinteger",
"label": "Maximum values"
},
{
"name": "disabled",
"type": "boolean",
"label": "Disabled"
}
]
}
],
"required": true
}
],
"type": "array",
"label": "Row Components",
"required": true
}
]
}
],
"required": true
}
]
},
{
"name": "files",
"type": "array",
"label": "Files",
"spec": [
{
"name": "filename",
"type": "filename",
"label": "File Name",
"required": true,
"semantic": "file:name"
},
{
"name": "data",
"type": "buffer",
"label": "Data",
"required": true,
"semantic": "file:data"
}
]
},
{
"name": "message_reference",
"type": "collection",
"label": "Message Reference",
"spec": [
{
"name": "message_id",
"type": "text",
"label": "Message ID"
}
]
},
{
"name": "channelId",
"type": "select",
"label": "Channel ID",
"required": true
}
]
}
}
]
}
]
},
{
"id": 5,
"module": "gateway:WebhookRespond",
"version": 1,
"parameters": {},
"mapper": {
"status": "200",
"body": "{{2.response}}",
"headers": []
},
"metadata": {
"designer": {
"x": 185,
"y": 0
},
"restore": {
"expect": {
"headers": {
"mode": "chose"
}
}
},
"expect": [
{
"name": "status",
"type": "uinteger",
"label": "Status",
"validate": {
"min": 100
},
"required": true
},
{
"name": "body",
"type": "any",
"label": "Body"
},
{
"name": "headers",
"type": "array",
"label": "Custom headers",
"validate": {
"maxItems": 16
},
"spec": [
{
"name": "key",
"label": "Key",
"type": "text",
"required": true,
"validate": {
"max": 256
}
},
{
"name": "value",
"label": "Value",
"type": "text",
"required": true,
"validate": {
"max": 4096
}
}
]
}
]
}
}
],
"metadata": {
"instant": true,
"version": 1,
"scenario": {
"roundtrips": 1,
"maxErrors": 3,
"autoCommit": true,
"autoCommitTriggerLast": true,
"sequential": false,
"slots": null,
"confidential": false,
"dataloss": false,
"dlq": false,
"freshVariables": false
},
"designer": {
"orphans": []
},
"zone": "eu1.make.com",
"notes": []
}
}
Filtering
What It Is
A filter is a condition gate on a module that controls whether downstream processing occurs for a given bundle. Filters evaluate conditions against incoming data and either pass or block the bundle.
Placement Rules
- Filters are configured on the target (downstream) module, not on the source.
- Never place a filter on the trigger module — the trigger always fires.
- A filter blocks the entire downstream path from that module, not just the filtered module itself.
- In router patterns, filters go on the first module inside each route, not on the router itself.
Filter Structure
Filters use a JSON structure with a name and a nested conditions array:
{
"filter": {
"name": "Only active users",
"conditions": [[
{"a": "{{1.status}}", "o": "text:equal", "b": "active"}
]]
}
}Each condition has three parts:
- `a` — the left operand (typically an IML expression referencing upstream output)
- `o` — the operator (from the operator reference below)
- `b` — the right operand (comparison value, can also be an IML expression)
Logical Grouping
Conditions are grouped using nested arrays:
AND (all conditions must match)
Put conditions in the same inner array:
"conditions": [[
{"a": "{{1.status}}", "o": "text:equal", "b": "active"},
{"a": "{{1.age}}", "o": "number:greaterorequal", "b": "18"}
]]OR (any group must match)
Put conditions in separate inner arrays:
"conditions": [
[{"a": "{{1.status}}", "o": "text:equal", "b": "active"}],
[{"a": "{{1.role}}", "o": "text:equal", "b": "admin"}]
]Complex (AND + OR)
(A AND B) OR C:
"conditions": [
[
{"a": "{{1.status}}", "o": "text:equal", "b": "active"},
{"a": "{{1.verified}}", "o": "boolean:equal", "b": "true"}
],
[{"a": "{{1.role}}", "o": "text:equal", "b": "admin"}]
]Operator Reference
Basic Operators
| Operator | Label |
|---|---|
exist | Exists |
notexist | Does not exist |
Boolean Operators
| Operator | Label |
|---|---|
boolean:equal | Equal to |
boolean:notequal | Not equal to |
Text Operators
| Operator | Label |
|---|---|
text:equal | Equal to |
text:notequal | Not equal to |
text:contain | Contains |
text:notcontain | Does not contain |
text:startwith | Starts with |
text:notstartwith | Does not start with |
text:endwith | Ends with |
text:notendwith | Does not end with |
text:pattern | Matches pattern (regex) |
text:notpattern | Does not match pattern |
All text operators have case-insensitive variants by appending :ci (e.g., text:equal:ci, text:contain:ci).
Numeric Operators
| Operator | Label |
|---|---|
number:equal | Equal to |
number:notequal | Not equal to |
number:less | Less than |
number:greater | Greater than |
number:lessorequal | Less than or equal to |
number:greaterorequal | Greater than or equal to |
Date Operators
| Operator | Label |
|---|---|
date:equal | Equal to |
date:notequal | Not equal to |
date:less | Earlier than |
date:greater | Later than |
date:lessorequal | Earlier than or equal to |
date:greaterorequal | Later than or equal to |
Time Operators
| Operator | Label |
|---|---|
time:equal | Equal to |
time:notequal | Not equal to |
time:less | Less than |
time:greater | Greater than |
time:lessorequal | Less than or equal to |
time:greaterorequal | Greater than or equal to |
Array Operators
| Operator | Label |
|---|---|
array:contain | Contains |
array:notcontain | Does not contain |
array:equal | Array length equal to |
array:notequal | Array length not equal to |
array:less | Array length less than |
array:greater | Array length greater than |
array:lessorequal | Array length less than or equal to |
array:greaterorequal | Array length greater than or equal to |
Array contain/notcontain also have :ci variants for case-insensitive matching.
IML in Filters
Both a and b operands can use IML expressions:
{"a": "{{addDays(1.date; 7)}}", "o": "date:less", "b": "{{now}}"}This allows dynamic comparisons against computed values, dates, and transformed data.
Router Filters
When using a Router module:
- The router itself has no filter.
- Each route is a separate execution path.
- Place filters on the first module inside each route's flow.
- Routes execute sequentially (top to bottom). Multiple routes can fire for the same bundle (unlike If-Else which is mutually exclusive).
Gotchas
- Choose the right operator type. Using
text:equalon a numeric field ornumber:equalon a text field may produce unexpected results. Match the operator group to the data type. - `exist`/`notexist` have no `b` value. These operators only check whether the field has a value, not what the value is. Only the
aoperand is needed. - Google Sheets internal filters. When filtering within Google Sheets modules (not Make filters), use uppercase column letters:
"a": "G". Lowercase letters and mapped values in theafield will not work. - Filters block the entire downstream path. A filter on module 3 prevents modules 4, 5, 6... from executing for blocked bundles, not just module 3.
Official Documentation
See also: Mapping for building IML references used in filter operands, IML Expressions for the expression language.
General Principles of Module Configuration
What It Is
Module configuration is the process of filling in a module's parameters and mapper so it performs the desired operation. Every module has a defined interface — a set of inputs with types, constraints, and dependencies. Configuring a module means providing the right values in the right domain (parameters or mapper).
The Two Configuration Domains
Understanding the distinction between parameters and mapper is fundamental. Placing a value in the wrong domain causes validation errors.
Parameters (Static Configuration)
Parameters hold values that are fixed at design time and cannot change during scenario execution:
- Component references — connection IDs, key IDs, webhook IDs, data store IDs, data structure IDs
- Mode selectors and dropdowns — operation type, HTTP method, output format
- Resource identifiers — selected from RPC-loaded lists (spreadsheet ID, folder ID, channel ID)
- Fixed settings — batch size, timeout, encoding
Parameters are baked into the module when the scenario is saved. They do not support IML expressions.
Mapper (Dynamic Configuration)
The mapper holds values that are evaluated at runtime using IML (Inline Mapping Language):
- References to upstream module outputs —
{{1.email}},{{3.items[1].name}} - Transformations —
{{upper(1.name)}},{{formatDate(1.created_at; "YYYY-MM-DD")}} - Conditional logic —
{{if(1.status = "active"; "Yes"; "No")}} - Literal values that could also be mapped — static text in a mapper field is valid, but IML expressions are the primary use case
The mapper is where data flows between modules. See IML Expressions for the full expression language and Mapping for mapping patterns.
Common mistake: Putting static dropdown selections in the mapper instead of parameters → causes "Field mandatory" + "Unknown field" errors.
The Configuration Workflow
Follow this sequence for every module:
Phase 1: Read the Module Interface
Call app-module_get with the instructions output format. Pass:
- appName — exact app name
- appVersion — exact version number
- moduleName — exact module slug
The instructions format returns everything needed to configure the module:
- Input schema — all parameters and mapper fields with types, required/optional status, allowed values
- Output schema — what data the module produces (needed for downstream mapping)
- Component instructions — which connections, keys, webhooks, data stores, or data structures the module needs
- RPC instructions — which RPCs to call to load dynamic field options, and what data to pass
Study the interface before setting any values. Never guess parameter names or structures.
Phase 2: Resolve Components (Authentication & Resources)
Before filling parameters, ensure all required components exist. The module interface specifies which components are needed.
Priority order: Data structures first (other components may depend on them) → webhooks → connections → keys → data stores.
Two creation paths:
| Component | Creation method | Why |
|---|---|---|
| Connections | Credential requests | Cannot create directly — involves OAuth flows or sensitive credential entry that the user must complete |
| Keys | Credential requests | Same reason — cryptographic material must be provided by the user |
| Webhooks | Direct MCP creation | Can be created programmatically via hooks_create |
| Data stores | Direct MCP creation | Can be created programmatically via data-stores_create (requires data structure ID) |
| Data structures | Direct MCP creation | Can be created programmatically via data-structures_create |
For connections and keys: create a credential request, provide the user with the authorization URL, wait for them to complete the flow, then retrieve the resulting component ID.
Store all component IDs — they go into the parameters domain.
Phase 3: Load Dynamic Field Options (RPCs)
Some parameters have values that must be fetched dynamically — e.g., a list of Google spreadsheets, Slack channels, or database tables. The module interface (from Phase 1) specifies which RPCs to call.
Call rpc_execute with:
- appName and appVersion — same as the module
- rpcName — the exact RPC name from the interface instructions
- data — context object; at minimum include the connection or key ID (e.g.,
{"__IMTCONN__": <id>}). Never omit the data parameter — it causes schema validation errors.
RPC responses return a list of options. If the list is very large (500+ items), ask the user for a specific name or search keywords rather than presenting the full list.
If an RPC fails with a 403 or permission error, the connection may lack required OAuth scopes. The user needs to create a new connection with the correct permissions.
Phase 4: Fill Parameters and Mapper
Work through the fields systematically:
1. Parameters first: Set component references (connection ID, webhook ID, etc.), dropdown selections, and RPC-selected resource IDs. 2. Mapper second: Set dynamic values using IML expressions referencing upstream module outputs. Use literal values only where no upstream data is needed.
The module interface schema tells which fields belong in parameters vs mapper. Follow it exactly.
Phase 5: Validate
Call validate_module_configuration with the assembled parameters and mapper. Always validate — no exceptions. This catches:
- Missing required fields
- Type mismatches
- Invalid select/dropdown values
- Structural errors in nested collections/arrays
- Incorrect parameter placement (parameters vs mapper)
Fix all reported errors before proceeding to the next module.
Example — catching a missing `valueInputOption` in Google Sheets `addRow`:
If validate_module_configuration is called for google-sheets:addRow with a mapper that omits valueInputOption:
// Incomplete mapper — missing valueInputOption
{
"appName": "google-sheets",
"appVersion": 2,
"moduleName": "addRow",
"parameters": { "__IMTCONN__": 12345, "mode": "select", "spreadsheetId": "/1abc..." },
"mapper": { "values": { "0": "{{1.name}}" } }
}Validation returns an error like "valueInputOption" is required. Corrected mapper:
"mapper": {
"valueInputOption": "USER_ENTERED",
"values": { "0": "{{1.name}}" }
}Re-validate after fixing — this error would otherwise appear only at runtime as 400: INVALID_ARGUMENT.
Configuration Order Across Modules
Configure modules left to right (upstream to downstream) so that output schemas are available for downstream mapping.
Exception — Array Aggregator: The built-in array aggregator aggregates into the data structure of the module after it. Workflow: 1. Configure the target module (after the aggregator) to the extent possible without aggregated data 2. Configure the aggregator (which references the target's structure) 3. Return to finish the target module's mapping
Parameter Types Reference
| Type | Description | Example value |
|---|---|---|
| text | Free-form string | "Hello world" |
| number | Numeric value (integer or float) | 42, 3.14 |
| integer | Whole number only | 10 |
| uinteger | Unsigned (non-negative) integer | 0, 100 |
| boolean | True or false | true |
| date | ISO 8601 date/datetime | "2026-03-17T10:00:00Z" |
| select | One value from a fixed set | "GET" from ["GET","POST","PUT","DELETE"] |
| collection | Nested object with named fields | {"name": "John", "age": 30} |
| array | Ordered list of items | [{"id": 1}, {"id": 2}] |
| buffer | Binary/file data | File reference from upstream module |
| url | URL string | "https://example.com" |
| Email address | "user@example.com" | |
| uuid | UUID string | "550e8400-e29b-41d4-a716-446655440000" |
Nested Parameters (Collections and Arrays)
Many modules have nested parameter structures:
- Collection parameters contain sub-parameters, each with its own type and required/optional status. Treat them like a mini-module interface.
- Array parameters contain a template item (usually a collection). Each array element follows the same structure.
- Nesting can be multiple levels deep. Walk the tree carefully.
Example: An HTTP module's headers parameter might be an array of collections, where each collection has name (text, required) and value (text, required).
Gotchas
- Always use instructions format. Calling
app-module_getwithout requesting the instructions format gives less useful output. The instructions format includes component requirements and RPC instructions. - Do not guess parameter names or values. Parameter names are exact slugs, not human-readable labels. Always retrieve the interface first.
- Select parameters are strict. Passing a value not in the allowed set causes a validation error. Check the spec for allowed values.
- Empty vs null vs missing. Some modules treat these differently. An empty string
""is not the same as omitting a parameter. When in doubt, omit optional parameters rather than sending empty values. The same rule applies — even more dangerously — to mapper fields on update and upsert modules, where an empty-string mapping silently overwrites existing record data. See Mapping → Field Omission on Updates and Upserts. - Dynamic/conditional parameters. Some modules have parameters that change based on other parameter values (e.g., selecting an action type reveals action-specific fields). The module interface describes these dependencies — set the controlling parameter first.
- Module version matters. Different app versions may have different parameter sets. Always use the version from
app-modules_list. - Never omit RPC data parameter. Even if only the connection ID is needed, always pass it. Omitting causes schema validation errors.
- Always resolve targets via RPC. When a module targets a specific user, channel, folder, or resource, resolve the target ID by calling the module's RPC — do not use connection metadata. The
userIdin OAuth connection metadata identifies the authenticated bot or app, not the intended recipient. Examples: - Slack DM: Use the
IMChannelsRPC to find the target user's DM channel ID. Do not use the connection'suserId(that's the bot). - Google Sheets: Use the spreadsheets RPC to resolve a spreadsheet ID by name.
- Any "channel", "recipient", "user", or "folder" field: Always resolve via the module's RPC, never hardcode or pull from connection metadata.
- Boolean select gotcha. Some parameters look like on/off dropdowns but use actual boolean
constvalues in their schema ({"const": true}/{"const": false}), not strings. If you pass"true"or"false"(strings) where the schema requirestrueorfalse(booleans), Make will silently accept the value but behave incorrectly. Always inspect theoneOfschema fromapp-module_getto confirm the expected type before setting the value.
Connection Restore Metadata
When a blueprint sets a connection parameter (e.g., "account": 13911586), Make's UI will show it as "not selected" unless the module's metadata.restore.parameters includes the connection's label and accountName. This is a display-only issue — the connection ID is correct — but it looks broken to users and can cause confusion.
Required pattern for every connection-bearing module:
"metadata": {
"restore": {
"parameters": {
"<connectionFieldName>": {
"label": "<connection name from connections_list>",
"data": {
"scoped": "true",
"connection": "<accountName from connections_list>"
}
}
}
}
}How to fill it:
1. Call connections_list before assembling the module's metadata. 2. Find the connection the user selected. 3. Use name → label, accountName → data.connection.
Example (Microsoft SMTP/IMAP email connection):
"metadata": {
"restore": {
"parameters": {
"account": {
"label": "Hotmail - Send Email",
"data": {
"scoped": "true",
"connection": "microsoft-smtp-imap"
}
}
}
}
}Rule: For every module that has a connection parameter (account, __IMTCONN__, makeConnectionId, or similar), always include the corresponding restore metadata. Missing restore metadata causes the UI to show the connection as unset even when the scenario runs correctly.
Official Documentation
See also: Mapping for connecting data between modules, IML Expressions for the formula language, Connections for the credential request flow, Filtering for filter conditions on modules.
IML Expressions
What It Is
IML (Inline Mapping Language) is Make's expression language used inside the mapper domain. It evaluates at runtime to produce dynamic values from upstream module outputs, built-in variables, and transformation functions.
All IML expressions are wrapped in double curly braces: {{expression}}.
Syntax Rules
Variables (Module References)
Reference upstream module outputs by module ID and field path:
{{1.email}}— fieldemailfrom module 1{{3.data.name}}— nested field access{{5.items[1].title}}— array indexing (1-based)
Full Bundle Reference
To reference the entire output bundle of a module (not a specific field), wrap the module ID in backticks:
{{\1\}}— the full output bundle of module 1{{\3\}}— the full output bundle of module 3
A bare numeric ID without backticks will NOT work. {{1}} does not parse correctly in IML — it must be {{\1\}}. This is required whenever a field expects the complete module output as a single object (e.g., passing an entire bundle to a JSON stringify, a Set Variable module, or an HTTP body).
Backtick Rule
When a field name contains spaces, special characters, or starts with a number, wrap that segment in backticks:
{{1.\Customer Name\}}— field with space{{1.\__IMTCONN__\}}— system variable{{1.data.\user address\.city}}— only the segment with spaces needs backticks{{\1\}}— full bundle reference (the module ID itself is a number, so it needs backticks)
Array Indexing
1-based indexing. First item is index 1, never 0.
{{3.output[1]}}— first item{{3.output[2].content[1].text}}— nested array access
Function Argument Separator
IML uses semicolons (;) to separate function arguments, not commas:
{{if(1.status = "active"; "Yes"; "No")}}{{formatDate(now; "YYYY-MM-DD")}}
`formatNumber` gotcha: The default decimal separator is a comma and the default thousands separator is a period (opposite of many locales). Example: formatNumber(123456789; 3; ,; .) = 123.456.789,000. Always specify separators explicitly to avoid confusion.
Operators
| Operator | Meaning |
|---|---|
= | Equal to |
!= | Not equal to |
< | Less than |
> | Greater than |
<= | Less than or equal to |
>= | Greater than or equal to |
& | Logical AND |
| `\ | ` |
+ | Addition |
- | Subtraction |
* | Multiplication |
/ | Division |
% | Modulo |
Logical operator usage
Use the symbols & (AND) and | (OR) — never the words AND / OR, which are not valid IML and will fail to parse.
Example 1 — combining bundle fields:
- Correct:
{{1.something & 2.somethingElse | 3.aaa}} - Wrong:
{{1.something AND 2.somethingElse OR 3.aaa}}
Example 2 — inside a conditional:
- Correct:
{{if(1.status = "active" & 1.role = "admin"; "activeAdmin"; "someoneElse")}} - Wrong:
{{if(1.status = "active" AND 1.role = "admin"; "activeAdmin"; "someoneElse")}}
Functions Reference
Only use functions documented here. Never use a function, variable, operator, or keyword not listed in this reference.
General Functions
| Function | Description |
|---|---|
if(expr; value1; value2) | Returns value1 if expr is true, otherwise value2 |
ifempty(value1; value2) | Returns value1 if not empty, otherwise value2 |
switch(expr; v1; r1; ...; else) | Matches expr against values, returns corresponding result |
get(object; path) | Returns value at path in object/array. Use only when path is variable, not for static access |
pick(object; key1; key2; ...) | Returns object with only the specified keys |
omit(object; key1; key2; ...) | Returns object without the specified keys |
equal(value; value) | Compares two values for equality |
String Functions
| Function | Description |
|---|---|
length(text) | Number of characters |
lower(text) | Lowercase |
upper(text) | Uppercase |
capitalize(text) | First character uppercase |
startcase(text) | Capitalize every word, lowercase rest |
trim(text) | Remove leading/trailing whitespace |
replace(text; search; replacement) | Replace occurrences |
substring(text; start; end) | Extract portion (0-based start index) |
split(text; separator) | Split into array |
indexOf(string; value; [start]) | Position of first occurrence (-1 if not found) |
contains(text; search) | Check if text contains search string |
toString(value) | Convert any value to string |
stripHTML(text) | Remove HTML tags |
escapeHTML(text) | Escape HTML tags |
| encodeURL(text) | URL-encode special characters | | decodeURL(text) | Decode URL-encoded text | | ascii(text; [removeDiacritics]) | Remove non-ASCII characters | | base64(text) | Encode to base64 | | toBinary(value) | Convert to binary data | | md5(text) | MD5 hash | | sha1(text; [encoding]; [key]; [keyEncoding]) | SHA1 hash (HMAC with key) | | sha256(text; [encoding]; [key]; [keyEncoding]) | SHA256 hash (HMAC with key) | | sha512(text; [encoding]; [key]; [keyEncoding]) | SHA512 hash (HMAC with key) | | replaceEmojiCharacters(text; replacement) | Replace emoji characters |
Date Functions
| Function | Description |
|---|---|
formatDate(date; format; [timezone]) | Format date as string |
parseDate(text; format; [timezone]) | Parse string to date |
addDays(date; number) | Add/subtract days |
addHours(date; number) | Add/subtract hours |
addMinutes(date; number) | Add/subtract minutes |
addSeconds(date; number) | Add/subtract seconds |
addMonths(date; number) | Add/subtract months |
addYears(date; number) | Add/subtract years |
setDate(date; number) | Set day of month |
setDay(date; number/name) | Set day of week (Sunday=1, Saturday=7, or English name e.g. monday) |
setMonth(date; number/name) | Set month |
setYear(date; number) | Set year |
setHour(date; number) | Set hour (0-23) |
setMinute(date; number) | Set minute (0-59) |
setSecond(date; number) | Set second (0-59) |
Values outside valid ranges adjust adjacent units (e.g., setting seconds to 70 adds a minute).
ISO 8601 datetime: Use a single formatDate() call with format "YYYY-MM-DDTHH:mm:ssZ". The T is a literal separator within the format string, Z outputs the timezone offset. Never concatenate separate dynamic date and time expressions into a full datetime. (Exception: combining a date-only formatDate result with a fixed literal time like T00:00:00Z for day boundaries is valid — see Common Errors.)
Math Functions
| Function | Description |
|---|---|
round(number) | Round to nearest integer |
ceil(number) | Round up |
floor(number) | Round down |
trunc(number; [decimals]) | Truncate to integer or decimal places |
abs(number) | Absolute value |
min(values) | Smallest value |
max(values) | Largest value |
sum(values) | Sum of values |
average(values) | Average of values |
median(values) | Median of values |
parseNumber(text; [decimalSeparator]) | Parse string to number |
formatNumber(number; decimals; [decSep]; [thousSep]) | Format number as string |
stdevS(values) | Sample standard deviation |
stdevP(values) | Population standard deviation |
Array Functions
| Function | Description |
|---|---|
length(array) | Number of items |
first(array) | First element |
last(array) | Last element |
map(array; key; [filterKey]; [filterValues]) | Extract values by key from array of objects (case-sensitive, use raw names) |
join(array; separator) | Concatenate into string |
contains(array; value) | Check if array contains value |
add(array; value1; value2; ...) | Add values to array |
remove(array; value1; value2; ...) | Remove values from array (primitive arrays only) |
sort(array; [order]; [key]) | Sort array (asc/desc/asc ci/desc ci) |
reverse(array) | Reverse order |
shuffle(array) | Random order |
merge(array1; array2; ...) | Merge arrays into one |
slice(array; start; [end]) | Extract portion (0-based indexing) |
flatten(array; [depth]) | Flatten nested arrays |
distinct(array; [key]) | Remove duplicates |
deduplicate(array) | Remove duplicates (primitive arrays) |
keys(object) | Get keys of object as array |
toArray(collection) | Convert collection to array of key-value pairs |
toCollection(array; keyField; valueField) | Convert key-value array to collection |
Variables
| Variable | Description |
|---|---|
now | Current date and time |
timestamp | Unix timestamp (seconds since epoch) |
pi | Mathematical constant π |
random | Random float between 0 (inclusive) and 1 (exclusive) |
uuid | RFC 4122 v4 unique identifier |
executionId | Unique ID of the current execution |
Keywords
| Keyword | Description |
|---|---|
null | Null (empty) value |
true | Boolean true |
false | Boolean false |
emptystring | Empty text |
emptyarray | Empty array |
space | Space character |
tab | Tab character |
newline | New line character |
nbsp | Non-breaking space |
carriagereturn | Carriage return |
ignore | Instructs engine to act as if field is empty |
erase | Sets field to empty value (empty array for array fields) |
Limitations
- No inline JSON. IML does not support inline JSON syntax like
{key: value}within expressions. - No `set()` function. IML cannot set a value at a specific path in an object.
Common Errors
- Smart quotes. Use straight quotes
"not curly quotes". Smart quotes cause mapping failures. - Index 0. Array indexing is 1-based.
{{1.items[0]}}does not work — use{{1.items[1]}}. - Commas instead of semicolons. Function arguments use
;not,. - Mapping arrays to single-value fields. If a field expects a single value but receives an array, use
first(),last(), or index the specific item. - JSON in text fields. To put a JSON object into a text field, use
{{toString(1.json)}}. - DateTime concatenation. Never concatenate date and time parts with
&,+, or literalToutside a format string. Use a single{{formatDate(date; "YYYY-MM-DDTHH:mm:ssZ")}}. - Non-existent date boundary functions. IML does not have
endOfDay(),startOfDay(),beginningOfDay(), or similar boundary functions. These produce "Unknown function" errors. To get day boundaries, useformatDateto extract the date portion and append a literal time: start of day{{formatDate(now; "YYYY-MM-DD")}}T00:00:00Z, end of day{{formatDate(now; "YYYY-MM-DD")}}T23:59:59Z. This is the one valid case of combining aformatDateresult with literal text — the "DateTime concatenation" rule above applies to building full datetimes from separate dynamic parts. - Google Sheets column references. Use 0-based numeric indices wrapped in backticks:
{{1.\0\}} (column A), {{1.\1\}} (column B), {{1.\2\}} (column C). This is the format the Make UI generates — do not use 1-based indices, bare numbers, or header names. The row number and sheet metadata are available as {{1.__ROW_NUMBER__}}, {{1.__SHEET__}}, and {{1.__SPREADSHEET_ID__}}. If the sheet has column headers (includesHeaders: true), the trigger output also includes named fields using the header text (e.g., {{1.email}}), but these are fragile — prefer the numeric index form for reliability.
Official Documentation
- Use Functions
- General Functions
- Math Functions
- Text and Binary Functions
- Date and Time Functions
- Array Functions
See also: Mapping for the mapper domain and how to discover upstream outputs, Filtering for using IML in filter conditions.
Keys
What It Is
Keys (also called keychains) are Make's way of managing cryptographic material — SSH keys, certificates, PEM/PFX files, and similar credentials that modules need for secure operations like JWT signing, SSH connections, or certificate-based authentication. Like connections, key IDs are stored in the module's parameters domain.
When It's Needed
- Modules that perform cryptographic operations (signing, encryption, decryption, SSH)
- Modules that require certificate-based authentication
- The module interface (from
app-module_getwith instructions format) specifies when a key is required and what type
Provisioning Workflow
Keys follow the same credential request flow as connections:
1. Extract Blueprint Components — returns required keys alongside connections. The output specifies the key type (keychain type) needed for each module.
2. Check existing keys — use keys_list to see if a compatible key already exists in the user's team.
3. Ask the user — present the existing compatible keys (if any) and ask whether to reuse one or create a new key.
4. Create via credential request if needed — use credential_requests_create for the key type. The user uploads or enters the cryptographic material through the secure credential request flow. The agent never handles raw key data directly.
5. Retrieve the key ID — after user completion, call credential_requests_get to obtain the key ID.
6. Assign to modules — place the key ID in the module's parameters under the field name from the module interface (commonly __IMTKEY__, but check the schema).
Key Types
Make supports multiple keychain types. The specific types available depend on the modules in the scenario. The Extract Blueprint Components tool output tells exactly which keychain type is needed — do not guess or hardcode types.
Common use cases:
- Encryptor app — AES Encrypt/Decrypt advanced, Create digital signature, PGP Encrypt/Decrypt
- SSH app — private keys for connections
- HTTP app — keychains for API key and Basic Auth
Key insertion methods: Direct Insert (copy-paste) or Extract from File (P12, PFX, PEM formats). OpenSSH format private keys must be converted to PEM format first (ssh-keygen -p -m PEM -f <path>).
Gotchas
- Never handle raw key data. The credential request flow handles all key material securely. The agent should never ask the user to paste private keys, certificates, or other sensitive cryptographic material into the conversation.
- Key field names vary. Don't assume the parameter name is always
__IMTKEY__. Check the module interface for the exact field name. - Keys are team-level resources. Like connections, they're shared across scenarios in a team.
Official Documentation
See also: Connections for the full credential request flow (same pattern), General Principles for the overall module configuration workflow.
Mapping
What It Is
Mapping is how data flows between modules in a Make scenario. The mapper domain of a module's configuration holds dynamic values — IML expressions that reference upstream module outputs, apply transformations, and build the input data the module needs at runtime.
The Mapper vs Parameters
- Mapper: dynamic values evaluated at runtime. References to upstream modules (
{{1.email}}), IML functions, conditional logic. - Parameters: static values baked in at design time. Connection IDs, dropdown selections, resource IDs.
Placing a value in the wrong domain causes validation errors. The module interface (from app-module_get with instructions format) specifies which fields belong in the mapper.
Module ID References
Mapper values reference upstream modules by their blueprint-assigned module ID — a unique numeric identifier in the blueprint.
Syntax: {{moduleId.fieldName}}
{{1.email}}— theemailfield from module with ID 1{{3.items[1].name}}— the first item'snamein theitemsarray from module 3{{5.status}}— thestatusfield from module 5
Module IDs are not sequential positions — they are the id property assigned to each module in the blueprint. When generating a new blueprint, assign unique numeric IDs (no duplicates). When editing an existing blueprint, use the IDs already present.
Full Bundle Reference
To reference the entire output bundle of a module (not a specific field), wrap the module ID in backticks: {{\1\}}.
A bare {{1}} without backticks will not work — IML cannot parse a bare numeric ID. The backtick rule applies because the module ID is a number. Use this when a field expects the complete module output as a single object (e.g., passing an entire bundle to JSON stringify, Set Variable, or an HTTP request body).
Discovering Upstream Module Outputs
To know what fields are available for mapping, the agent must learn the output schema of every upstream module. This is done module by module, left to right, as part of the configuration process.
Static Output Schemas
When app-module_get returns a static output schema (no RPCs), the available fields are directly readable. Use them as mapping targets in downstream modules.
Dynamic Output Schemas (RPCs)
Some modules have outputs that depend on their configuration — for example:
- A Google Sheets module's output columns depend on which spreadsheet/sheet is selected
- A data store module's output fields depend on the data structure attached to the store
- A form retrieval module's output depends on which form is selected
In these cases, the output schema from app-module_get contains RPC references instead of static field definitions. To resolve them:
1. Merge the module's parameters and mapper into a single flat data object. 2. Execute the output RPC via rpc_execute, passing the merged data. The RPC uses the module's configured inputs (e.g., the selected spreadsheet ID) to determine what the output will be. 3. The RPC returns the dynamic output schema — the actual fields the module will produce at runtime.
Important: This is why modules must be configured left to right. The output schema of a module depends on its configuration, and downstream modules need that schema to set up their mapping.
Memorize Module Outputs
Track every module's resolved output schema as configuration progresses. Downstream modules may reference any upstream module's output — not just the immediately preceding one.
Building the Mapper Object
The mapper is a JSON object where keys are the target module's input field names and values are IML expressions:
{
"name": "{{1.firstName}} {{1.lastName}}",
"email": "{{1.email}}",
"status": "active",
"created": "{{formatDate(now; \"YYYY-MM-DD\")}}"
}- Mapped values: IML expressions referencing upstream outputs (
{{1.email}}) - Static values: Literal strings, numbers, booleans (valid in mapper when no upstream data is needed)
- Transformed values: IML functions applied to upstream data (
{{upper(1.name)}}) - Nested structures: Collections and arrays following the module's input schema
See IML Expressions for the full expression language.
Field Omission on Updates and Upserts
On update, upsert, and patch modules — anything that modifies an existing record rather than creating one — omit the key entirely from `mapper` for any field you don't intend to write. Do not include the key with an empty string "".
Why this matters:
- An empty-string mapping writes
""to the target field, overwriting whatever value was there. A missing key tells Make "leave this field alone." - Make's visual editor renders an empty-string mapping identically to a field with no mapping at all — both look like a blank input box. A user reviewing the scenario in the UI has no way to see that the mapper is silently zeroing out a field.
validate_module_configurationaccepts""as a valid value, so the validator will not flag this. The damage only shows up at runtime, on real records.
Wrong — sending an empty string for phone on an update will erase the contact's phone number:
{
"recordId": "{{1.id}}",
"name": "{{1.firstName}} {{1.lastName}}",
"email": "{{1.email}}",
"phone": ""
}Right — omit phone entirely; the record's existing phone stays untouched:
{
"recordId": "{{1.id}}",
"name": "{{1.firstName}} {{1.lastName}}",
"email": "{{1.email}}"
}When the intent really is to clear a field, prefer the explicit IML erase keyword over "" (see IML Expressions — erase is listed there alongside null and ignore). It documents the intent in the blueprint and won't be confused for an accidental blank.
Create modules are usually safe — most APIs treat "" and "absent" as equivalent on insert — but the omission habit is worth keeping uniform across both, since the same blueprint shape often gets edited later into an update.
Gotchas
- Module IDs must be unique. When generating blueprints, never assign the same ID to two modules. When editing existing blueprints, preserve existing IDs.
- Output RPCs need merged data. When executing output schema RPCs, always pass the merged parameters + mapper as the data object. Without this, the RPC can't determine the dynamic output.
- Static values in mapper are valid but uncommon. If a field always has the same value and doesn't need upstream data, it can still go in the mapper — but check whether it belongs in parameters instead.
- Array indexing is 1-based.
{{1.items[1]}}is the first item, not{{1.items[0]}}. Using index 0 will not work. - Backtick rule for special field names. If a field name contains spaces or special characters, wrap it in backticks:
{{1.\Customer Name\}}. See IML Expressions for details.
Official Documentation
See also: IML Expressions for the formula language, Filtering for conditions that use mapped values, General Principles for the full configuration workflow.
Webhooks
What It Is
A webhook is an HTTP endpoint that triggers a Make scenario when it receives a request. Webhooks enable instant, event-driven execution — the scenario runs immediately when data arrives, rather than polling on a schedule. Webhook IDs are stored in the module's parameters domain.
When It's Needed
- The scenario uses an instant trigger (webhook-based trigger module)
- An external system needs to push data into Make in real-time
- The module interface (from
app-module_getwith instructions format) specifies a hook component is required
Two Types of Webhooks
Branded Webhooks (App-Specific Instant Triggers)
These are built into specific apps and labeled "INSTANT" in module lists (e.g., Slack - Watch Events, Stripe - Watch Events). Their output structure is known — it's defined by the instant trigger module itself. The webhook is automatically configured with the app's API when the connection is established.
Custom Webhooks (Gateway Package)
The generic Webhooks > Custom webhook module accepts any HTTP payload. Custom webhooks offer more flexibility:
- With data structure: Define a data structure upfront that describes the expected payload. This makes the output fields available for mapping in downstream modules.
- Without data structure: Send any data to the webhook endpoint. The structure can be learned from the first request — Make detects the fields automatically. This is useful when the payload format isn't known in advance.
Provisioning Workflow
Webhooks are returned by Extract Blueprint Components alongside connections and keys. Unlike connections and keys, webhooks can be created directly via MCP tools.
Step 1: Identify Required Webhooks
Call Extract Blueprint Components with the unconfigured blueprint. The output specifies which modules need webhooks and what type.
Step 2: Create New Webhook
Always create a new webhook — do not reuse existing ones. Use hooks_create with the parameters specified by Extract Blueprint Components (webhook type, associated app).
Custom Webhook Creation — Required Parameters
When creating a custom webhook (typeName: "gateway-webhook"), the data object must include all three of these fields or the API returns a validation error for each missing one:
{
"name": "My Webhook",
"teamId": <teamId>,
"typeName": "gateway-webhook",
"data": {
"headers": true,
"method": "any",
"stringify": false
}
}| Field | Required | Values | Effect |
|---|---|---|---|
headers | yes | true / false | Whether to include request headers in the output |
method | yes | "any" / "GET" / "POST" / etc. | HTTP method(s) accepted by the webhook |
stringify | yes | true / false | Whether to return the body as a raw string instead of parsed |
Omitting any of these three fields causes the hooks_create call to fail with a validation error listing all missing fields. Always include them explicitly.
Step 3: Define Data Structure (Custom Webhooks)
For custom webhooks, optionally define a data structure for the expected payload:
1. Create the data structure via data-structures_create with the expected fields. 2. Associate it with the webhook during creation or update.
If skipping the data structure definition, the webhook will learn its output from the first incoming request.
Step 4: Assign to Modules
Place the webhook (hook) ID in the module's parameters under the field name specified by the module interface (commonly __IMTHOOK__, but check the schema).
Gotchas
- Webhook URL is unique and acts as authentication. Do not expose it publicly without additional validation logic in the scenario.
- Custom webhook output depends on data structure. Without a defined data structure and before any request has been received, the webhook's output fields are unknown — downstream modules cannot map from them until the structure is learned.
- One webhook per trigger module. Each webhook trigger module needs its own webhook instance.
- Branded webhooks need connections first. App-specific instant triggers require the app's connection to be set up before the webhook can be created, because the webhook registration happens through the app's API.
- Hook field names vary. Don't assume the parameter name is always
__IMTHOOK__. Check the module interface for the exact field name.
Official Documentation
See also: Connections for provisioning connections (needed before branded webhooks), Data Structures for defining payload schemas, General Principles for the overall workflow.