Build with CLI
Zapier is a platform for creating integrations and workflows. This CLI is your gateway to creating custom applications on the Zapier platform.
This doc describes the latest CLI version (15.18.1), as of this writing. If you’re using an older version of the CLI, you may want to check out these historical releases:
Getting Started
If you’re new to Zapier Platform CLI, we strongly recommend you to walk through the Tutorial for a more thorough introduction. If you haven’t used Zapier before, we would highly recommend that you learn the basics in our Zapier Getting Started Guide.
Requirements
All Zapier CLI integrations are run using Node.js v18
.
You can develop using any version of Node you’d like, but your eventual code must be compatible with v18
. If you’re using features not yet available in v18
, you can transpile your code to a compatible format with Babel (or similar).
To ensure stability for our users, we strongly encourage you run tests on v18
sometime before your code reaches users. This can be done multiple ways.
Firstly, by using a CI tool (like Travis CI or Circle CI, which are free for open source projects). We provide a sample .travis.yml file in our template integrations to get you started.
Alternatively, you can change your local node version with tools such as nvm. Then you can either swap to that version with nvm use v18
, or do nvm exec v18 zapier test
so you can run tests without having to switch versions while developing.
Quick Setup Guide
First up is installing the CLI and setting up your auth to create a working “Zapier Example” integration. It will be private to you and visible in your live Zap Editor.
Note: If you log into Zapier via the single sign-on (Google, Facebook, or Microsoft), you may not have a Zapier password. If that’s the case, you’ll need to generate a deploy key, go to your Zapier developer account here and create/copy a key, then run
zapier login
command with the —sso flag.
Your Zapier CLI should be installed and ready to go at this point. Next up, we’ll create our first integration!
Depending on the authentication method for your integration, you’ll also likely need to set your CLIENT_ID
and CLIENT_SECRET
as environment variables. These are the consumer key and secret in OAuth1 terminology.
You should now have a working local integration. You can run several local commands to try it out.
Next, you’ll probably want to upload integration to Zapier itself so you can start testing live.
Go check out our full CLI reference documentation to see all the other commands!
Creating a Local Integration
Creating an Integration can be done entirely locally and they are fairly simple Node.js apps using the standard Node environment and should be completely testable. However, a local integration stays local until you zapier register
.
If you’d like to manage your local Integration, use these commands:
zapier init myapp
- initialize/start a local integration projectzapier convert 1234 .
- initialize/start from an existing integrationzapier scaffold resource Contact
- auto-injects a new resource, trigger, etc.zapier test
- run the same tests asnpm test
zapier validate
- ensure your integration is validzapier describe
- print some helpful information about your integration
Local Project Structure
In your integration’s folder, you should see this general recommended structure. The index.js
is Zapier’s entry point to your integration. Zapier expects you to export an App
definition there.
Local App Definition
The core definition of your App
will look something like this, and is what your index.js
should provide as the only export:
Tip: You can use higher order functions to create any part of your App definition!
Zapier Platform Schema
The Zapier Platform Schema provides details for various schema components of a Zapier integration. Each schema outlines the required fields, data types, and configurations for features such as authentication, triggers, creates, searches, and HTTP requests. It’s essential for integration developers to refer to this schema documentation when building their Zapier integrations, as it ensures that the correct structure is followed and the necessary components are used. The detailed examples, valid configurations, and anti-examples help guide integraton developers in creating reliable and functional integrations, avoid common errors, and ensure compatibility with the Zapier platform.
Registering an Integration
Registering your Integration with Zapier is a necessary first step which only enables basic administrative functions. It should happen before zapier push
which is to used to actually expose an Integration Version in the Zapier interface and editor.
Note: This doesn’t put your integration in the editor - see the docs on pushing an Integration Version to do that!
If you’d like to manage your Integration, use these commands:
zapier integrations
- list the integrations in Zapier you can administerzapier register "Integration Title"
- creates a new integration in Zapierzapier link
- lists and links a selected integration in Zapier to your current folderzapier history
- print the history of your integrationzapier team:add
user@example.com
admin
- add an admin to help maintain/develop your integrationzapier users:add
user@example.com
1.0.0
- invite a user try your integration version 1.0.0
Converting an Existing Integration
If you have an existing Zapier legacy Web Builder integration, you can use it as a template to kickstart your local integration.
Your CLI integration will be created and you can continue working on it.
Note: There is no way to convert a CLI integration to a Web Builder integration and we do not plan on implementing this.
Introduced in v8.2.0, you are able to convert new integrations built in Zapier Platform UI to CLI.
Authentication
Most integrations require some sort of authentication. The Zapier platform provides core behaviors for several common authentication methods that might be used with your integration, as well as the ability to customize authentication further.
When a user authenticates to your integration through Zapier, a “connection” is created representing their authentication details. Data tied to a specific authentication connection is included in the bundle object under bundle.authData
.
Basic
Useful if your integration requires two pieces of information to authenticate: username
and password
, which only the end user can provide. By default, Zapier will do the standard Basic authentication base64 header encoding for you (via an automatically registered middleware).
To create a new integration with basic authentication, run
zapier init [your integration name] --template basic-auth
. You can also review an example of that code here.
If your integration uses Basic auth with an encoded API key rather than a username and password, like Authorization: Basic APIKEYHERE:x
, consider the Custom authentication method instead.
Digest
Added in v7.4.0.
The setup and user experience of Digest Auth is identical to Basic Auth. Users provide Zapier their username and password, and Zapier handles all the nonce and quality of protection details automatically.
To create a new integration with digest authentication, run
zapier init [your integration name] --template digest-auth
. You can also review an example of that code here.
Limitation: Currently, MD5-sess and SHA are not implemented. Only the MD5 algorithm is supported. In addition, server nonces are not reused. That means for every
z.request
call, Zapier will send an additional request beforehand to get the server nonce.
Custom
Custom auth is most commonly used for integrations that authenticate with API keys, although it also provides flexibility for any unusual authentication setup. You’ll likely provide some custom beforeRequest
middleware or a requestTemplate
(see Making HTTP Requests) to pass in data returned from the authentication process, most commonly by adding/computing needed headers.
To create a new integration with custom authentication, run
zapier init [your integration name] --template custom-auth
. You can also review an example of that code here.
Session
Session auth gives you the ability to exchange some user-provided data for some authentication data; for example, username and password for a session key. It can be used to implement almost any authentication method that uses that pattern - for example, alternative OAuth flows.
To create a new integration with session authentication, run
zapier init [your integration name] --template session-auth
. You can also review an example of that code here.
For Session auth, the function that fetches the additional authentication data needed to make API calls (authentication.sessionConfig.perform
) has the user-provided fields in bundle.inputData
. Afterwards, bundle.authData
contains the data returned by that function (usually the session key or token).
OAuth1
Added in v7.5.0
.
Zapier’s OAuth1 implementation matches X (formerlly called Twitter) and Trello implementations of the 3-legged OAuth flow.
To create a new integration with OAuth1, run
zapier init [your integration name] --template oauth1-trello
. You can also check out oauth1-trello, oauth1-tumblr, and oauth1-twitter for working example integrations with OAuth1.
The flow works like this:
- Zapier makes a call to your API requesting a “request token” (also known as “temporary credentials”).
- Zapier sends the user to the authorization URL, defined by your integration, along with the request token.
- Once authorized, your website sends the user to the
redirect_uri
Zapier provided. Usezapier describe
command to find out what it is: - Zapier makes a backend call to your API to exchange the request token for an “access token” (also known as “long-lived credentials”).
- Zapier stores the
access_token
and uses it to make calls on behalf of the user.
You are required to define:
getRequestToken
: The API call to fetch the request tokenauthorizeUrl
: The authorization URLgetAccessToken
: The API call to fetch the access token
You’ll also likely need to set your CLIENT_ID
and CLIENT_SECRET
as environment variables. These are the consumer key and secret in OAuth1 terminology.
Your auth definition would look something like this:
For OAuth1, authentication.oauth1Config.getRequestToken
, authentication.oauth1Config.authorizeUrl
, and authentication.oauth1Config.getAccessToken
have fields like redirect_uri
and the temporary credentials in bundle.inputData
. After getAccessToken
runs, the resulting token value(s) will be stored in bundle.authData
for the connection.
Also, authentication.oauth1Config.getAccessToken
has access to the additional return values in rawRequest
and cleanedRequest
should you need to extract other values (for example, from the query string).
OAuth2
Zapier’s OAuth2 implementation is based on the authorization_code
flow, similar to GitHub and Facebook.
To create a new integration with OAuth2, run
zapier init [your integration name] --template oauth2
. You can also check out our working example integration.
If your integration’s OAuth2 flow uses a different grant type, such as client_credentials
, try using Session auth instead.
The OAuth2 flow looks like this:
- Zapier sends the user to the authorization URL defined by your integration.
- Once authorized, your website sends the user to the
redirect_uri
Zapier provided. Use thezapier describe
command to find out what it is: - Zapier makes a backend call to your API to exchange the
code
for anaccess_token
. - Zapier stores the
access_token
and uses it to make calls on behalf of the user. - (Optionally) Zapier can refresh the token if it expires.
You are required to define:
authorizeUrl
: The authorization URLgetAccessToken
: The API call to fetch the access token
If the access token has a limited life and you want to refresh the token when it expires, you’ll also need to define the API call to perform that refresh (refreshAccessToken
). You can choose to set autoRefresh: true
, as in the example integration, if you want Zapier to automatically make a call to refresh the token after receiving a 401. See Stale Authentication Credentials for more details on handling auth refresh.
You’ll also likely want to set your CLIENT_ID
and CLIENT_SECRET
as environment variables:
Your auth definition would look something like this:
For OAuth2, authentication.oauth2Config.authorizeUrl
, authentication.oauth2Config.getAccessToken
, and authentication.oauth2Config.refreshAccessToken
have fields like redirect_uri
and state
in bundle.inputData
. After the code is exchanged for an access token and/or refresh token, those tokens are stored in bundle.authData
for the connection.
Also, authentication.oauth2Config.getAccessToken
has access to the additional return values in rawRequest
and cleanedRequest
should you need to extract other values (for example, from the query string).
If you define fields
to collect additional details from the user, please note that client_id
and client_secret
are reserved keys and cannot be used as keys for input form fields.
Note: The OAuth2
state
param is a standard security feature that helps ensure that authorization requests are only coming from your servers. Most OAuth clients have support for this and will send back thestate
query param that the user brings to your app. The Zapier Platform performs this check and this required field cannot be disabled. The state parameter is automatically generated by Zapier in the background, and can be accessed atbundle.inputData.state
. Since Zapier uses thestate
to verify that GET requests to your redirect URL truly come from your integration, it needs to be generated by Zapier so that it can be validated later (once the user confirms that they’d like to grant Zapier permission to access their account in your app).
OAuth2 with PKCE
Added in v14.0.0.
Zapier’s OAuth2 implementation also supports PKCE. This implementation is an extension of the OAuth2 authorization_code
flow described above.
To use PKCE in your OAuth2 flow, you’ll need to set the following variables:
enablePkce: true
getAccessToken.body
to includecode_verifier: "{{bundle.inputData.code_verifier}}"
The OAuth2 PKCE flow uses the same flow as OAuth2 but adds a few extra parameters:
- Zapier computes a
code_verifier
andcode_challenge
internally and stores thecode_verifier
in the Zapier bundle. - Zapier sends the user to the authorization URL defined by your integration. We automatically include the computed
code_challenge
andcode_challenge_method
in the authorization request. - Once authorized, your website sends the user to the
redirect_uri
Zapier provided. - Zapier makes a call to your API to exchange the code but you must include the computed
code_verifier
in the request for anaccess_token
. - Zapier stores the
access_token
and uses it to make calls on behalf of the user.
Your auth definition would look something like this:
The computed code_verifier
uses this standard: RFC 7636 Code Verifier
The computed code_challenge
uses this standard: RFC 7636 Code Challenge
Connection Label
When a user connects to your app via Zapier and a connection is created to hold the related data in bundle.authData
, the connection is automatically labeled with the integration name. You also have the option of setting a connection label (connectionLabel
), which can be extremely helpful to identify information like which user is connected or what instance of your app they are connected to. That way, users don’t get confused if they have multiple connections to your app.
When setting a connection label, you can use either a string with variable references (as shown in Basic Auth) or a function (as shown in Digest Auth).
When using a string, you have access to the information in bundle.authData
and the information returned from the test request in bundle.inputData
, all at the top level. So in Basic auth, if connectionLabel
is {{username}}
, that refers to the username used for authentication.
When using a function, this “hoisting” of data to the top level is skipped, and you must refer to data items by their fully qualified name, as shown in the line return bundle.inputData.username;
in the Digest Auth snippet. return username;
would not work in this context.
NOTE: Do not use sensitive authentication data such as passwords or API keys in the connection label. It’s visible in plain text on Zapier. The purpose of the label is to identify the connection for the user, so stick with data such as username or instance identifier that is meaningful but not sensitive.
Domain and subdomain validation
When adding a subdomain input field, commonly used in OAuth implementations, additional validation is strongly recommended to prevent a potential security vulnerability. If not taken into account, an attacker could utilize a maliciously constructed subdomain field (like attacker-domain.com/
) in order to redirect OAuth connection requests to that attacker-controlled domain (because attacker-domain.com/.your-domain.com
resolves to the attacker’s domain instead of the expected one).
This vulnerability presents itself when:
- The authentication method uses pre-configured tokens or secret values (for example, OAuth v2)
- User is able to input a domain or subdomain when authenticating within Zapier
- Integration stores sensitive authentication details (in environment variables, for example) which are used as part of the authentication process
Taking the following steps prevents the potential for an attacker to access your integration’s sensitive authentication information, such as the OAuth client ID or secret.
- If your integration allows for the user to provide a domain, validate the input against an allow-list of trusted domains.
- If your integration allows for the user to provide a subdomain, add conditional validation for the subdomain string whenever you include the value in your OAuth HTTP requests. This change will prevent potential exploitation of the subdomain vulnerability.
- If you’re using OAuth-based authentications, update the
getAccessToken
and optionalrefreshAccessToken
configuration methods. If the integration uses shorthand HTTP requests, switching to manual HTTP requests will allow you to perform this manual subdomain validation.
Example code for handling subdomain validation:
Resources
A resource
is a representation (as a JavaScript object) of one of the REST resources of your API. Say you have a /recipes
endpoint for working with recipes; you can define a recipe resource in your integration that will tell Zapier how to do create,
read, and search operations on that resource.
The quickest way to create a resource is with the zapier scaffold
command:
This will generate the resource file and add the necessary statements to the index.js
file to import it.
Resource Definition
A resource has a few basic properties. The first is the key
, which allows Zapier to identify the resource on our backend.
The second is the noun
, the user-friendly name of the resource that is presented to users throughout the Zapier UI.
Check out this working example integration to see resources in action.
After those, there is a set of optional properties that tell Zapier what methods can be performed on the resource. The complete list of available methods can be found in the Resource Schema Docs. For now, let’s focus on two:
list
- Tells Zapier how to fetch a set of this resource. This becomes a Trigger in the Zapier Editor.create
- Tells Zapier how to create a new instance of the resource. This becomes an Action in the Zapier Editor.
Here is a complete example of what the list method might look like
The method is made up of two properties, a display
and an operation
. The display
property (schema) holds the info needed to present the method as an available Trigger in the Zapier Editor. The operation
(schema) provides the implementation to make the API call.
Adding a create method looks very similar.
Every method you define on a resource
Zapier converts to the appropriate Trigger, Create, or Search. Our examples
above would result in an integration with a New Recipe Trigger and an Add Recipe Create.
Note the keys for the Trigger, Create, Search, and Search or Create are automatically generated (in case you want to use them in a dynamic dropdown), like: {resourceName}List
, {resourceName}Create
, {resourceName}Search
, and {resourceName}SearchOrCreate
; in the examples above, {resourceName}
would be recipe
.
Triggers/Searches/Creates
Triggers, Searches, and Creates are the way an integration defines what it is able to do. Triggers read data into Zapier (i.e. watch for new recipes). Searches locate individual records (find recipe by title). Creates create new records in your system (add a recipe to the catalog).
The definition for each of these follows the same structure. Here is an example of a trigger:
You can find more details on the definition for each by looking at the Trigger Schema, Search Schema, and Create Schema.
To create a new integration with a premade trigger, search, or create, run
zapier init [your integration name]
and select from the list that appears. You can also check out our working example integrations here.
To add a trigger, search, or create to an existing integration, run
zapier scaffold [trigger|search|create] [noun]
to create the necessary files to your project. For example,zapier scaffold trigger post
will create a new trigger called “New Post”.
Return Types
Each of the 3 types of function should return a certain data type for use by the platform. There are automated checks to let you know when you’re trying to pass the wrong type back. For reference, each expects:
Method | Return Type | Notes |
---|---|---|
Trigger | Array | 0 or more objects; passed to the deduper if polling |
Search | Array | 0 or more objects. Only the first object will be returned, so if len > 1, put the best match first |
Create | Object | Return values are evaluated by isPlainObject |
When a trigger function returns an empty array, the Zap will not trigger. For REST Hook triggers, this can be used to filter data if the available subscription options are not specific enough for the Zap’s needs.
Returning Line Items (Array of Objects)
In some cases, you may want to include multiple items in the data you return for Searches or Creates. To do that, return the set of items as an array of objects under a descriptive key. This may be as part of another object (like items in an invoice) or as multiple top-level items.
For example, a Create Order action returning an order with multiple items might look like this:
While a Find Users search could return multiple items under an object key within an array, like this:
A standard search would return just the inner array of users, and only the first user would be provided as a final result. Returning line items instead means that the “first result” return is the object containing all the user details within it.
Using the standard approach is recommended, because not all Zapier integrations support line items directly, so users may need to take additional actions to reformat this data for use in their Zaps. More detail on that at Use line items in Zaps. However, there are use cases where returning multiple results is helpful enough to outweigh that additional effort.
Fallback Sample
In cases where Zapier needs to show an example record to the user, but we are unable to get a live example from the API, Zapier will fallback to this hard-coded sample. This should reflect the data structure of the Trigger’s perform method, and have dummy values that we can show to any user.
Input Fields
Moved to Input Fields.
Output Fields
On each trigger, search, or create in the operation directive - you can provide an array of objects as fields under the outputFields
. Output Fields are what users see when they select a field provided by your trigger, search or create to map it to another.
Output Fields are optional, but can be used to:
- Define friendly labels for the returned fields. By default, we will humanize for example
my_key
as My Key. - Make sure that custom fields that may not be found in every live sample and - since they’re custom to the connected account - cannot be defined in the static sample, can still be mapped.
- (Added in v15.6.0) Define what field(s) can be used to uniquely identify and deduplicate items returned by a polling trigger call.
The schema for outputFields
is shared with inputFields
but only these properties are relevant:
key
- includes the field when not present in the live sample. When nolabel
property is provided,key
will be humanized and displayed as the field name.label
- defines the field name displayed to users.type
- defines the type for static sample data. A validation warning will be displayed if the static sample does not match the specified type.required
- defines whether the field is required in static sample data. A validation warning will be displayed if the value is true and the static sample does not contain the field.primary
- defines whether the field is part of the primary key for polling trigger deduplication.
Custom/Dynamic Output Fields are defined in the same way as Custom/Dynamic Input Fields.
Nested & Children (Line Item) Fields
To define an Output Field for a nested field use {{parent}}__{{key}}
. For children (line item) fields use {{parent}}[]{{key}}
.
Buffered Create Actions
Added in v15.15.0. This feature is currently internal-only.
A Buffered Create allows you to create objects in bulk with a single or fewer API request(s). This is useful when you want to reduce the number of requests made to your server. When enabled, Zapier holds the data until the buffer reaches a size limit or a certain time has passed, then sends the buffered data using the performBuffer
function you define.
To implement a Buffered Create, you define a buffer
configuration object and a performBuffer
function in the operation
object. In the buffer
config object, you specify how you want to group the buffered data using the groupedBy
setting and the maximum number of items to buffer using the limit
setting.
The performBuffer
function should replace the perform
function. Note that perform
cannot be defined along with performBuffer
. Check out the create
action operation schema for details.
Similar to the general perform
function accepting two arguments, z
and bundle
objects, the performBuffer
function accepts z
and bufferedBundle
objects. They share the same z
object, but the bufferedBundle
object is different from the bundle
object. The bufferedBundle
object has an idempotency ID set at bufferedBundle.buffer[].meta.id
for each object in the buffer. performBuffer
would have to return the idempotency IDs to tell Zapier which objects were successfully written. Find the details about the bufferedBundle
object here.
Here is an example of a Buffered Create action:
Deploying an Integration Version
An Integration Version is related to a specific Integration but is an “immutable” implementation of your integration. This makes it easy to run multiple versions for multiple users concurrently. The Integration Version is pulled from the version within the package.json
. To create a new Integration Version, update the version number in that file. By default, every Integration Version is private but you can zapier promote
it to production for use by over 1 million Zapier users.
If you’d like to manage your Version, use these commands:
zapier versions
- list the versions for the current directory’s integrationzapier push
- push the current version of current directory’s integration & version (read frompackage.json
)zapier promote 1.0.0
- mark a version as the “production” versionzapier migrate 1.0.0 1.0.1 [100%]
- move users between versions, regardless of deployment statuszapier deprecate 1.0.0 2020-06-01
- mark a version as deprecated, but let users continue to use it (we’ll email them)zapier env:set 1.0.0 KEY=VALUE
- set an environment variable to some valuezapier delete:version 1.0.0
- delete a version entirely. This is mostly for clearing out old test apps you used personally. It will fail if there are any users. You probably wantdeprecate
instead.zapier pull
- pull the latest version from Zapier server. This is used in the event that Zapier made an update since your last version.
Note: To see the changes that were just pushed reflected in the browser, you have to manually refresh the browser each time you push.
Private Integration Version (default)
A simple zapier push
will only create the Integration Version in your editor. No one else using Zapier can see it or use it.
Sharing an Integration Version
This is how you would share your integration with friends, co-workers or clients. This is perfect for quality assurance, testing with active users or just sharing any app you like.
You can also invite anyone on the internet to your integration by using the links from zapier users:links
. The link should look something like https://zapier.com/platform/public-invite/1/222dcd03aed943a8676dc80e2427a40d/
. You can put this in your help docs, post it to Twitter, add it to your email campaign, etc. You can choose an invite link specific to an integration version or for the entire integration (i.e. all integration versions).
Promoting an Integration Version
Promotion is how you would share your integration with every one of the 1 million+ Zapier users. If this is your first time promoting - you may have to wait for the Zapier team to review and approve your integration.
If this isn’t the first time you’ve promoted your integration - you might have users on older versions. You can zapier migrate
to either move users over (which can be dangerous if you have breaking changes). Or, you can zapier deprecate
to give users some time to move over themselves.
Pulling Latest Version from Zapier
Zapier may fix bugs or add new features to your integration and release a new version. If you attempt to use zapier push
and we’ve released a newer version, you will be prevented from pushing until you run zapier pull
to update your local files with the latest version.
Any destructive file changes will prompt you with a confirmation dialog before continuing.
Z Object
We provide several methods off of the z
object, which is provided as the first argument to all function calls in your integration.
The
z
object is passed into your functions as the first argument - IE:perform: (z) => {}
.
z.request([url], options)
z.request([url], options)
is a promise based HTTP client with some Zapier-specific goodies. See Making HTTP Requests. z.request()
will percent-encode non-ascii characters and these reserved characters: :$/?#[]@$&+,;=^@`\
. Use skipEncodingChars
to modify this behaviour.
z.console
z.console.log(message)
is a logging console, similar to Node.js console
but logs remotely, as well as to stdout in tests. See Log Statements
z.dehydrate(func, inputData)
z.dehydrate(func, inputData)
is used to lazily evaluate a function, perfect to avoid API calls during polling or for reuse. See Dehydration.
z.dehydrateFile(func, inputData)
z.dehydrateFile
is used to lazily download a file, perfect to avoid API calls during polling or for reuse. See File Dehydration.
z.stashFile(bufferStringStream, [knownLength], [filename], [contentType])
z.stashFile(bufferStringStream, [knownLength], [filename], [contentType])
is a promise based file stasher that returns a URL file pointer. See Stashing Files.
z.JSON
z.JSON
is similar to the JSON built-in like z.JSON.parse('...')
, but catches errors and produces nicer tracebacks.
z.hash()
z.hash()
is a crypto tool for doing things like z.hash('sha256', 'my password')
z.errors
z.errors
is a collection error classes that you can throw in your code, like throw new z.errors.HaltedError('...')
.
The available errors are:
Error
(added in v9.3.0) - Stops the current operation, allowing for (auto) replay. Read more on General ErrorsHaltedError
- Stops current operation, but will never turn off Zap. Read more on Halting ExecutionExpiredAuthError
- Stops the current operation and emails user to manually reconnect. Read more on Stale Authentication CredentialsRefreshAuthError
- (OAuth2 or Session Auth) Tells Zapier to refresh credentials and retry operation. Read more on Stale Authentication CredentialsThrottledError
(new in v11.2.0) - Tells Zapier to retry the current operation after a delay specified in seconds. Read more on Handling Throttled Requests
For more details on error handling in general, see here.
z.cursor
The z.cursor
object exposes two methods:
z.cursor.get(): Promise<string|null>
z.cursor.set(string): Promise<null>
Any data you set
will be available to that Zap for about an hour (or until it’s overwritten). For more information, see: paging.
z.generateCallbackUrl()
The z.generateCallbackUrl()
will return a callback URL your app can POST
to later for handling long running tasks (like transcription or encoding jobs). In the meantime, the Zap and Task will wait for your response and the user will see the Task marked as waiting.
For example, in your perform
you might do:
And in your own /api/slow-job
view (or more likely, an async job) you’d make this request to Zapier when the long-running job completes to populate bundle.cleanedRequest
:
We recommend using
bundle.meta.isLoadingSample
to determine if the execution is happening in the foreground (IE: during Zap setup) as usingz.generateCallbackUrl()
can be inappropriate given the disconnect. Instead, wait for the long running request without generating a callback, or if you must, return stubbed data.
By default the payload POST
ed to the callback URL will augment the data returned from the initial perform
to compose the final value.
If you need to customize what the final value should be you can define a performResume
method that receives three bundle properties:
bundle.outputData
is{"hello": "world"}
, the data returned from the initialperform
bundle.cleanedRequest
is{"foo": "bar"}
, the payload from the callback URLbundle.rawRequest
is the full request object corresponding tobundle.cleanedRequest
The app will have a maximum of 30 days to
POST
to the callback URL. If a user deletes or modifies the Zap or Task in the meantime, we will not resume the task.
performResume
will only run when the Zap runs live, and cannot be tested in the Zap Editor when configuring the Zap. It is possible to usebundle.meta.isLoadingSample
to load a fixed sample to allow users to test a step that includesperformResume
.
Bundle Object
This object holds the user’s auth details and the data for the API requests.
The
bundle
object is passed into your functions as the second argument - IE:perform: (z, bundle) => {}
.
bundle.authData
bundle.authData
is user-provided authentication data, like api_key
or access_token
. Read more on authentication.
bundle.inputData
bundle.inputData
is user-provided data for this particular run of the trigger/search/create, as defined by the inputFields
. For example:
bundle.inputDataRaw
bundle.inputDataRaw
is like bundle.inputData
, but before processing such as interpreting friendly datetimes and rendering {{curlies}}
:
“curlies” represent data mapped in from previous steps. They take the form
{{NODE_ID__key_name}}
.
You’ll usually want to use bundle.inputData
instead.
bundle.meta
bundle.meta
contains extra information useful for doing advanced behaviors depending on what the user is doing. It has the following options:
key | default | description |
---|---|---|
isLoadingSample | false | If true, this run was initiated manually via the Zap Editor |
isFillingDynamicDropdown | false | If true, this poll is being used to populate a dynamic dropdown. You only need to return the fields you specified (such as id and name ), though returning everything is fine too |
isPopulatingDedupe | false | If true, the results of this poll will be used to initialize the deduplication list rather than trigger a zap. You should grab as many items as possible. See also: deduplication |
limit | -1 | The number of items you should fetch. -1 indicates there’s no limit. Build this into your calls insofar as you are able |
page | 0 | Used in paging to uniquely identify which page of results should be returned |
isTestingAuth | false | (legacy property) If true, the poll was triggered by a user testing their account (via clicking “test” or during setup). We use this data to populate the auth label, but it’s mostly used to verify we made a successful authenticated request |
withSearch | undefined | When a create is called as part of a search-or-create step, withSearch will be the key of the search. |
Before v8.0.0, the information in
bundle.meta
was different. See the old docs for the previous values and the wiki for a mapping of old values to new.
Here’s an example of a polling trigger that is also used to power a dynamic dropdown:
bundle.rawRequest
bundle.rawRequest
is only available in theperform
for webhooks,getAccessToken
for OAuth authentication methods, andperformResume
in a callback action.
bundle.rawRequest
holds raw information about the HTTP request that triggered the perform
method or that represents the user’s browser request that triggered the getAccessToken
call:
In bundle.rawRequest
, headers other than Content-Length
and Content-Type
will be prefixed with Http-
, and all headers will be named in Camel-Case. For example, the header X-Time-GMT
would become Http-X-Time-Gmt
.
bundle.cleanedRequest
bundle.cleanedRequest
is only available in theperform
for webhooks,getAccessToken
for OAuth authentication methods, andperformResume
in a callback action.
bundle.cleanedRequest
will return a formatted and parsed version of the request. Some or all of the following will be available:
bundle.outputData
bundle.outputData
is only available in theperformResume
in a callback action.
bundle.outputData
will return a whatever data you originally returned in the perform
, allowing you to mix that with bundle.rawRequest
or bundle.cleanedRequest
.
bundle.targetUrl
bundle.targetUrl
is only available in theperformSubscribe
andperformUnsubscribe
methods for webhooks.
This the URL to which you should send hook data. It’ll look something like https://hooks.zapier.com/1234/abcd
. We provide it so you can make a POST request to your server. Your server should store this URL and use is as a destination when there’s new data to report.
For example:
Read more in the REST hook example.
bundle.subscribeData
bundle.subscribeData
is available in theperform
andperformUnsubscribe
method for webhooks.
This is an object that contains the data you returned from the performSubscribe
function. It should contain whatever information you need send a DELETE
request to your server to stop sending webhooks to Zapier.
Read more in the REST hook example.
BufferedBundle Object
Added in v15.15.0.
This object holds a user’s auth details (bufferedBundle.authData
) and the buffered data (bufferedBundle.buffer
) for the API requests. It is used only with a create
action’s performBuffer
function.
The
bufferedBundle
object is passed into theperformBuffer
function as the second argument - IE:performBuffer: async (z, bufferedBundle) => {}
.
bufferedBundle.authData
It is a user-provided authentication data, like api_key
or access_token
. Read more on authentication.
bufferedBundle.groupedBy
It is a user-provided data for a set of selected inputFields
to group the multiple runs of a create
action by.
bufferedBundle.buffer
It is an array of objects of user-provided data and some meta data to allow multiple runs of a create
action be processed in a single API request.
bufferedBundle.buffer[].inputData
It is a user-provided data for a particular run of a create
action in the buffer, as defined by the inputFields
.
bufferedBundle.buffer[].meta
It contains an idempotency id
provided to the create
action to identify each run’s data in the buffered data.
Environment
Integrations can define environment variables that are available when the integration’s code executes. They work just like environment variables defined on the command line. They are useful when you have data like an OAuth client ID and secret that you don’t want to commit to source control. Environment variables can also be used as a quick way to toggle between a staging and production environment during integration development.
It is important to note that variables are defined on a per-version basis! When you push a new version, the existing variables from the previous version are copied, so you don’t have to manually add them. However, edits made to one version’s environment will not affect the other versions.
Defining Environment Variables
To define an environment variable, use the env
command:
You will likely also want to set the value locally for testing.
Alternatively, we provide some extra tooling to work with an .env
(or .environment
, see below note) that looks like this:
.env
is the new recommended name for the environment file since v5.1.0. The old name.environment
is deprecated but will continue to work for backward compatibility.
And then in your test/basic.js
file:
This is a popular way to provide
process.env.ACCESS_TOKEN || bundle.authData.access_token
for convenient testing.
NOTE Variables defined via
zapier env:set
will always be uppercased. For example, you would access the variable defined byzapier env:set 1.0.0 foo_bar=1234
withprocess.env.FOO_BAR
.
Accessing Environment Variables
To view existing environment variables, use the env
command.
Within your integration, you can access the environment via the standard process.env
- any values set via local export
or zapier env:set
will be there.
For example, you can access the process.env
in your perform functions and in templates:
Note! Be sure to lazily access your environment variables - see When to use placeholders or curlies?.
Adding Throttle Configuration
Added in v15.4.0.
When a throttle configuration is set for an action, Zapier uses it to apply throttling when the limit for the timeframe window is exceeded. It can be set at the root level and/or on an action. When set at the root level, it is the default throttle configuration used on each action of the integration. And when set in an action’s operation object, the root-level default is overwritten for that action only. Note that the throttle limit is not shared across actions unless for those with the same key, window, limit, and scope when “action” is not in the scope.
To throttle an action, you need to set a throttle
object with the following variables:
window [integer]
: The timeframe, in seconds, within which the system tracks the number of invocations for an action. The number of invocations begins at zero at the start of each window.limit [integer]
: The maximum number of invocations for an action, allowed within the timeframe window.key [string]
(added in v15.6.0): The key to throttle with in combination with the scope. User data provided for the input fields can be used in the key with the use of the curly braces referencing. For example, to access the user data provided for the input field “test_field”, use{{bundle.inputData.test_field}}
. Note that a required input field should be referenced to get user data always.retry [boolean]
(added in v15.8.0): The effect of throttling on the tasks of the action.true
means throttled tasks are automatically retried after some delay, whilefalse
means tasks are held without retry. It defaults totrue
. NOTE that it has no effect on polling triggers and should not be set.filter [string]
(added in v15.8.0): EXPERIMENTAL: Account-based attribute to override the throttle by. You can set to one of the following: “free”, “trial”, “paid”. Therefore, the throttle scope would be automatically set to “account” and ONLY the accounts based on the specified filter will have their requests throttled based on the throttle overrides while the rest are throttled based on the original configuration.scope [array]
: The granularity to throttle by. You can set the scope to one or more of the following options;- ‘user’ - Throttles based on user ids.
- ‘auth’ - Throttles based on auth ids.
- ‘account’ - Throttles based on account ids for all users under a single account.
- ‘action’ - Throttles the action it is set on separately from other actions.
overrides [array[object]]
(added in v15.6.0): EXPERIMENTAL: Overrides the original throttle configuration based on a Zapier account attribute;window [integer]
: Same description as above.limit [integer]
: Same description as above.filter [string]
: Account-based attribute to override the throttle by. You can set to one of the following: “free”, “trial”, “paid”. Therefore, the throttle scope would be automatically set to “account” and ONLY the accounts based on the specified filter will have their requests throttled based on the throttle overrides while the rest are throttled based on the original configuration.retry [boolean]
(added in v15.6.1): The effect of throttling on the tasks of the action.true
means throttled tasks are automatically retried after some delay, whilefalse
means tasks are held without retry. It defaults totrue
. NOTE that it has no effect on polling triggers and should not be set.
Both window
and limit
are required and others are optional. By default, throttling is scoped to the action and account.
Here is a typical usage of the throttle configuration:
Making HTTP Requests
There are two ways to make HTTP requests:
- Shorthand HTTP Requests - Easy to use, but limits what you can control. Best for simple requests.
- Manual HTTP Requests - Gives you full control over the request and response.
Use these helper constructs to reduce boilerplate:
requestTemplate
- an object literal of HTTP request options that will be merged with every request.beforeRequest
- middleware that mutates every request before it is sent.afterResponse
- middleware that mutates every response before it is completed.
Note: you can install any HTTP client you like - but this is greatly discouraged as you lose automatic HTTP logging and middleware.
Shorthand HTTP Requests
For simple HTTP requests that do not require special pre- or post-processing, you can specify the HTTP request options as an object literal in your app definition.
This features:
- Lazy
{{curly}}
replacement. - JSON and form body de-serialization.
- Automatic non-2xx error raising.
In the URL above, {{bundle.authData.subdomain}}
is automatically replaced with the live value from the bundle. If the call returns a non 2xx return code, an error is automatically raised. The response body is automatically parsed as JSON or form-encoded and returned.
An error will be raised if the response cannot be parsed as JSON or form-encoded. To use shorthand requests with other response types, add middleware that sets response.data
to the parsed response.
Manual HTTP Requests
Use this when you need full control over the request/response. For example:
- To do processing (usually involving
bundle.inputData
) before a request is made - To do processing of an API’s response before you return data to Zapier
- To process an unusual response type, such as XML
To make a manual request, pass your request options to z.request()
then use the resulting response object to return the data you want:
Manual requests perform lazy {{curly}}
replacement. In the URL above, {{bundle.authData.subdomain}}
is automatically replaced with the live value from the bundle.
POST and PUT Requests
To POST or PUT data to your API you can do this:
Note: you need to call
z.JSON.stringify()
before setting thebody
.
Using HTTP middleware
HTTP middleware is a function or piece of code that sits between a client request and the server response, allowing you to inspect, modify, or handle the request or response before they reach their destination. You use middleware to perform common tasks like adding security headers, logging requests, handling errors, or modifying data in a centralized way without repeating code. Common examples include adding a header to all outgoing responses to improve security, or catching and handling weird errors so that users receive a friendly error message instead of the system-generated message.
To process all HTTP requests in a certain way, use the beforeRequest
and afterResponse
middleware functions.
Middleware functions go in your app definition:
A beforeRequest
middleware function takes a request options object, and returns a (possibly mutated) request object. An afterResponse
middleware function takes a response object, and returns a (possibly mutated) response object. Middleware functions are executed in the order specified in the app definition, and each subsequent middleware receives the request or response object returned by the previous middleware.
Middleware functions can be asynchronous - just return a promise from the middleware function.
The second argument for middleware is the z
object, but it does not include z.request()
as using that would easily create infinite loops.
Here is the full request lifecycle when you call z.request({...})
:
- set defaults on the
request
object - run your
beforeRequest
middleware functions in order - add applicable auth headers (e.g. adding
Basic ...
forbasic
auth), if applicable - add
request.params
torequest.url
- execute the
request
, store the result inresponse
- try to auto-parse response body for non-raw requests, store result in
response.data
- log the request to Zapier’s logging server
- if the status code is
401
, you’re using a refresh-able auth (such asoauth2
orsession
) andautoRefresh
istrue
in your auth configuration, throw aRefreshAuthError
. The server will attempt to refresh the authentication again and retry the whole step - run your
afterResponse
middleware functions in order - call
response.throwForStatus()
unlessresponse.skipThrowForStatus
istrue
The resulting response object is returned from z.request()
.
Example Integration: check out https://github.com/zapier/zapier-platform/tree/main/example-apps/middleware for a working example integration using HTTP middleware.
Error Response Handling
Since v10.0.0
, z.request()
calls response.throwForStatus()
before it returns a response. You can disable automatic error throwing by setting skipThrowForStatus
on the request object:
You can also do it in afterResponse
if the API uses a status code >= 400 that should not be treated as an error.
For developers using v9.x and below, it’s your responsibility to throw an exception for an error response. That means you should call response.throwForStatus()
or throw an error yourself, likely following the z.request
call.
This behavior has changed periodically across major versions, which changes how/when you have to worry about handling errors. Here’s a diagram to illustrate that:
Ensure you’re handling errors correctly for your platform version. The latest released version is 15.18.1.
HTTP Request Options
Shorthand requests and manual requests support the following HTTP options
:
url
: HTTP url, you can provide it as a separate argument (z.request(url, options)
) or as part of theoptions
object (z.request({url: url, ...})
).method
: HTTP method, default isGET
.headers
: request headers object, format{'header-key': 'header-value'}
.params
: URL query params object, format{'query-key': 'query-value'}
.body
: request body, can be a string, buffer, readable stream or plain object. When it is an object/array and theContent-Type
header isapplication/x-www-form-urlencoded
the body will be transformed to query string parameters, otherwise we’ll set the header toapplication/json; charset=utf-8
and JSON encode the body. Default isnull
.allowGetBody
: includebody
inGET
requests. Set totrue
to enable. Default isfalse
. Set only if required by the receiving API. See section 4.3.1 in RFC 7231.json
: shortcut object/array/etc. you want to JSON encode into body. Default isnull
.form
: shortcut object. you want to form encode into body. Default isnull
.raw
: set this to stream the response instead of consuming it immediately. Default isfalse
.redirect
: set tomanual
to extract redirect headers,error
to reject redirect, default isfollow
.follow
: maximum redirect count, set to0
to not follow redirects. default is20
.compress
: support gzip/deflate content encoding. Set tofalse
to disable. Default istrue
.agent
: Node.jshttp.Agent
instance, allows custom proxy, certificate etc. Default isnull
.timeout
: request / response timeout in ms. Set to0
to disable (OS limit still applies), timeout reset onredirect
. Default is0
(disabled).signal
(added in v15.14.1): enables cancelling requests via a timeout set by anAbortController
. More details innode-fetch
docs here. Default isnull
.size
: maximum response body size in bytes. Set to0
to disable. Default is0
(disabled).skipThrowForStatus
(added in v10.0.0): don’t callresponse.throwForStatus()
before resolving the request withresponse
. See HTTP Response Object.
HTTP Response Object
The response object returned by z.request([url], options)
supports the following fields and methods:
status
: The response status code, i.e.200
,404
, etc.content
: The response content as a String. For Buffer, tryoptions.raw = true
.data
(added in v10.0.0): The response content as an object if the content is JSON orapplication/x-www-form-urlencoded
(undefined
otherwise).headers
: Response headers object. The header keys are all lower case.getHeader(key)
: Retrieve response header, case insensitive:response.getHeader('My-Header')
skipThrowForStatus
(added in v10.0.0): don’t callthrowForStatus()
before resolving the request with this response.throwForStatus()
: Throws an error if400 <= statusCode < 600
.request
: The original request options object (see above).
Additionally, if request.raw
is true
, the raw response has the following properties:
json()
: Get the response content as an object, ifoptions.raw = true
and content is JSON (returns a promise).undefined
in non-raw requests.body
: A stream available only if you provideoptions.raw = true
.
Dehydration
Dehydration, and its counterpart Hydration, is a tool that can lazily load data that might be otherwise expensive to retrieve aggressively.
- Dehydration - think of this as “make a pointer”, you control the creation of pointers with
z.dehydrate(func, inputData)
(orz.dehydrateFile(func, inputData)
for files). This usually happens in a trigger step. - Hydration - think of this as an automatic step that “consumes a pointer” and “returns some data”, Zapier does this automatically behind the scenes. This usually happens in an action step.
This is very common when Stashing Files - but that isn’t their only use!
The method z.dehydrate(func, inputData)
has two required arguments:
func
- the function to call to fetch the extra data. Can be any rawfunction
, defined in the file doing the dehydration or imported from another part of your integration. You must also register the function in the integration’shydrators
property. Note that since v10.1.0, the maximum payload size to pass toz.dehydrate
/z.dehydrateFile
is 6KB.inputData
- this is an object that contains things like apath
orid
- whatever you need to load data on the other side- A known limitation of hydration is a 5 minute cache if the hydration call is made with identical
inputData
within that timeframe. To workaround this cache for records triggering hydration in close succession, include a unique value in theinputData
, for example atimestamp
in addition to the recordid
.
Why do I need to register my functions? Because of how JavaScript works with its module system, we need an explicit handle on the function that can be accessed from the App definition without trying to “automagically” (and sometimes incorrectly) infer code locations.
Here is an example that pulls in extra data for a movie:
And in future steps of the Zap - if Zapier encounters a pointer as returned by z.dehydrate(func, inputData)
- Zapier will tie it back to your integration and pull in the data lazily.
Why can’t I just load the data immediately? Isn’t it easier? In some cases it can be - but imagine an API that returns 100 records when polling - doing 100x
GET /id.json
aggressive inline HTTP calls when 99% of the time Zapier doesn’t need the data yet is wasteful.
Merging Hydrated Data
As you’ve seen, the usual call to dehydrate will assign the result to an object property:
In this example, all of the movie details will be located in the details
property (e.g. details.releaseDate
) after hydration occurs. But what if you want these results available at the top-level (e.g. releaseDate
)? Zapier supports a specific keyword for this scenario:
Using $HOIST$
as the key will signal to Zapier that the results should be merged into the object containing the $HOIST$
key. You can also use this to merge your hydrated data into a property containing “partial” data that exists before dehydration occurs:
File Dehydration
Added in v7.3.0.
The method z.dehydrateFile(func, inputData)
allows you to download a file lazily. It takes the same arguments as z.dehydrate(func, inputData)
does, but is recommended when the data is a file.
An example can be found in the Stashing Files section.
What makes z.dehydrateFile
different from z.dehydrate
has to do with efficiency and when Zapier chooses to hydrate data. Knowing which pointers give us back files helps us delay downloading files until it’s absolutely necessary. Not only will it help avoid unnecessary file downloads, it can also prevent errors if the file has a limited availability. (Stashing files, described below, can also help with that situation.)
A good example is when users are creating Zaps in the Zap Editor. If a pointer is made by z.dehydrate
, the Zap Editor will hydrate the data immediately after pulling in samples. This allows users to map fields from the hydrated data into the subsequent steps of the Zap. If, however, the pointer is made by z.dehydrateFile
, the Zap Editor will wait to hydrate the file, and will display a placeholder instead. There’s nothing inside binary file data for users to map in the subsequent steps.
z.dehydrateFile(func, inputData)
was added in v7.3.0. We used to recommend usingz.dehydrate(func, inputData)
for files, but we now recommend changing it toz.dehydrateFile(func, inputData)
for a better user experience.
Stashing Files
It can be expensive to download and stream files or they can require complex handshakes to authorize downloads - so we provide a helpful stash routine that will take any String
, Buffer
or Stream
and return a URL file pointer suitable for returning from triggers, searches, creates, etc.
The interface z.stashFile(bufferStringStream, [knownLength], [filename], [contentType])
takes a single required argument - the extra three arguments will be automatically populated in most cases. Here’s a full example:
Most likely you’d want to stream from another URL - note the usage of z.request({raw: true})
:
Note: you should only be using
z.stashFile()
in a hydration method or a hook trigger’sperform
if you’re sending over a short-lived URL to a file. Otherwise, it can be very expensive to stash dozens of files in a polling call - for example!
See a full example with dehydration/hydration wired in correctly:
To create a new integration for handling files, run
zapier init [your integration name] --template files
. You can also check out our working example integration here.
Logging
To view the logs for your integration, use the zapier logs
command.
There are three types of logs for a Zapier integration:
http
: logged automatically by Zapier on HTTP requestsbundle
: logged automatically on every method executionconsole
: manual logs viaz.console.log()
statements (see below for details)
Note that by default, this command will only fetch logs associated to your user.
For advanced logging options, including the option to fetch logs for other users or specific integration versions, look at the help for the logs command:
Console Logging
To manually print a log statement in your code, use z.console.log
:
The z.console
object has all the same methods and works just like the Node.js Console
class - the only difference is we’ll log to our distributed datastore and you can view the logs via zapier logs
(more below).
Viewing Console Logs
To see your z.console.log
logs, do:
Viewing Bundle Logs
To see the bundle logs, do:
HTTP Logging
If you are using shorthand HTTP requests or the z.request()
method that we provide, HTTP logging is handled automatically for you. For example:
HTTP logging will often work with other methods of making requests as well, but if you’re using another method and having trouble seeing logs, try using z.request()
.
Viewing HTTP Logs
To see the HTTP logs, do:
To see detailed HTTP logs, including data such as headers and request and response bodies, do:
Error Handling
APIs are not always available. Users do not always input data correctly to formulate valid requests. Thus, it is a good idea to write integrations defensively and plan for 4xx and 5xx responses from APIs. Without proper handling, errors often have incomprehensible messages for end users, or possibly go uncaught.
Zapier provides a couple of tools to help with error handling. First is the
afterResponse
middleware (docs), which provides a hook for
processing all responses from HTTP calls. Second is response.throwForStatus()
(docs), which throws an error if the response status indicates
an error (status >= 400). Since v10.0.0, we automatically call this method before returning the
response, unless you set skipThrowForStatus
on the request or response object. The
last tool is the collection of errors in z.errors
(docs), which control
the behavior of Zaps when various kinds of errors occur.
General Errors
Errors due to a misconfiguration in a user’s Zap should be handled in your integration
by throwing z.errors.Error
with a user-friendly message and optional error and
status code. Typically, this will be prettifying 4xx responses or APIs that return
errors as 200s with a payload that describes the error.
Example: throw new z.errors.Error('Contact name is too long.', 'InvalidData', 400);
z.errors.Error
was added in v9.3.0. If you’re on an older version ofzapier-platform-core
, throw a standard JavaScriptError
instead, such asthrow new Error('A user-friendly message')
.
A couple best practices to keep in mind:
- Elaborate on terse messages. “not_authenticated” -> “Your API Key is invalid. Please reconnect your account.”
- If the error calls out a specific field, surface that information to the user. “Provided data is invalid” -> “Contact name is invalid”
- If the error provides details about why a field is invalid, add that in too! “Contact name is invalid” -> “Contact name is too long”
- The second, optional argument should be a code that a computer could use to identify the type of error.
- The last, optional argument should be the HTTP status code, if any.
The code and status can be used by us to provide relevant troubleshooting to the user when we communicate the error.
Note that if a Zap raises too many error messages it will be automatically turned off, so only use these if the scenario is truly an error that needs to be fixed.
Halting Execution
Any operation can be interrupted or “halted” (not success, not error, but
stopped for some specific reason) with a HaltedError
. You might find yourself
using this error in cases where a required pre-condition is not met. For instance,
in a create to add an email address to a list where duplicates are not allowed,
you would want to throw a HaltedError
if the Zap attempted to add a duplicate.
This would indicate failure, but it would be treated as a soft failure.
Unlike throwing z.errors.Error
, a Zap will never by turned off when this error is thrown
(even if it is raised more often than not).
Example: throw new z.errors.HaltedError('Your reason.');
Stale Authentication Credentials
For integrations that require manual refresh of authorization on a regular basis, Zapier
provides a mechanism to notify users of expired credentials. With the
ExpiredAuthError
, the current operation is interrupted and a predefined email
is sent out asking the user to refresh the credentials. While the auth is
disconnected, Zap runs will not be executed, to prevent more calls with expired
credentials. (The runs will be
Held,
and the user will be able to replay them after reconnecting.)
Example: throw new z.errors.ExpiredAuthError('You must manually reconnect this auth.');
For integrations that use OAuth2 with autoRefresh: true
or Session Auth, core
injects
a built-in afterResponse
middleware that throws an error when the response status
is 401. The error will signal Zapier to refresh the credentials and then retry the
failed operation. You can also throw this error manually if your server doesn’t use the 401 status or you want to trigger an auth refresh even if the credentials aren’t stale.
Example: throw new z.errors.RefreshAuthError();
Handling Throttled Requests
Since v11.2.0, there are two types of errors that can cause Zapier to throttle an operation and retry at a later time. This is useful if the API you’re interfacing with reports it is receiving too many requests, often indicated by receiving a response status code of 429.
If a response receives a status code of 429 and is not caught, Zapier will re-attempt the operation after a delay.
The delay can be customized by the server response containing a specific
Retry-After header in your error response or with a specified time delay in seconds using a ThrottledError
:
Instead of a user’s Zap erroring and halting, the request will be repeatedly retried at the specified time.
For throttled requests that occur during processing of a webhook trigger’s perform, before results are produced; there is a max retry delay of 300 seconds and a default delay of 60 seconds if none is specified. For webhook processing only, if a request during the retry attempt is also throttled, it will not be re-attempted again.
Testing
There are several ways to test your Zapier integration:
- You can use the
zapier invoke
command to invoke a trigger, search, create, or an auth operation locally. - You can write unit tests for your Zapier integration that run locally, outside of the Zapier editor.
- You can run these tests in a CI tool like Travis.
Using zapier invoke
Command
Added in v15.17.0.
The zapier invoke <ACTION_TYPE> <ACTION_KEY>
CLI command emulates how the Zapier production environment would invoke your integration. Since it runs code locally, it’s a fast way to debug and test interactively without needing to deploy the code to Zapier.
Its general execution flow involves calling operation.inputFields
of an action, resolving the input data to the expected types, and then calling the operation.perform
method.
zapier invoke --help
should be self-explanatory, but here’s a quick rundown:
Writing Unit Tests
From v10 of zapier-platform-cli
, we recommend using the Jest testing framework. After running zapier init
you should find an example test to start from in the test
directory.
Note: On v9, the recommendation was Mocha. You can still use Mocha if you prefer.
Using the z
Object in Tests
Introduced in core@11.1.0
, appTester
can now run arbitrary functions:
Mocking Requests
It’s useful to test your code without actually hitting any external services. Nock is a Node.js utility that intercepts requests before they ever leave your computer. You can specify a response code, body, headers, and more. It works out of the box with z.request
by setting up your nock
before calling appTester
.
Here’s more info about nock and its usage in the README.
Running Unit Tests
To run all your tests do:
You can also go direct with
npm test
ornode_modules/.bin/jest
.
Testing & Environment Variables
The best way to store sensitive values (like API keys, OAuth secrets, or passwords) is in an .env
(or .environment
, see below note) file (learn more). Then, you can include the following before your tests run:
.env
is the new recommended name for the environment file since v5.1.0. The old name.environment
is deprecated but will continue to work for backward compatibility.
Remember: NEVER add your secrets file to version control!
Additionally, you can provide them dynamically at runtime:
Or, export
them explicitly and place them into the environment:
Testing in Your CI
Whether you use Travis, Circle, Jenkins, or another service, we aim to make it painless to test in an automated environment.
Behind the scenes zapier test
does a standard npm test
, which could be Jest or Mocha, based on your project setup.
This makes it straightforward to integrate into your testing interface. For example, if you want to test with Travis CI, the .travis.yml
would look something like this:
You can substitute zapier test
with npm test
, or a direct call to node_modules/.bin/jest
. We recommend putting environment variables directly into the configuration screens Jenkins, Travis, or other services provide.
Alternatively to reading the deploy key from root (the default location), you may set the ZAPIER_DEPLOY_KEY
environment variable to run privileged commands without the human input needed for zapier login
. We suggest encrypting your deploy key in the manner your CI provides (such as these instructions, for Travis).
Debugging Tests
Sometimes tests aren’t enough, and you may want to step through your code and set breakpoints. The testing suite is a regular Node.js process, so debugging it doesn’t take anything special. Because we recommend jest
for testing, these instructions will outline steps for debugging w/ jest, but other test runners will work similarly. You can also refer to Jest’s own docs on the subject.
To start, add the following line to the scripts
section of your package.json
:
This will tell node
to inspect the jest
processes, which is exactly what we need.
Next, add a debugger;
statement somewhere in your code, probably in a perform
method:
This creates a breakpoint while inspect
ing, or a starting point for our manual inspection.
Next, you’ll need an inspection client. The most available one is probably the Google Chrome browser, but there are lots of options. We’ll use Chrome for this example. In your terminal (and in your integration’s root directory), run yarn test:debug
(or npm run test:debug
). You should see the following:
Now in Chrome, go to chrome://inspect. Make sure Discover Network Targets
is checked and you should see a path to your jest
file on your local machine:
Click inspect
. A new window will open. Next, click the little blue arrow in the top right to actually run the code:
After a few seconds, you’ll see your code, the debugger
statement, and info about the current environment on the right panel. You should see familiar data in the Locals
section, such as the response
variable, and the z
object.
Debugging combined with thorough unit tests will hopefully equip you in keeping your Zapier integration in smooth working order.
Using npm
Modules
Use npm
modules just like you would use them in any other node app, for example:
And then package.json
will be updated, and you can use them like anything else:
During the zapier build
or zapier push
step - we’ll copy all your code to a temporary folder and do a fresh re-install of modules.
Note: If your package isn’t being pushed correctly (IE: you get “Error: Cannot find module ‘whatever’” in production), try adding the
--disable-dependency-detection
flag tozapier push
.
Note 2: You can also try adding a
includeInBuild
array property (with paths to include, which will be evaluated to RegExp, with a case insensitive flag) to your.zapierapprc
file, to make it look like:
Warning: Do not use compiled libraries unless you run your build on the AWS AMI
ami-4fffc834
, or follow the Docker instructions below.
Building Native Packages with Docker
Unfortunately if you are developing on a macOS or Windows box you won’t be able to build native libraries locally. If you try and push locally build native modules, you’ll get runtime errors during usage. However, you can use Docker and Docker Compose to do this in a pinch. Make sure you have all the necessary Docker programs installed and follow along.
First, create your Dockerfile
:
And finally, create your docker-compose.yml
file:
Note: Watch out for your
package-lock.json
file, if it exists for local install it might incorrectly pin a native version.
Now you should be able to run docker-compose run pusher
and see the build and push successfully complete!
Using Transpilers
If you would like to use a transpiler like babel
, you can add a script named _zapier-build
to your package.json
, which will be run during zapier build
,
zapier push
, and zapier upload
. See the following example:
Then, you can have your fancy ES7 code in src/*
and a root index.js
like this:
And work with commands like this:
There are a lot of details left out - check out the full example integration here.
We recommend using
zapier init .
to create an integration - you’ll be presented with a list of currently available example templates to start with.
Command Line Tab Completion
Introduced in v9.1.0, the zapier autocomplete
command shows instructions for generating command line autocomplete.
Follow those instructions to enable completion for zapier
commands and flags!
The Zapier Platform Packages
The Zapier Platform consists of 3 npm packages that are released simultaneously.
zapier-platform-cli
is the code that powers thezapier
command. You use it most commonly with thetest
,scaffold
, andpush
commands. It’s installed withnpm install -g zapier-platform-cli
and does not correspond to a particular integration.zapier-platform-core
is what allows your integration to interact with Zapier. It holds thez
object and integration tester code. Your integration depends on a specific version ofzapier-platform-core
in thepackage.json
file. It’s installed vianpm install
along with the rest of your integrations’s dependencies.zapier-platform-schema
enforces integration structure behind the scenes. It’s a dependency ofcore
, so it will be installed automatically.
To learn more about the structure of the code (especially if you’re interested in contributing), check out the ARCHITECTURE.md
file(s).
Updating These Packages
The Zapier platform and its tools are under active development. While you don’t need to install every release, we release new versions because they are better than the last. We do our best to adhere to Semantic Versioning wherein we won’t break your code unless there’s a major
release. Otherwise, we’re just fixing bugs (patch
) and adding features (minor
).
Broadly speaking, all releases will continue to work indefinitely. While you never have to upgrade your integration’s zapier-platform-core
dependency, we recommend keeping an eye on the changelog to see what new features and bug fixes are available.
For more info about which Node versions are supported, see the faq.
The most recently released version of cli
and core
is 15.18.1. You can see the versions you’re working with by running zapier -v
.
To update cli
, run npm install -g zapier-platform-cli
.
To update the version of core
your integration depends on, set the zapier-platform-core
dependency in your package.json
to a version listed here and reinstall your dependencies (either yarn
or npm install
).
For maximum compatibility, keep the versions of cli
and core
in sync.