Developing Custom Zap Integrations for Zapier
When built-in Zapier apps are insufficient, custom Zapier Apps are developed (via Zapier CLI) or Webhooks + Code by Zapier are used for custom logic.
Code by Zapier — JavaScript in Zap
The built-in Code step allows writing JavaScript directly in a Zap:
// Input data from previous step
const orderData = inputData.orderPayload; // JSON string
const order = JSON.parse(orderData);
// Transformation
const lineItems = order.items.map(item => ({
sku: item.product_code,
name: item.product_name,
qty: item.quantity,
price: (item.price_cents / 100).toFixed(2),
total: ((item.price_cents * item.quantity) / 100).toFixed(2)
}));
const totalDiscount = order.discounts.reduce((sum, d) => sum + d.amount_cents, 0);
// Return data for next steps
output = {
lineItemsJson: JSON.stringify(lineItems),
lineItemsCount: lineItems.length,
subtotal: (order.subtotal_cents / 100).toFixed(2),
discount: (totalDiscount / 100).toFixed(2),
total: (order.total_cents / 100).toFixed(2),
isHighValue: order.total_cents > 1000000, // > 10,000 RUB
formattedDate: new Date(order.created_at).toLocaleDateString('en-US', {
day: 'numeric', month: 'long', year: 'numeric'
})
};
Custom Zapier App (Zapier CLI)
For public integration or reuse — a full-fledged Zapier App:
npm install -g zapier-platform-cli
zapier login
zapier init my-app --template=trigger
cd my-app
// triggers/newOrder.js
const subscribeHook = async (z, bundle) => {
const data = {
url: bundle.targetUrl,
event: 'order.created'
};
const response = await z.request({
url: `${process.env.BASE_URL}/webhooks`,
method: 'POST',
body: data,
});
return response.data;
};
const unsubscribeHook = async (z, bundle) => {
const webhookId = bundle.subscribeData.id;
return z.request({
url: `${process.env.BASE_URL}/webhooks/${webhookId}`,
method: 'DELETE',
});
};
const getOrder = async (z, bundle) => {
const response = await z.request({
url: `${process.env.BASE_URL}/orders/${bundle.cleanedRequest.id}`,
});
return [response.data];
};
module.exports = {
key: 'new_order',
noun: 'Order',
display: {
label: 'New Order',
description: 'Triggers when a new order is created.',
},
operation: {
type: 'hook',
performSubscribe: subscribeHook,
performUnsubscribe: unsubscribeHook,
perform: getOrder,
sample: { id: 1, total: 5000, status: 'paid' },
},
};
// creates/createContact.js
const createContact = async (z, bundle) => {
const response = await z.request({
url: `${process.env.BASE_URL}/contacts`,
method: 'POST',
body: {
email: bundle.inputData.email,
name: bundle.inputData.name,
phone: bundle.inputData.phone,
},
});
return response.data;
};
module.exports = {
key: 'create_contact',
noun: 'Contact',
display: {
label: 'Create Contact',
description: 'Creates a new contact.',
},
operation: {
inputFields: [
{ key: 'email', required: true, type: 'string' },
{ key: 'name', required: true, type: 'string' },
{ key: 'phone', required: false, type: 'string' },
],
perform: createContact,
sample: { id: 1, email: '[email protected]' },
},
};
Testing and Deployment
# Test individual trigger
zapier test
# Deploy to Zapier (private app)
zapier push
# Promote to public
zapier promote 1.0.0
Authentication in Custom App
// authentication.js — OAuth2
module.exports = {
type: 'oauth2',
oauth2Config: {
authorizeUrl: {
url: 'https://example.com/oauth/authorize',
params: { client_id: '{{process.env.CLIENT_ID}}', scope: 'read write' }
},
getAccessToken: {
url: 'https://example.com/oauth/token',
body: {
code: '{{bundle.inputData.code}}',
client_id: '{{process.env.CLIENT_ID}}',
client_secret: '{{process.env.CLIENT_SECRET}}',
}
},
refreshAccessToken: {
url: 'https://example.com/oauth/token',
body: {
refresh_token: '{{bundle.authData.refresh_token}}',
grant_type: 'refresh_token',
}
},
},
};
Timeframe
Code by Zapier for custom transformation — a few hours. Full Custom Zapier App with 2–3 triggers and 3–5 actions — 1–2 weeks.







