Loading tutorials…
Loading tutorials…
When a built-in action cannot do what you need, Code by Zapier is the escape hatch. Run Python or JavaScript inline. Handy, dangerous if abused, and the source of about 30% of advanced-Zap breakages we see.
Who this is forOperators who hit the limits of built-in Zapier actions and need custom logic — string parsing, date math, signature verification, calling an unsupported API. Comfortable with basic Python or JS.
What you'll need
Step 1
If your script is 5 lines of parsing or formatting, Code by Zapier is fine. If it is 50+ lines, calls external APIs, or needs version control, host it elsewhere (Cloudflare Worker, AWS Lambda) and call it via a webhook.
Code by Zapier runs untracked in the cloud. There is no version control, no diff, no rollback. Every edit is immediate and permanent.
Acceptable use cases: string parsing, date math, JSON reshaping, simple calculations, calling a single API endpoint with no retry logic.
Bad use cases: any script over 30 lines, anything that needs unit tests, anything that handles money or PII, anything that calls 3+ external services.
For bad-use cases, host the logic on Cloudflare Workers, AWS Lambda, or a small Node/Python server. Have Zapier POST to it via Webhooks. You get source control, retries, observability — and Zapier orchestrates instead of executing.
Rule of thumb: if you would not paste this code into Slack and expect a colleague to maintain it, do not put it in Code by Zapier.
Step 2
In the Zap editor, click "+" → search "Code by Zapier" → choose "Run Python" or "Run Javascript." Both are equivalent for most use cases.
Click "+" between two steps in your Zap.
Search for "Code by Zapier" → choose either "Run Python" or "Run Javascript."
Both run in a sandboxed environment. Python uses Python 3.10. JavaScript uses Node 18 (no npm, no imports beyond built-ins).
Pick the language you know better. Python has cleaner string/date handling. JavaScript has cleaner async if you call APIs.
Configure "Input Data" — this is how you pass trigger/upstream-action fields into your script. Each key/value becomes a variable accessible in the script.
Step 3
In the script editor, parse inputs first, do the work, return outputs as a dict (Python) or object (JS). Always handle missing fields explicitly.
Python pattern: `email = input_data.get("email", "").strip().lower()` — never `input_data["email"]`, which crashes on missing key.
JS pattern: `const email = (inputData.email || "").trim().toLowerCase()` — same defensive style.
Do the work. Keep it short.
Return a dict (Python) or object (JS) at the end. Python: `output = {"email_normalized": email, "is_valid": "@" in email}`. JS: `output = { email_normalized: email, is_valid: email.includes("@") }`
Every key in `output` becomes an available field for downstream steps. Use clear names — `email_normalized` not `e1`.
Step 4
Wrap risky operations (API calls, regex, date parsing) in try/except (Python) or try/catch (JS). Set fallback values. Never let an uncaught exception kill the Zap.
A common pattern: parse a date string from a trigger field. The trigger field can be empty, malformed, or in an unexpected format. Without try/except, the Zap crashes.
Python: `try: parsed = datetime.fromisoformat(input_data["date"]); valid = True\nexcept Exception: parsed = None; valid = False`
JS: `let parsed = null, valid = false; try { parsed = new Date(inputData.date); valid = !isNaN(parsed.getTime()) } catch (e) { valid = false }`
Return both the parsed value and a `valid` flag. Downstream steps can filter on `valid` to skip malformed inputs without halting the whole Zap.
For API calls (requests in Python, fetch in JS): always wrap. Always set a timeout. Always check status code before reading body.
Step 5
Click "Test step." Zapier runs the script with the input data from upstream steps. Inspect the output carefully — silent type coercion is common.
Click "Test step." Zapier executes your script with the current sample data.
Inspect the output. Verify every returned key is the expected type — strings should be strings, numbers should be numbers, booleans should be booleans (not "true"/"false" strings).
Zapier converts your Python/JS output to JSON. Some types convert in surprising ways: Python's `None` becomes null, JS `undefined` becomes null, `datetime` objects become strings.
If the test fails, the error message shows in the Test panel. Read carefully — most errors are missing keys or wrong types in input_data.
Retest with at least 2-3 different sample inputs (different upstream events) to catch edge-case behavior before publishing.
Step 6
API keys go in environment-style storage (set in Code step config), never in the script body. All input fields are untrusted — validate before using.
Do not paste API keys directly into the script body. Zapier shows the script in plain text in the editor and Zap History.
Use the "Input Data" panel as a poor-man's environment variable: add a key like `STRIPE_KEY` with the value, reference as `input_data["STRIPE_KEY"]`. Still visible in the editor to anyone with edit access, but at least not hardcoded in the script body.
For high-security keys, do NOT use Code by Zapier — use a Cloudflare Worker or similar where you can use real env vars and rotate keys properly.
Validate every input field. If your script trusts `input_data["amount"]` to be a number and a trigger field returns a string, you get type errors or worse, silent miscalculation.
Never log full input data to console — Zap History will display the log, including any sensitive fields. Use selective logging.
Step 7
In the script editor, write a comment block at the top explaining: what this script does, what inputs it expects, what outputs it returns. Future-you (or your specialist) will need this.
Code by Zapier has no separate documentation surface. The script body is the only place to leave notes.
At the top of every Code step, add a comment: "Purpose: normalize email and validate domain. Inputs: email (string), allowed_domains (csv string). Outputs: normalized_email (string), is_allowed (bool), reason (string)."
Without this, a script you wrote 6 months ago becomes archaeology. Specialists charge for the discovery time.
Also document in the Zap description (Zap → Settings → Description) which steps use Code and what each does. Helps future maintenance.
Common mistakes
Putting too much logic in one Code step
What goes wrong: A 60-line Python script is doing parsing, API calls, validation, and reshaping. When it breaks, debugging takes an hour because you cannot isolate which part failed. Maintenance becomes impossible.
How to avoid: Split into multiple Code steps, each doing one thing. Or move the whole logic to a Cloudflare Worker and call it via Webhooks by Zapier — gives you source control, retries, and proper logs.
Trusting input_data fields exist
What goes wrong: `input_data["email"]` crashes when the upstream step didn't return an email. Zap halts. You spend 30 minutes debugging before realizing the trigger was missing the field.
How to avoid: Always use `.get()` (Python) or `||` fallback (JS). Set explicit default values for every input. Return a validity flag that downstream steps can filter on.
Hardcoding secrets
What goes wrong: You paste an API key in the script body. A teammate with editor access screenshots the Zap for documentation. The key is now in a shared doc forever. Rotation requires finding every reference.
How to avoid: Pass secrets via the Input Data panel as named fields. For high-stakes secrets, do not use Code by Zapier at all — move to a hosted Worker/Lambda with real env vars.
No fallback for API call failures
What goes wrong: Your Code step calls a third-party API. The API has a brief 503 outage. Your script crashes. The whole Zap halts. The trigger event is lost unless you manually replay (Pro+ only auto-replays).
How to avoid: Wrap API calls in try/except with a timeout. On failure, return a fallback value and a `success: false` flag. Filter downstream steps on `success` so failures route to a different path (e.g., a Slack alert).
No inline documentation
What goes wrong: Six months later, you (or a teammate) cannot tell what the Code step does without reading every line. Replacing it requires reverse-engineering. Cost: 1-2 hours per script per maintenance cycle.
How to avoid: Always write a header comment with purpose, inputs, outputs. Always describe non-obvious logic with inline comments. Treat the Code step body as documentation as much as logic.
Type coercion surprises
What goes wrong: Your script returns `output = {"count": 5}`. Downstream Zapier action expects a string and the platform silently coerces 5 to "5". Math you do downstream breaks because "5" + "3" = "53".
How to avoid: Always return the exact type you need downstream. Use explicit string formatting (`str(count)` in Python, `String(count)` in JS) when you mean a string. Test downstream actions to verify the type round-trips correctly.
Recap
Done — what's next
How to set up Webhooks by Zapier (Catch Hook and custom payloads)
Read the next tutorial
Hand it off
Code by Zapier is the part of the platform where most teams accumulate hidden technical debt fastest. Specialists who have shipped 100+ Code steps know which scripts belong in Code vs. which belong in a Cloudflare Worker. EverestX automation specialists typically rebuild a problematic Code-heavy Zap stack in 1-2 weeks, $400-800 total at $14-16/hr.
See specialist rates
No. The sandbox only includes Python 3 standard library and Node 18 built-ins. If you need a package, either rewrite the logic in vanilla code or move the step to a hosted environment (Cloudflare Worker, AWS Lambda) called via Webhooks.
10 seconds per execution on most tiers. Long-running scripts (loops over 100+ items, API calls with retries) will hit this limit and the Zap will halt. For longer workloads, host externally.
Python is cleaner for string and date handling. JavaScript is cleaner for async operations and JSON manipulation. Pick the language your team knows better — both run with similar limits and the same input/output contract.
Open Zap → History → click the failed run → expand the Code step. You see the input_data passed in and the error message. Print statements (`print()` in Python, `console.log()` in JS) appear in the history. Use them liberally during development.
Indirectly — by POSTing to a Webhooks-by-Zapier-triggered Zap. This is the cleanest pattern for triggering one Zap from another. Direct Zap-to-Zap calls are not supported.
Zapier
If your app sends data and Zapier needs to receive it instantly, polling won't cut it. Webhooks by Zapier handle the instant case — once you get past the auth, payload-shape, and parsing gotchas that bite every first-timer.
Zapier
Default Zapier behavior on errors: fire once, fail silent, halt the Zap. Lose data. This walks through auto-replay, dedicated error Zaps, fallback paths, and the monitoring discipline that catches breaks within an hour — not after the next quarterly review.
Zapier
Your Zap was working last week. Today, Zap History shows red. This walks through the diagnostic flow specialists run — OAuth, payload shape, rate limits, schema drift — in the order that surfaces the issue fastest.
Zapier
Zapier is the default. Make (formerly Integromat) is the budget choice. The honest answer for which to pick depends on your stack, your volume, and how technical your team is. Here's the framework we use when we audit accounts.
HubSpot Marketing Hub
Workflows are the engine under HubSpot's marketing automation. They are also where 80% of the silent breakage happens — wrong enrollment criteria, missing re-enrollment toggles, branch logic that loops. Here's how specialists build them so they hold up.