zapier-platform-cli
The CLI for apps in the Zapier Developer Platform.
Last updated 20 days ago by zapier-engineering .
UNLICENSED · Repository · Bugs · Original npm · Tarball · package.json
$ cnpm install zapier-platform-cli 
SYNC missed versions from official npm registry.

Zapier
Zapier Platform CLI

Travis npm version

Zapier is a platform for creating integrations and workflows. This CLI is your gateway to creating custom applications on the Zapier platform.

These docs are available here, the CLI docs are available here, and you can view all the schema definitions here.

Table of Contents

Getting Started

If you're new to Zapier Platform CLI, we strongly recommend you to walk through the Tutorial for a more thorough introduction.

What is an App?

A CLI App is an implementation of your app's API. You build a Node.js application that exports a single object (JSON Schema) and upload it to Zapier. Zapier introspects that definition to find out what your app is capable of and what options to present end users in the Zap Editor.

For those not familiar with Zapier terminology, here is how concepts in the CLI map to the end user experience:

  • Authentication, (usually) which lets us know what credentials to ask users for. This is used during the "Connect Accounts" section of the Zap Editor.
  • Triggers, which read data from your API. These have their own section in the Zap Editor.
  • Creates, which send data to your API to create new records. These are listed under "Actions" in the Zap Editor.
  • Searches, which find specific records in your system. These are also listed under "Actions" in the Zap Editor.
  • Resources, which define an object type in your API (say a contact) and the operations available to perform on it. These are automatically extracted into Triggers, Searches, and Creates.

How does Zapier Platform CLI Work?

Zapier takes the App you upload and sends it over to Amazon Web Service's Lambda. We then make calls to execute the operations your App defines as we execute Zaps. Your App takes the input data we provide (if any), makes the necessary HTTP calls, and returns the relevant data, which gets fed back into Zapier.

Zapier Platform CLI vs UI

The Zapier Platform includes two ways to build integrations: a CLI to build integrations in your local development environment and deploy them from the command line, and a UI to create integrations with a visual builder from your browser. Both use the same Zapier platform, so pick the one that fits your team's needs best, as the difference is in how you develop the integration.

Zapier Platform CLI is designed to be used by development teams who collaborate with version control and CI, and can be used to build more advanced integrations with custom coding for every part of your API calls and response parsing.

Zapier Platform UI is designed to quickly spin up new integrations, and collaborate on them with teams that include non-developers. It's the quickest way to start using the Zapier platform—and you can manage your CLI apps' core details from its online UI as well. Coming soon, you will be able to convert Zapier Platform UI integrations to CLI to start development in your browser then finish out the core features in your local development environment.

→ Learn more in our Zapier Platform UI vs CLI post.

Requirements

All Zapier CLI apps are run using Node.js v8.10.0.

You can develop using any version of Node you'd like, but your eventual code must be compatible with v8.10.0. If you're using features not yet available in v8.10.0, 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 v8.10.0 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 apps to get you started.

Alternatively, you can change your local node version with tools such as nvm or n. Then you can either swap to that version with nvm use v8.10.0, or do nvm exec v8.10.0 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" application. It will be private to you and visible in your live Zap Editor.

# install the CLI globally
npm install -g zapier-platform-cli

# setup auth to Zapier's platform with a deploy key
zapier login

Your Zapier CLI should be installed and ready to go at this point. Next up, we'll create our first app!

# create a directory with the minimum required files
zapier init example-app

# move into the new directory
cd example-app

# install all the libraries needed for your app
npm install

Note: there are plenty of templates & example apps to choose from! View all Example Apps here..

You should now have a working local app. You can run several local commands to try it out.

# run the local tests
# the same as npm test, but adds some extra things to the environment
zapier test

Next, you'll probably want to upload app to Zapier itself so you can start testing live.

# push your app to Zapier
zapier push

Go check out our full CLI reference documentation to see all the other commands!

Tutorial

For a full tutorial, head over to our Tutorial for a comprehensive walkthrough for creating your first app. If this isn't your first rodeo, read on!

Creating a Local App

Tip: check the Quick Setup if this is your first time using the platform!

Creating an App 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 app stays local until you zapier register.

# make your folder
mkdir zapier-example
cd zapier-example

# create the needed files from a template
zapier init . --template=trigger

# install all the libraries needed for your app
npm install

If you'd like to manage your local App, use these commands:

  • zapier init . --template=resource - initialize/start a local app project (see templates here)
  • zapier convert 1234 . - initialize/start from an existing app (alpha)
  • zapier scaffold resource Contact - auto-injects a new resource, trigger, etc.
  • zapier test - run the same tests as npm test
  • zapier validate - ensure your app is valid
  • zapier describe - print some helpful information about your app

Local Project Structure

In your app's folder, you should see this general recommended structure. The index.js is Zapier's entry point to your app. Zapier expects you to export an App definition there.

$ tree .
.
├── README.md
├── index.js
├── package.json
├── triggers
│   └── contact-by-tag.js
├── resources
│   └── Contact.js
├── test
│   ├── basic.js
│   ├── triggers.js
│   └── resources.js
├── build
│   └── build.zip
└── node_modules
    ├── ...
    └── ...

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:

const App = {
  // both version strings are required
  version: require('./package.json').version,
  platformVersion: require('zapier-platform-core').version,

  // see "Authentication" section below
  authentication: {},

  // see "Dehydration" section below
  hydrators: {},

  // see "Making HTTP Requests" section below
  requestTemplate: {},
  beforeRequest: [],
  afterResponse: [],

  // See "Resources" section below
  resources: {},

  // See "Triggers/Searches/Creates" section below
  triggers: {},
  searches: {},
  creates: {}
};

module.exports = App;

Tip: you can use higher order functions to create any part of your App definition!

Registering an App

Registering your App 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 App Version in the Zapier interface and editor.

# register your app
zapier register "Zapier Example"

# list your apps
zapier apps

Note: this doesn't put your app in the editor - see the docs on pushing an App Version to do that!

If you'd like to manage your App, use these commands:

  • zapier apps - list the apps in Zapier you can administer
  • zapier register "Name" - creates a new app in Zapier
  • zapier link - lists and links a selected app in Zapier to your current folder
  • zapier history - print the history of your app
  • zapier collaborate [user@example.com] - add admins to your app who can push
  • zapier invite [user@example.com] [1.0.0] - add users to try your app version 1.0.0 before promotion

Deploying an App Version

An App Version is related to a specific App but is an "immutable" implementation of your app. This makes it easy to run multiple versions for multiple users concurrently. The App Version is pulled from the version within the package.json. To create a new App Version, update the version number in that file. By default, every App Version is private but you can zapier promote it to production for use by over 1 million Zapier users.

# push your app version to Zapier
zapier push

# list your versions
zapier versions

If you'd like to manage your Version, use these commands:

  • zapier versions - list the versions for the current directory's app
  • zapier push - push the current version of current directory's app & version (read from package.json)
  • zapier promote [1.0.0] - mark a version as the "production" version
  • zapier migrate [1.0.0] [1.0.1] [100%] - move users between versions, regardless of deployment status
  • zapier deprecate [1.0.0] [YYYY-MM-DD] - mark a version as deprecated, but let users continue to use it (we'll email them)
  • zapier env 1.0.0 [KEY] [value] - set an environment variable to some value
  • zapier 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 want deprecate instead.

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 App Version (default)

A simple zapier push will only create the App Version in your editor. No one else using Zapier can see it or use it.

Sharing an App Version

This is how you would share your app with friends, co-workers or clients. This is perfect for quality assurance, testing with active users or just sharing any app you like.

# sends an email this user to let them view the app version 1.0.0 in the UI privately
zapier invite user@example.com 1.0.0

# sends an email this user to let them admin the app (make changes just like you)
zapier collaborate user@example.com

You can also invite anyone on the internet to your app by observing the URL at the bottom of zapier invite, it 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. Note this will invite users to every app version.

Promoting an App Version

Promotion is how you would share your app 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 app.

If this isn't the first time you've promoted your app - 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.

# promote your app version to all Zapier users
zapier promote 1.0.1

# OPTIONAL - migrate your users between one app version to another
zapier migrate 1.0.0 1.0.1

# OR - mark the old version as deprecated
zapier deprecate 1.0.0 2017-01-01

Converting an Existing App

If you have an existing Zapier legacy Web Builder app, you can use it as a template to kickstart your local application.

# Convert an existing Web Builder app to a CLI app in the my-app directory
# App ID 1234 is from URL https://zapier.com/developer/builder/app/1234/development
zapier convert 1234 my-app

Your CLI app will be created and you can continue working on it.

Since v3.3.0, zapier convert has been improved a lot. But this is still in an alpha state - you'll likely have to edit the code to make it work.

Note - there is no way to convert a CLI app to a Web Builder app and we do not plan on implementing this.

Coming soon, you will also be able to convert new integrations built in Zapier Platform UI to CLI.

Authentication

Most applications require some sort of authentication - and Zapier provides a handful of methods for helping your users authenticate with your application. Zapier will provide some of the core behaviors, but you'll likely need to handle the rest.

Hint: You can access the data tied to your authentication via the bundle.authData property in any method called in your app. Exceptions exist in OAuth and Session auth. Please see them below.

Basic

Useful if your app requires two pieces of information to authentication: 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).

Example App: check out https://github.com/zapier/zapier-platform-example-app-basic-auth for a working example app for basic auth.

Note: if you do the common API Key pattern like Authorization: Basic APIKEYHERE:x you should look at the "Custom" authentication method instead.

const authentication = {
  type: 'basic',
  // "test" could also be a function
  test: {
    url: 'https://example.com/api/accounts/me.json'
  },
  connectionLabel: '{{bundle.authData.username}}' // Can also be a function, check digest auth below for an example
  // you can provide additional fields, but we'll provide `username`/`password` automatically
};

const App = {
  // ...
  authentication: authentication
  // ...
};

Digest

New in v7.4.0.

The setup and user experience of Digest Auth is identical to Basic Auth. Users will provide Zapier their username and password and Zapier will handle all the nonce and quality of protection details automatically.

Example App: check out https://github.com/zapier/zapier-platform-example-app-digest-auth for a working example app for digest auth.

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 sends an additional request beforehand to get the server nonce.

const authentication = {
  type: 'digest',
  // "test" could also be a function
  test: {
    url: 'https://example.com/api/accounts/me.json'
  },
  connectionLabel: '{{bundle.authData.username}}' // Can also be a function, check digest auth below for an example
  // you can provide additional fields, but we'll provide `username`/`password` automatically
};

const App = {
  // ...
  authentication: authentication
  // ...
};

Custom

This is what most "API Key" driven apps should default to using. You'll likely provide some custom beforeRequest middleware or a requestTemplate to complete the authentication by adding/computing needed headers.

Example App: check out https://github.com/zapier/zapier-platform-example-app-custom-auth for a working example app for custom auth.

const authentication = {
  type: 'custom',
  // "test" could also be a function
  test: {
    url:
      'https://{{bundle.authData.subdomain}}.example.com/api/accounts/me.json'
  },
  fields: [
    {
      key: 'subdomain',
      type: 'string',
      required: true,
      helpText: 'Found in your browsers address bar after logging in.'
    },
    {
      key: 'api_key',
      type: 'string',
      required: true,
      helpText: 'Found on your settings page.'
    }
  ]
};

const addApiKeyToHeader = (request, z, bundle) => {
  request.headers['X-Subdomain'] = bundle.authData.subdomain;
  const basicHash = Buffer.from(`${bundle.authData.api_key}:x`).toString(
    'base64'
  );
  request.headers.Authorization = `Basic ${basicHash}`;
  return request;
};

const App = {
  // ...
  authentication: authentication,
  beforeRequest: [addApiKeyToHeader]
  // ...
};

Session

Probably the most "powerful" mechanism for authentication - it gives you the ability to exchange some user provided data for some authentication data (IE: username & password for a session key).

Example App: check out https://github.com/zapier/zapier-platform-example-app-session-auth for a working example app for session auth.

const getSessionKey = (z, bundle) => {
  const promise = z.request({
    method: 'POST',
    url: 'https://example.com/api/accounts/login.json',
    body: {
      username: bundle.authData.username,
      password: bundle.authData.password
    }
  });

  return promise.then(response => {
    if (response.status === 401) {
      throw new Error('The username/password you supplied is invalid');
    }
    return {
      sessionKey: z.JSON.parse(response.content).sessionKey
    };
  });
};

const authentication = {
  type: 'session',
  // "test" could also be a function
  test: {
    url: 'https://example.com/api/accounts/me.json'
  },
  fields: [
    {
      key: 'username',
      type: 'string',
      required: true,
      helpText: 'Your login username.'
    },
    {
      key: 'password',
      type: 'string',
      required: true,
      helpText: 'Your login password.'
    }
    // For Session Auth we store `sessionKey` automatically in `bundle.authData`
    // for future use. If you need to save/use something that the user shouldn't
    // need to type/choose, add a "computed" field, like:
    // {key: 'something': type: 'string', required: false, computed: true}
    // And remember to return it in sessionConfig.perform
  ],
  sessionConfig: {
    perform: getSessionKey
  }
};

const includeSessionKeyHeader = (request, z, bundle) => {
  if (bundle.authData.sessionKey) {
    request.headers = request.headers || {};
    request.headers['X-Session-Key'] = bundle.authData.sessionKey;
  }
  return request;
};

const sessionRefreshIf401 = (response, z, bundle) => {
  if (bundle.authData.sessionKey) {
    if (response.status === 401) {
      throw new z.errors.RefreshAuthError(); // ask for a refresh & retry
    }
  }
  return response;
};

const App = {
  // ...
  authentication: authentication,
  beforeRequest: [includeSessionKeyHeader],
  afterResponse: [sessionRefreshIf401]
  // ...
};

Note - For Session auth, authentication.sessionConfig.perform will have the provided fields in bundle.inputData instead of bundle.authData because bundle.authData will only have "previously existing" values, which will be empty the first time the Zap runs.

OAuth1

New in v7.5.0.

Zapier's OAuth1 implementation matches Twitter's and Trello's implementation of the 3-legged OAuth flow.

Example Apps: check out oauth1-trello, oauth1-tumblr, and oauth1-twitter for working example apps with OAuth1.

The flow works like this:

  1. Zapier makes a call to your API requesting a "request token" (also known as "temporary credentials")
  2. Zapier sends the user to the authorization URL, defined by your app, along with the request token
  3. Once authorized, your website sends the user to the redirect_uri Zapier provided. Use zapier describe command to find out what it is:
  4. Zapier makes a call on our backend to your API to exchange the request token for an "access token" (also known as "long-lived credentials")
  5. Zapier remembers the access token and makes calls on behalf of the user

You are required to define:

  • getRequestToken: The API call to fetch the request token
  • authorizeUrl: The authorization URL
  • getAccessToken: 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.

# setting the environment variables on Zapier.com
$ zapier env 1.0.0 CLIENT_ID 1234
$ zapier env 1.0.0 CLIENT_SECRET abcd

# and when running tests locally, don't forget to define them in .env or in the command!
$ CLIENT_ID=1234 CLIENT_SECRET=abcd zapier test

Your auth definition would look something like this:

const _ = require('lodash');

const authentication = {
  type: 'oauth1',
  oauth1Config: {
    getRequestToken: {
      url: 'https://{{bundle.inputData.subdomain}}.example.com/request-token',
      method: 'POST',
      auth: {
        oauth_consumer_key: '{{process.env.CLIENT_ID}}',
        oauth_consumer_secret: '{{process.env.CLIENT_SECRET}}',

        // 'HMAC-SHA1' is used by default if not specified.
        // 'HMAC-SHA256', 'RSA-SHA1', 'PLAINTEXT' are also supported.
        oauth_signature_method: 'HMAC-SHA1',
        oauth_callback: '{{bundle.inputData.redirect_uri}}'
      }
    },
    authorizeUrl: {
      url: 'https://{{bundle.inputData.subdomain}}.example.com/authorize',
      params: {
        oauth_token: '{{bundle.inputData.oauth_token}}'
      }
    },
    getAccessToken: {
      url: 'https://{{bundle.inputData.subdomain}}.example.com/access-token',
      method: 'POST',
      auth: {
        oauth_consumer_key: '{{process.env.CLIENT_ID}}',
        oauth_consumer_secret: '{{process.env.CLIENT_SECRET}}',
        oauth_token: '{{bundle.inputData.oauth_token}}',
        oauth_token_secret: '{{bundle.inputData.oauth_token_secret}}',
        oauth_verifier: '{{bundle.inputData.oauth_verifier}}'
      }
    }
  },
  test: {
    url: 'https://{{bundle.authData.subdomain}}.example.com/me'
  },
  // If you need any fields upfront, put them here
  fields: [
    { key: 'subdomain', type: 'string', required: true, default: 'app' }
    // For OAuth1 we store `oauth_token` and `oauth_token_secret` automatically
    // in `bundle.authData` for future use. If you need to save/use something
    // that the user shouldn't need to type/choose, add a "computed" field, like:
    // {key: 'user_id': type: 'string', required: false, computed: true}
    // And remember to return it in oauth1Config.getAccessToken
  ]
};

// A middleware that is run before z.request() actually makes the request. Here we're
// adding necessary OAuth1 parameters to `auth` property of the request object.
const includeAccessToken = (req, z, bundle) => {
  if (
    bundle.authData &&
    bundle.authData.oauth_token &&
    bundle.authData.oauth_token_secret
  ) {
    // Just put your OAuth1 credentials in req.auth, Zapier will sign the request for
    // you.
    req.auth = req.auth || {};
    _.defaults(req.auth, {
      oauth_consumer_key: process.env.CLIENT_ID,
      oauth_consumer_secret: process.env.CLIENT_SECRET,
      oauth_token: bundle.authData.oauth_token,
      oauth_token_secret: bundle.authData.oauth_token_secret
    });
  }
  return req;
};

const App = {
  // ...
  authentication: authentication,
  beforeRequest: [includeAccessToken]
  // ...
};

module.exports = App;

Note - For OAuth1, authentication.oauth1Config.getRequestToken, authentication.oauth1Config.authorizeUrl, and authentication.oauth1Config.getAccessToken will have the provided fields in bundle.inputData instead of bundle.authData because bundle.authData will only have "previously existing" values, which will be empty when the user hasn't connected their account on your service to Zapier. Also note that authentication.oauth1Config.getAccessToken has access to the users 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.

Example App: check out https://github.com/zapier/zapier-platform-example-app-oauth2 for a working example app for OAuth2.

It looks like this:

  1. Zapier sends the user to the authorization URL defined by your app
  2. Once authorized, your website sends the user to the redirect_uri Zapier provided. Use zapier describe command to find out what it is:
  3. Zapier makes a call on our backend to your API to exchange the code for an access_token
  4. Zapier remembers the access_token and makes calls on behalf of the user
  5. (Optionally) Zapier can refresh the token if it expires

You are required to define the authorization URL and the API call to fetch the access token. You'll also likely want to set your CLIENT_ID and CLIENT_SECRET as environment variables:

# setting the environment variables on Zapier.com
$ zapier env 1.0.0 CLIENT_ID 1234
$ zapier env 1.0.0 CLIENT_SECRET abcd

# and when running tests locally, don't forget to define them in .env or in the command!
$ CLIENT_ID=1234 CLIENT_SECRET=abcd zapier test

Your auth definition would look something like this:

const authentication = {
  type: 'oauth2',
  test: {
    url:
      'https://{{bundle.authData.subdomain}}.example.com/api/accounts/me.json'
  },
  // you can provide additional fields for inclusion in authData
  oauth2Config: {
    // "authorizeUrl" could also be a function returning a string url
    authorizeUrl: {
      method: 'GET',
      url:
        'https://{{bundle.inputData.subdomain}}.example.com/api/oauth2/authorize',
      params: {
        client_id: '{{process.env.CLIENT_ID}}',
        state: '{{bundle.inputData.state}}',
        redirect_uri: '{{bundle.inputData.redirect_uri}}',
        response_type: 'code'
      }
    },
    // Zapier expects a response providing {access_token: 'abcd'}
    // "getAccessToken" could also be a function returning an object
    getAccessToken: {
      method: 'POST',
      url:
        'https://{{bundle.inputData.subdomain}}.example.com/api/v2/oauth2/token',
      body: {
        code: '{{bundle.inputData.code}}',
        client_id: '{{process.env.CLIENT_ID}}',
        client_secret: '{{process.env.CLIENT_SECRET}}',
        redirect_uri: '{{bundle.inputData.redirect_uri}}',
        grant_type: 'authorization_code'
      },
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      }
    },
    scope: 'read,write'
  },
  // If you need any fields upfront, put them here
  fields: [
    { key: 'subdomain', type: 'string', required: true, default: 'app' }
    // For OAuth2 we store `access_token` and `refresh_token` automatically
    // in `bundle.authData` for future use. If you need to save/use something
    // that the user shouldn't need to type/choose, add a "computed" field, like:
    // {key: 'user_id': type: 'string', required: false, computed: true}
    // And remember to return it in oauth2Config.getAccessToken/refreshAccessToken
  ]
};

const addBearerHeader = (request, z, bundle) => {
  if (bundle.authData && bundle.authData.access_token) {
    request.headers.Authorization = `Bearer ${bundle.authData.access_token}`;
  }
  return request;
};

const App = {
  // ...
  authentication: authentication,
  beforeRequest: [addBearerHeader]
  // ...
};

module.exports = App;

Note - For OAuth2, authentication.oauth2Config.authorizeUrl, authentication.oauth2Config.getAccessToken, and authentication.oauth2Config.refreshAccessToken will have the provided fields in bundle.inputData instead of bundle.authData because bundle.authData will only have "previously existing" values, which will be empty when the user hasn't connected their account on your service to Zapier. Also note that authentication.oauth2Config.getAccessToken has access to the users return values in rawRequest and cleanedRequest should you need to extract other values (for example from the query string).

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 app that will tell Zapier how to do create, read, and search operations on that resource.

const Recipe = {
  // `key` is the unique identifier the Zapier backend references
  key: 'recipe',
  // `noun` is the user-friendly name displayed in the Zapier UI
  noun: 'Recipe',
  // `list` and `create` are just a couple of the methods you can define
  list: {
    // ...
  },
  create: {
    // ...
  }
};

The quickest way to create a resource is with the zapier scaffold command:

zapier scaffold resource "Recipe"

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.

Example App: check out https://github.com/zapier/zapier-platform-example-app-resource for a working example app using resources.

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

const listRecipesRequest = {
  url: 'http://example.com/recipes'
};

const Recipe = {
  key: 'recipe',
  // ...
  list: {
    display: {
      label: 'New Recipe',
      description: 'Triggers when a new recipe is added.'
    },
    operation: {
      perform: listRecipesRequest
    }
  }
};

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.

const createRecipeRequest = {
  url: 'http://example.com/recipes',
  method: 'POST',
  body: {
    name: 'Baked Falafel',
    style: 'mediterranean'
  }
};

const Recipe = {
  key: 'recipe',
  // ...
  list: {
    // ...
  },
  create: {
    display: {
      label: 'Add Recipe',
      description: 'Adds a new recipe to our cookbook.'
    },
    operation: {
      perform: createRecipeRequest
    }
  }
};

Every method you define on a resource Zapier converts to the appropriate Trigger, Create, or Search. Our examples above would result in an app 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 app 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:

const recipeListRequest = {
  url: 'http://example.com/recipes'
};

const App = {
  // ...
  triggers: {
    new_recipe: {
      key: 'new_recipe', // uniquely identifies the trigger
      noun: 'Recipe', // user-friendly word that is used to refer to the resource
      // `display` controls the presentation in the Zapier Editor
      display: {
        label: 'New Recipe',
        description: 'Triggers when a new recipe is added.'
      },
      // `operation` implements the API call used to fetch the data
      operation: {
        perform: recipeListRequest
      }
    },
    another_trigger: {
      // Another trigger definition...
    }
  }
};

You can find more details on the definition for each by looking at the Trigger Schema, Search Schema, and Create Schema.

Example App: check out https://github.com/zapier/zapier-platform-example-app-trigger for a working example app using triggers.

Example App: check out https://github.com/zapier/zapier-platform-example-app-rest-hooks for a working example app using REST hook triggers.

Example App: check out https://github.com/zapier/zapier-platform-example-app-search for a working example app using searches.

Example App: check out https://github.com/zapier/zapier-platform-example-app-create for a working example app using creates.

Return Types

Each of the 3 types of function expects a certain type of object. As of core v1.0.11, there are automated checks to let you know when you're trying to pass the wrong type back. There's more info in each relevant post_X section of the v2 docs. For reference, each expects:

Method Return Type Notes
Trigger Array 0 or more objects that will be passed to the deduper
Search Array 0 or more objects. If len > 0, put the best match first
Action Object Return values are evaluated by isPlainObject

Input Fields

On each trigger, search, or create in the operation directive - you can provide an array of objects as fields under the inputFields. Input Fields are what your users would see in the main Zapier user interface. For example, you might have a "Create Contact" action with fields like "First name", "Last name", "Email", etc. These fields will be able to accept input from previous steps in a Zap, for example:

gif of setting up an action field in Zap Editor

You can find more details about setting action fields from a user perspective in our help documentation.

Those fields have various options you can provide, here is a succinct example:

const App = {
  // ...
  creates: {
    create_recipe: {
      // ...
      operation: {
        // an array of objects is the simplest way
        inputFields: [
          {
            key: 'title',
            required: true,
            label: 'Title of Recipe',
            helpText: 'Name your recipe!'
          },
          {
            key: 'style',
            required: true,
            choices: { mexican: 'Mexican', italian: 'Italian' }
          }
        ],
        perform: () => {}
      }
    }
  }
};

You can find more details on the different field schema options at our Field Schema.

Custom/Dynamic Fields

In some cases, it might be necessary to provide fields that are dynamically generated - especially for custom fields. This is a common pattern for CRMs, form software, databases and more. Basically - you can provide a function instead of a field and we'll evaluate that function - merging the dynamic fields with the static fields.

You should see bundle.inputData partially filled in as users provide data - even in field retrieval. This allows you to build hierarchical relationships into fields (EG: only show issues from the previously selected project).

A function that returns a list of dynamic fields cannot include additional functions in that list to call for dynamic fields.

const recipeFields = (z, bundle) => {
  const response = z.request('http://example.com/api/v2/fields.json');
  // json is is [{"key":"field_1"},{"key":"field_2"}]
  return response.then(res => res.json);
};

const App = {
  // ...
  creates: {
    create_recipe: {
      // ...
      operation: {
        // an array of objects is the simplest way
        inputFields: [
          {
            key: 'title',
            required: true,
            label: 'Title of Recipe',
            helpText: 'Name your recipe!'
          },
          {
            key: 'style',
            required: true,
            choices: { mexican: 'Mexican', italian: 'Italian' }
          },
          recipeFields // provide a function inline - we'll merge the results!
        ],
        perform: () => {}
      }
    }
  }
};

Additionally, if there is a field that affects the generation of dynamic fields, you can set the altersDynamicFields: true property. This informs the Zapier UI that whenever the value of that field changes, fields need to be recomputed. An example could be a static dropdown of "dessert type" that will change whether the function that generates dynamic fields includes a field "with sprinkles." If your field affects others, this is an important property to set.

module.exports = {
  key: 'dessert',
  noun: 'Dessert',
  display: {
    label: 'Order Dessert',
    description: 'Orders a dessert.'
  },
  operation: {
    inputFields: [
      {
        key: 'type',
        required: true,
        choices: { 1: 'cake', 2: 'ice cream', 3: 'cookie' },
        altersDynamicFields: true
      },
      function(z, bundle) {
        if (bundle.inputData.type === '2') {
          return [{ key: 'with_sprinkles', type: 'boolean' }];
        }
        return [];
      }
    ],
    perform: function(z, bundle) {
      /* ... */
    }
  }
};

Only dropdowns support altersDynamicFields.

Dynamic Dropdowns

Sometimes, API endpoints require clients to specify a parent object in order to create or access the child resources. For instance, specifying a spreadsheet id in order to retrieve its worksheets. Since people don't speak in auto-incremented ID's, it is necessary that Zapier offer a simple way to select that parent using human readable handles.

Our solution is to present users a dropdown that is populated by making a live API call to fetch a list of parent objects. We call these special dropdowns "dynamic dropdowns."

To define one you include the dynamic property on the inputFields object. The value for the property is a dot-separated string concatenation.

//...
issue: {
  key: 'issue',
  //...
  create: {
    //...
    operation: {
      inputFields: [
        {
          key: 'project_id',
          required: true,
          label: 'This is a dynamic dropdown',
          dynamic: 'project.id.name'
        }, // will call the trigger with a key of project
        {
          key: 'title',
          required: true,
          label: 'Title',
          helpText: 'What is the name of the issue?'
        }
      ]
    }
  }
}

The dot-separated string concatenation follows this pattern:

  • The key of the trigger you want to use to power the dropdown. required
  • The value to be made available in bundle.inputData. required
  • The human friendly value to be shown on the left of the dropdown in bold. optional

In the above code example the dynamic property makes reference to a trigger with a key of project. Assuming the project trigger returns an array of objects and each object contains an id and name key, i.e.

[
  { id: '1', name: 'First Option', dateCreated: '01/01/2000' },
  { id: '2', name: 'Second Option', dateCreated: '01/01/2000' },
  { id: '3', name: 'Third Option', dateCreated: '01/01/2000' },
  { id: '4', name: 'Fourth Option', dateCreated: '01/01/2000' }
];

The dynamic dropdown would look something like this. screenshot of dynamic dropdown in Zap Editor

In the first code example the dynamic dropdown is powered by a trigger. You can also use a resource to power a dynamic dropdown. To do this combine the resource key and the resource method using camel case.

const App = {
  //...
  resources: {
    project: {
      key: 'project',
      //...
      list: {
        //...
        operation: {
          perform: () => {
            return [{ id: 123, name: 'Project 1' }];
          } // called for project_id dropdown
        }
      }
    },
    issue: {
      key: 'issue',
      //...
      create: {
        //...
        operation: {
          inputFields: [
            {
              key: 'project_id',
              required: true,
              label: 'Project',
              dynamic: 'projectList.id.name'
            }, // calls project.list
            {
              key: 'title',
              required: true,
              label: 'Title',
              helpText: 'What is the name of the issue?'
            }
          ]
        }
      }
    }
  }
};

In some cases you will need to power a dynamic dropdown but do not want to make the Trigger available to the end user. Here it is best practice to create the trigger and set hidden: true on it's display object.

const App = {
  //...
  triggers: {
    new_project: {
      key: 'project',
      noun: 'Project',
      // `display` controls the presentation in the Zapier Editor
      display: {
        label: 'New Project',
        description: 'Triggers when a new project is added.',
        hidden: true
      },
      operation: {
        perform: projectListRequest
      }
    },
    another_trigger: {
      // Another trigger definition...
    }
  }
};

You can have multiple dynamic dropdowns in a single Trigger or Action. And a dynamic dropdown can depend on the value chosen in another dynamic dropdown when making it's API call. Such as a Spreadsheet and Worksheet dynamic dropdown in a trigger or action. This means you must make sure that the key of the first dynamic dropdown is the same as referenced in the trigger powering the second.

Let's say you have a Worksheet trigger with a perform method similar to this.

perform: () => {
  return z
    .request('http://example.com/api/v2/projects.json', {
      params: {
        spreadsheet_id: bundle.inputData.spreadsheet_id
      }
    })
    .then(response => z.JSON.parse(response.content));
};

And your New Records trigger has a Spreadsheet and a Worksheet dynamic dropdown. The Spreadsheet dynamic dropdown must have a key of spreadsheet_id. When the user selects a spreadsheet via the dynamic dropdown the value chosen is made available in bundle.inputData. It will then be passed to the Worksheet trigger when the user clicks on the Worksheet dynamic dropdown.

const App = {
  //...
  triggers: {
    //...
    issue: {
      key: 'new_records',
      //...
      create: {
        //...
        operation: {
          inputFields: [
            {
              key: 'spreadsheet_id',
              required: true,
              label: 'Spreadsheet',
              dynamic: 'spreadsheet.id.name'
            },
            {
              key: 'worksheet_id',
              required: true,
              label: 'Worksheet',
              dynamic: 'worksheet.id.name'
            }
          ]
        }
      }
    }
  }
};

The Google Sheets integration is an example of this pattern.

If you want your trigger to perform specific scripting for a dynamic dropdown you will need to make use of bundle.meta.isFillingDynamicDropdown. This can be useful if need to make use of pagination in the dynamic dropdown to load more options.

const App = {
  //...
  resources: {
    project: {
      key: 'project',
      //...
      list: {
        //...
        operation: {
          canPaginate: true,
          perform: () => {
            if (bundle.meta.isFillingDynamicDropdown) {
              // perform pagination request here
            } else {
              return [{ id: 123, name: 'Project 1' }];
            }
          }
        }
      }
    },
    issue: {
      key: 'issue',
      //...
      create: {
        //...
        operation: {
          inputFields: [
            {
              key: 'project_id',
              required: true,
              label: 'Project',
              dynamic: 'projectList.id.name'
            }, // calls project.list
            {
              key: 'title',
              required: true,
              label: 'Title',
              helpText: 'What is the name of the issue?'
            }
          ]
        }
      }
    }
  }
};

Search-Powered Fields

For fields that take id of another object to create a relationship between the two (EG: a project id for a ticket), you can specify the search property on the field to indicate that Zapier needs to prompt the user to setup a Search step to populate the value for this field. Similar to dynamic dropdowns, the value for this property is a dot-separated concatenation of a search's key and the field to use for the value.

const App = {
  // ...
  resources: {
    project: {
      key: 'project',
      // ...
      search: {
        // ...
        operation: {
          perform: () => {
            return [{ id: 123, name: 'Project 1' }];
          } // called for project_id
        }
      }
    },
    issue: {
      key: 'issue',
      // ...
      create: {
        // ...
        operation: {
          inputFields: [
            {
              key: 'project_id',
              required: true,
              label: 'Project',
              dynamic: 'projectList.id.name',
              search: 'projectSearch.id'
            }, // calls project.search (requires a trigger in the "dynamic" property)
            {
              key: 'title',
              required: true,
              label: 'Title',
              helpText: 'What is the name of the issue?'
            }
          ]
        }
      }
    }
  }
};

NOTE: This has to be combined with the dynamic property to give the user a guided experience when setting up a Zap.

If you don't define a trigger for the dynamic property, the search connector won't show.

Computed Fields

In OAuth and Session Auth, Zapier automatically stores every value from an integration’s auth API response i.e. that’s getAccessToken and refreshAccessToken for OAuth and getSessionKey for session auth.

You can return additional fields in these responses, on top of the expected access_token or refresh_token for OAuth and sessionKey for Session auth. They will be saved in bundle.authData. You can reference these fields in any subsequent API call as needed.

If you want Zapier to validate that these additional fields exist, you need to use Computed Fields. If you define computed fields in your integration, Zapier will check to make sure those fields exist when it runs the authentication test API call.

Computed fields work like any other field, though with computed: true property, and required: false as user can not enter computed fields themselves. Reference computed fields in API calls as {{bundle.authData.field}}, replacing field with that field's name from your test API call response.

You can see examples of computed fields in the OAuth2 or Session Auth example sections.

Nested & Children (Line Item) Fields

When your action needs to accept an array of items, you can include an input field with the children attribute. The children attribute accepts a list of fields that can be input for each item in this array.

const App = {
  // ...
  operation: {
    // ...
    inputFields: [
      {
        key: 'lineItems',
        required: true,
        children: [
          {
            key: 'lineItemId',
            type: 'integer',
            label: 'Line Item ID',
            required: true
          },
          {
            key: 'name',
            type: 'string',
            label: 'Name',
            required: true
          },
          {
            key: 'description',
            type: 'string',
            label: 'Description'
          }
        ]
      }
    ]
    // ...
  }
};

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 your users would 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.

The schema for outputFields is shared with inputFields but only the key and required properties are relevant.

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}}.

const recipeOutputFields = (z, bundle) => {
  const response = z.request('http://example.com/api/v2/fields.json');
  // json is like [{"key":"field_1","label":"Label for Custom Field"}]
  return response.then(res => z.JSON.parse(res.content));
};

const App = {
  // ...
  triggers: {
    new_recipe: {
      // ...
      operation: {
        perform: () => {},
        sample: {
          id: 1,
          title: 'Pancake',
          author: {
            id: 1,
            name: 'Amy'
          },
          ingredients: [
            {
              name: 'Egg',
              amount: 1
            },
            {
              name: 'Milk',
              amount: 60,
              unit: 'g'
            },
            {
              name: 'Flour',
              amount: 30,
              unit: 'g'
            }
          ]
        },
        // an array of objects is the simplest way
        outputFields: [
          {
            key: 'id',
            label: 'Recipe ID',
            type: 'integer'
          },
          {
            key: 'title',
            label: 'Recipe Title',
            type: 'string'
          },
          {
            key: 'author__id',
            label: 'Author User ID',
            type: 'integer'
          },
          {
            key: 'author__name',
            label: 'Author Name',
            type: 'string'
          },
          {
            key: 'ingredients[]name',
            label: 'Ingredient Name',
            type: 'string'
          },
          {
            key: 'ingredients[]amount',
            label: 'Ingredient Amount',
            type: 'number'
          },
          {
            key: 'ingredients[]unit',
            label: 'Ingredient Unit',
            type: 'string'
          },
          recipeOutputFields // provide a function inline - we'll merge the results!
        ]
      }
    }
  }
};

Z Object

We provide several methods off of the z object, which is provided as the first argument to all function calls in your app.

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.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:

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.

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:

{
  createdBy: 'his name is Bobby Flay'
  style: 'he cooks mediterranean'
}

bundle.inputDataRaw

bundle.inputDataRaw is kind of like inputData, but before rendering {{curlies}}:

{
  createdBy: 'his name is {{123__chef_name}}'
  style: 'he cooks {{456__style}}'
}

"curlies" are 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

Before version 8.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.

There's also bundle.meta.zap.id, which is only available in the performSubscribe and performUnsubscribe methods.

The user's Zap ID is available during the subscribe and unsubscribe methods.

For example - you could do:

Current Tags

  • 8.4.2                                ...           latest (20 days ago)

84 Versions

  • 8.4.2                                ...           20 days ago
  • 8.4.1                                ...           a month ago
  • 8.4.0                                ...           a month ago
  • 8.3.0                                ...           3 months ago
  • 8.2.1                                ...           5 months ago
  • 8.2.0                                ...           6 months ago
  • 8.1.0                                ...           7 months ago
  • 8.0.1                                ...           8 months ago
  • 8.0.0                                ...           8 months ago
  • 7.6.1                                ...           10 months ago
  • 7.6.0                                ...           a year ago
  • 7.5.0                                ...           a year ago
  • 7.4.0                                ...           a year ago
  • 7.3.0                                ...           a year ago
  • 7.2.2                                ...           a year ago
  • 7.2.1                                ...           a year ago
  • 7.2.0                                ...           a year ago
  • 7.1.0                                ...           a year ago
  • 7.0.0                                ...           a year ago
  • 6.1.0                                ...           a year ago
  • 6.0.0                                ...           2 years ago
  • 5.2.0                                ...           2 years ago
  • 5.1.0                                ...           2 years ago
  • 5.0.0                                ...           2 years ago
  • 4.3.2                                ...           2 years ago
  • 4.3.1                                ...           2 years ago
  • 4.3.0                                ...           2 years ago
  • 4.2.3                                ...           2 years ago
  • 4.2.1                                ...           2 years ago
  • 4.2.0                                ...           2 years ago
  • 4.1.0                                ...           2 years ago
  • 4.0.0                                ...           2 years ago
  • 3.3.0                                ...           2 years ago
  • 3.2.1                                ...           2 years ago
  • 3.2.0                                ...           2 years ago
  • 3.1.0                                ...           2 years ago
  • 3.0.1                                ...           2 years ago
  • 3.0.0                                ...           2 years ago
  • 2.2.0                                ...           2 years ago
  • 2.1.0                                ...           2 years ago
  • 2.0.1                                ...           2 years ago
  • 2.0.0                                ...           2 years ago
  • 1.0.11                                ...           2 years ago
  • 1.0.10                                ...           3 years ago
  • 1.0.9                                ...           3 years ago
  • 1.0.8                                ...           3 years ago
  • 1.0.7                                ...           3 years ago
  • 1.0.6                                ...           3 years ago
  • 1.0.5                                ...           3 years ago
  • 1.0.4                                ...           3 years ago
  • 1.0.3                                ...           3 years ago
  • 1.0.2                                ...           3 years ago
  • 1.0.1                                ...           3 years ago
  • 1.0.0                                ...           3 years ago
  • 0.10.2                                ...           3 years ago
  • 0.10.1                                ...           3 years ago
  • 0.9.12                                ...           3 years ago
  • 0.9.10                                ...           3 years ago
  • 0.9.9                                ...           3 years ago
  • 0.9.8                                ...           3 years ago
  • 0.9.7                                ...           3 years ago
  • 0.9.6                                ...           3 years ago
  • 0.9.5                                ...           3 years ago
  • 0.9.4                                ...           3 years ago
  • 0.9.3                                ...           3 years ago
  • 0.9.2                                ...           3 years ago
  • 0.9.1                                ...           3 years ago
  • 0.9.0                                ...           3 years ago
  • 0.3.15                                ...           3 years ago
  • 0.3.14                                ...           3 years ago
  • 0.3.13                                ...           3 years ago
  • 0.3.12                                ...           3 years ago
  • 0.3.11                                ...           3 years ago
  • 0.3.10                                ...           3 years ago
  • 0.3.9                                ...           3 years ago
  • 0.3.8                                ...           3 years ago
  • 0.3.7                                ...           3 years ago
  • 0.3.6                                ...           3 years ago
  • 0.3.5                                ...           3 years ago
  • 0.3.4                                ...           3 years ago
  • 0.3.3                                ...           3 years ago
  • 0.3.2                                ...           3 years ago
  • 0.3.1                                ...           3 years ago
  • 0.3.0                                ...           3 years ago
Downloads
Today 0
This Week 84
This Month 188
Last Day 0
Last Week 0
Last Month 128
Dependencies (35)
Dev Dependencies (10)
Dependents (0)
None

Copyright 2014 - 2017 © taobao.org |