npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

zapier-platform-cli

v15.7.0

Published

The CLI for managing integrations in Zapier Developer Platform.

Downloads

40,276

Readme

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

You may find some documents on the Zapier site duplicate or outdated. The most up-to-date contents are always available on GitHub:

Our code is updated frequently. To see a full list of changes, look no further than the CHANGELOG.

This doc describes the latest CLI version (15.7.0), as of this writing. If you're using an older version of the CLI, you may want to check out these historical releases:

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?

Note: this document uses "app" while modern Zapier nomenclature refers instead to "integrations". In both cases, the phrase refers to your code that connects your API with Zapier.

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 Visual Builder (to create integrations with a visual builder from your browser). Both use the same underlying platform, so pick the one that fits your team's needs best. The main difference is how you make changes to your code.

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. You can also export 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 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 apps 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" 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

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 app!

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

> Note: When you run `zapier init`, you'll be presented with a list of templates to start with. Pick the one that matches a feature you'll need (such as "dynamic-dropdown" for an integration with [dynamic dropdown fields](https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#dynamic-dropdowns)), or select "minimal" for an integration with only the essentials. [View more example apps here](https://github.com/zapier/zapier-platform/tree/main/example-apps).

# move into the new directory
cd example-app

# install all the libraries needed for your app
npm install

Depending on the authentication method for your app, 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:set 1.0.0 CLIENT_ID=1234
$ zapier env:set 1.0.0 CLIENT_SECRET=abcd

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 minimal

# install all the libraries needed for your app
npm install

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

  • zapier init myapp - initialize/start a local app project
  • zapier convert 1234 . - initialize/start from an existing app
  • 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 integrations

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 integrations - list the apps in Zapier you can administer
  • zapier register "App Title" - 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 team:add [email protected] admin - add an admin to help maintain/develop your app
  • zapier users:add [email protected] 1.0.0 - invite a user try your app version 1.0.0

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 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 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 users:add [email protected] 1.0.0

# sends an email this user to let them admin the app (make changes just like you)
zapier team:add [email protected]

You can also invite anyone on the internet to your app 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 app version or for the entire app (i.e. all app versions).

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 2020-06-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.

Note: There is no way to convert a CLI app to a Web Builder app 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.

# the --version flag is what denotes this command is interacting with a Visual Builder app
# zapier convert <APP_ID> --version <APP_VERSION> <PATH>
zapier convert 1234 --version 1.0.1 my-app

Authentication

Most applications require some sort of authentication. The Zapier platform provides core behaviors for several common authentication methods that might be used with your application, as well as the ability to customize authentication further.

When a user authenticates to your application 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 app 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 app name] --template basic-auth. You can also review an example of that code here.

If your app 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.

const authentication = {
  type: 'basic',
  // "test" could also be a function
  test: {
    url: 'https://example.com/api/accounts/me.json',
  },
  connectionLabel: '{{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,
  // ...
};

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

const getConnectionLabel = (z, bundle) => {
  // bundle.inputData will contain what the "test" URL (or function) returns
  return bundle.inputData.username;
};

const authentication = {
  type: 'digest',
  // "test" could also be a function
  test: {
    url: 'https://example.com/api/accounts/me.json',
  },
  connectionLabel: getConnectionLabel,

  // you can provide additional fields, but we'll provide `username`/`password` automatically
};

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

Custom

Custom auth is most commonly used for apps 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 app name] --template custom-auth. You can also review an example of that code here.

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,
  beforeRequest: [addApiKeyToHeader],
  // ...
};

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 app name] --template session-auth. You can also review an example of that code here.

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

  // response.throwForStatus() if you're using core v9 or older

  return {
    sessionKey: response.data.sessionKey,
    // or response.json.sessionKey if you're using core v9 and older
  };
};

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 App = {
  // ...
  authentication,
  beforeRequest: [includeSessionKeyHeader],
  // ...
};

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 Twitter and Trello implementations of the 3-legged OAuth flow.

To create a new integration with OAuth1, run zapier init [your app name] --template oauth1-trello. You can also 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 backend call to your API to exchange the request token for an "access token" (also known as "long-lived credentials").
  5. 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 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:set 1.0.0 CLIENT_ID=1234
$ zapier env:set 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,
  beforeRequest: [includeAccessToken],
  // ...
};

module.exports = App;

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 app name] --template oauth2. You can also check out our working example app.

If your app's OAuth2 flow uses a different grant type, such as client_credentials, try using Session auth instead.

The OAuth2 flow 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 the zapier describe command to find out what it is:
  3. Zapier makes a backend call to your API to exchange the code for an access_token.
  4. Zapier stores the access_token and uses it to make calls on behalf of the user.
  5. (Optionally) Zapier can refresh the token if it expires.

Note: When building a public integration, the redirect_uri will change once the app is approved for publishing, to be more consistent with your app’s branding. Depending on your API, you may need to add this new redirect_uri to an allow list in order for users to continue connecting to your app on Zapier. To access the new redirect_uri, run zapier describe again once the app is published.

You are required to define:

  • authorizeUrl: The authorization URL
  • getAccessToken: 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 app, 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:

# setting the environment variables on Zapier.com
$ zapier env:set 1.0.0 CLIENT_ID=1234
$ zapier env:set 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,
  beforeRequest: [addBearerHeader],
  // ...
};

module.exports = App;

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 the state 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 at bundle.inputData.state. Since Zapier uses the state to verify that GET requests to your redirect URL truly come from your app, 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:

  1. enablePkce: true
  2. getAccessToken.body to include code_verifier: "{{bundle.inputData.code_verifier}}"

The OAuth2 PKCE flow uses the same flow as OAuth2 but adds a few extra parameters:

  1. Zapier computes a code_verifier and code_challenge internally and stores the code_verifier in the Zapier bundle.
  2. Zapier sends the user to the authorization URL defined by your app. We automatically include the computed code_challenge and code_challenge_method in the authorization request.
  3. Once authorized, your website sends the user to the redirect_uri Zapier provided.
  4. Zapier makes a call to your API to exchange the code but you must include the computed code_verifier in the request for an access_token.
  5. Zapier stores the access_token and uses it to make calls on behalf of the user.

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',
        code_verifier: '{{bundle.inputData.code_verifier}}', // Added for PKCE
      },
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
    },
    scope: 'read,write',
    enablePkce: true, // Added for PKCE
  },
  // 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,
  beforeRequest: [addBearerHeader],
  // ...
};

module.exports = App;

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

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.

Check out this working example app 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

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

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 Recipe = {
  key: 'recipe',
  // ...
  list: {
    // ...
  },
  create: {
    display: {
      label: 'Add Recipe',
      description: 'Adds a new recipe to our cookbook.',
    },
    operation: {
      perform: {
        method: 'POST',
        url: 'https://example.com/recipes',
        body: {
          name: 'Baked Falafel',
          style: 'mediterranean',
        },
      },
    },
  },
};

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 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: {
          url: 'https://example.com/recipes',
        },
      },
    },
    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.

To create a new integration with a premade trigger, search, or create, run zapier init [your app name] and select from the list that appears. You can also check out our working example apps 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:

order = {
  name: 'Zap Zaplar',
  total_cost: 25.96,
  items: [
    { name: 'Zapier T-Shirt', unit_price: 11.99, quantity: 3, line_amount: 35.97, category: 'shirts' },
    { name: 'Orange Widget', unit_price: 7.99, quantity: 10, line_amount: 79.90, category: 'widgets' },
    { name:'Stuff', unit_price: 2.99, quantity: 7, line_amount: 20.93, category: 'stuff' },
    { name: 'Allbird Shoes', unit_price: 2.99, quantity: 7, line_amount: 20.93, category: 'shoes' },
  ],
  zip: 01002
}

While a Find Users search could return multiple items under an object key within an array, like this:

result = [{
  users: [
      { name: 'Zap Zaplar', age: 12, city: 'Columbia', region: 'Missouri' },
      { name: 'Orange Crush', age: 28, city: 'West Ocean City', region: 'Maryland' },
      { name: 'Lego Brick', age: 91, city: 'Billund', region: 'Denmark' },
    ],
  }];

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.

,sample: {
  dummydata_field1: 'This will be compared against your perform method output'
  style: 'mediterranean'
}

Input Fields

On each trigger, search, or create in the operation directive, you can provide fields as an array of objects under inputFields. Input Fields are what your users see in Zapier when setting up your app's triggers and actions. 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 the user, or 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 brief 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: () => {},
      },
    },
  },
};

Notably, fields come in different types, which may look and act differently in the Zap editor. The default field display is a single-line input field.

| Type | Behavior | |------|----------| | string | Accepts text input. | | text | Displays large, <textarea>-style entry box, accepts text input. | | code | Displays large, <textarea>-style box with a fixed-width font, accepts text input. | | integer | Accepts integer number values. | | number | Accepts any numeric value, including decimal numbers. | | boolean | Displays dropdown menu offering true and false options. Passes along true or false. | | datetime | Accepts both precise and human-readable date-time values. Passes along an ISO-formatted time string. | | file | Accepts a file object or a string. If a URL is provided in the string, Zapier will automatically make a GET for that file. Otherwise, a text file will be generated. | | password | Displays entered characters as hidden, accepts text input. Does not accept input from previous steps. | | copy | Does not allow users to enter data. Shows the value of the Markdown-formatted Help Text for the field as a rich text note in the Zap editor. Good for important notices to users. |

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

Custom/Dynamic Fields

In some cases, you may need to provide dynamically-generated fields - especially for custom ones. This is common functionality for CRMs, form software, databases, and other highly-customizable platforms. Instead of an explicit field definition, you can provide a function we'll evaluate to return a list of fields - merging the dynamic 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 (e.g. 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 = async (z, bundle) => {
  const response = await z.request('https://example.com/api/v2/fields.json');

  // Call response.throwForStatus() if you're using zapier-platform-core v9 or older

  // Should return an array like [{"key":"field_1"},{"key":"field_2"}]
  return response.data; // response.json if you're using core v9 or older
};

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 property altersDynamicFields: true. This informs the Zapier UI whenever the value of that field changes, the input fields need to be recomputed. For example, imagine the selection on a static dropdown called "Dessert Type" determining whether the function generating dynamic fields includes the field "With Sprinkles?" or not. If the value in one input 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.

When using dynamic fields, the fields will be retrieved in three different contexts:

  • Whenever the value of a field with altersDynamicFields is changed, as described above.
  • Whenever the Zap Editor opens the "Set up" section for the trigger or action.
  • Whenever the "Refresh fields" button at the bottom of the Editor's "Set up" section is clicked.

Be sure to set up your code accordingly - for example, don't rely on any input fields already having a value, since they won't have one the first time the "Set up" section loads.

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',