From 0dde726054eed7423d392884eb44fb69dd6ed21c Mon Sep 17 00:00:00 2001 From: kola-er Date: Tue, 26 Mar 2024 17:19:42 +0100 Subject: [PATCH 1/2] PDE-4798 Sync docs from zapier-platform to visual builder --- docs/_reference/cli-docs.md | 1343 +++++++++++++++++++---------------- 1 file changed, 715 insertions(+), 628 deletions(-) diff --git a/docs/_reference/cli-docs.md b/docs/_reference/cli-docs.md index e3f453ba..b885fd7c 100644 --- a/docs/_reference/cli-docs.md +++ b/docs/_reference/cli-docs.md @@ -22,7 +22,7 @@ You may find some documents on the Zapier site duplicate or outdated. The most u Our code is updated frequently. To see a full list of changes, look no further than [the CHANGELOG](https://github.com/zapier/zapier-platform/blob/main/CHANGELOG.md). -This doc describes the latest CLI version (**15.5.1**), as of this writing. If you're using an older version of the CLI, you may want to check out these historical releases: +This doc describes the latest CLI version (**15.6.0**), as of this writing. If you're using an older version of the CLI, you may want to check out these historical releases: - CLI Docs: [14.x](https://github.com/zapier/zapier-platform/blob/zapier-platform-cli@14.1.2/packages/cli/README.md), [13.x](https://github.com/zapier/zapier-platform/blob/zapier-platform-cli@13.0.0/packages/cli/README.md) - CLI Reference: [14.x](https://github.com/zapier/zapier-platform/blob/zapier-platform-cli@14.1.2/packages/cli/docs/cli.md), [13.x](https://github.com/zapier/zapier-platform/blob/zapier-platform-cli@13.0.0/packages/cli/docs/cli.md) @@ -32,124 +32,122 @@ This doc describes the latest CLI version (**15.5.1**), as of this writing. If y -- [Zapier Platform CLI docs](#zapier-platform-cli-docs) - - [Table of Contents](#table-of-contents) - - [Getting Started](#getting-started) - - [What is an App?](#what-is-an-app) - - [How does Zapier Platform CLI Work?](#how-does-zapier-platform-cli-work) - - [Zapier Platform CLI vs UI](#zapier-platform-cli-vs-ui) - - [Requirements](#requirements) - - [Quick Setup Guide](#quick-setup-guide) - - [Tutorial](#tutorial) - - [Creating a Local App](#creating-a-local-app) - - [Local Project Structure](#local-project-structure) - - [Local App Definition](#local-app-definition) - - [Registering an App](#registering-an-app) - - [Deploying an App Version](#deploying-an-app-version) - - [Private App Version (default)](#private-app-version-default) - - [Sharing an App Version](#sharing-an-app-version) - - [Promoting an App Version](#promoting-an-app-version) - - [Converting an Existing App](#converting-an-existing-app) - - [Authentication](#authentication) - - [Basic](#basic) - - [Digest](#digest) - - [Custom](#custom) - - [Session](#session) - - [OAuth1](#oauth1) - - [OAuth2](#oauth2) - - [OAuth2 with PKCE](#oauth2-with-pkce) - - [Connection Label](#connection-label) - - [Resources](#resources) - - [Resource Definition](#resource-definition) - - [Triggers/Searches/Creates](#triggerssearchescreates) - - [Return Types](#return-types) - - [Returning Line Items (Array of Objects)](#returning-line-items-array-of-objects) - - [Fallback Sample](#fallback-sample) - - [Input Fields](#input-fields) - - [Custom/Dynamic Fields](#customdynamic-fields) - - [Dynamic Dropdowns](#dynamic-dropdowns) - - [Search-Powered Fields](#search-powered-fields) - - [Computed Fields](#computed-fields) - - [Nested \& Children (Line Item) Fields](#nested--children-line-item-fields) - - [Output Fields](#output-fields) - - [Nested \& Children (Line Item) Fields](#nested--children-line-item-fields-1) - - [Z Object](#z-object) - - [`z.request([url], options)`](#zrequesturl-options) - - [`z.console`](#zconsole) - - [`z.dehydrate(func, inputData)`](#zdehydratefunc-inputdata) - - [`z.dehydrateFile(func, inputData)`](#zdehydratefilefunc-inputdata) - - [`z.stashFile(bufferStringStream, [knownLength], [filename], [contentType])`](#zstashfilebufferstringstream-knownlength-filename-contenttype) - - [`z.JSON`](#zjson) - - [`z.hash()`](#zhash) - - [`z.errors`](#zerrors) - - [`z.cursor`](#zcursor) - - [`z.generateCallbackUrl()`](#zgeneratecallbackurl) - - [Bundle Object](#bundle-object) - - [`bundle.authData`](#bundleauthdata) - - [`bundle.inputData`](#bundleinputdata) - - [`bundle.inputDataRaw`](#bundleinputdataraw) - - [`bundle.meta`](#bundlemeta) - - [`bundle.rawRequest`](#bundlerawrequest) - - [`bundle.cleanedRequest`](#bundlecleanedrequest) - - [`bundle.outputData`](#bundleoutputdata) - - [`bundle.targetUrl`](#bundletargeturl) - - [`bundle.subscribeData`](#bundlesubscribedata) - - [Environment](#environment) - - [Defining Environment Variables](#defining-environment-variables) - - [Accessing Environment Variables](#accessing-environment-variables) - - [Adding Throttle Configuration](#adding-throttle-configuration) - - [Making HTTP Requests](#making-http-requests) - - [Shorthand HTTP Requests](#shorthand-http-requests) - - [Manual HTTP Requests](#manual-http-requests) - - [POST and PUT Requests](#post-and-put-requests) - - [Using HTTP middleware](#using-http-middleware) - - [Error Response Handling](#error-response-handling) - - [HTTP Request Options](#http-request-options) - - [HTTP Response Object](#http-response-object) - - [Dehydration](#dehydration) - - [Merging Hydrated Data](#merging-hydrated-data) - - [File Dehydration](#file-dehydration) - - [Stashing Files](#stashing-files) - - [Logging](#logging) - - [Console Logging](#console-logging) - - [Viewing Console Logs](#viewing-console-logs) - - [Viewing Bundle Logs](#viewing-bundle-logs) - - [HTTP Logging](#http-logging) - - [Viewing HTTP Logs](#viewing-http-logs) - - [Error Handling](#error-handling) - - [General Errors](#general-errors) - - [Halting Execution](#halting-execution) - - [Stale Authentication Credentials](#stale-authentication-credentials) - - [Handling Throttled Requests](#handling-throttled-requests) - - [Testing](#testing) - - [Writing Unit Tests](#writing-unit-tests) - - [Using the `z` Object in Tests](#using-the-z-object-in-tests) - - [Mocking Requests](#mocking-requests) - - [Running Unit Tests](#running-unit-tests) - - [Testing \& Environment Variables](#testing--environment-variables) - - [Testing in Your CI](#testing-in-your-ci) - - [Debugging Tests](#debugging-tests) - - [Using `npm` Modules](#using-npm-modules) - - [Building Native Packages with Docker](#building-native-packages-with-docker) - - [Using Transpilers](#using-transpilers) - - [FAQs](#faqs) - - [Why doesn't Zapier support newer versions of Node.js?](#why-doesnt-zapier-support-newer-versions-of-nodejs) - - [How do I manually set the Node.js version to run my app with?](#how-do-i-manually-set-the-nodejs-version-to-run-my-app-with) - - [When to use placeholders or curlies?](#when-to-use-placeholders-or-curlies) - - [Does Zapier support XML (SOAP) APIs?](#does-zapier-support-xml-soap-apis) - - [Is it possible to iterate over pages in a polling trigger?](#is-it-possible-to-iterate-over-pages-in-a-polling-trigger) - - [How do search-powered fields relate to dynamic dropdowns and why are they both required together?](#how-do-search-powered-fields-relate-to-dynamic-dropdowns-and-why-are-they-both-required-together) - - [What's the deal with pagination? When is it used and how does it work?](#whats-the-deal-with-pagination-when-is-it-used-and-how-does-it-work) - - [How does deduplication work?](#how-does-deduplication-work) - - [Why are my triggers complaining if I don't provide an explicit `id` field?](#why-are-my-triggers-complaining-if-i-dont-provide-an-explicit-id-field) - - [Node X No Longer Supported](#node-x-no-longer-supported) - - [What Analytics are Collected?](#what-analytics-are-collected) - - [What's the Difference Between an "App" and an "Integration"?](#whats-the-difference-between-an-app-and-an-integration) - - [Command Line Tab Completion](#command-line-tab-completion) - - [The Zapier Platform Packages](#the-zapier-platform-packages) - - [Updating These Packages](#updating-these-packages) - - [Get Help!](#get-help) - - [Developing on the CLI](#developing-on-the-cli) +- [Getting Started](#getting-started) + * [What is an App?](#what-is-an-app) + * [How does Zapier Platform CLI Work?](#how-does-zapier-platform-cli-work) + * [Zapier Platform CLI vs UI](#zapier-platform-cli-vs-ui) + * [Requirements](#requirements) + * [Quick Setup Guide](#quick-setup-guide) + * [Tutorial](#tutorial) +- [Creating a Local App](#creating-a-local-app) + * [Local Project Structure](#local-project-structure) + * [Local App Definition](#local-app-definition) +- [Registering an App](#registering-an-app) +- [Deploying an App Version](#deploying-an-app-version) + * [Private App Version (default)](#private-app-version-default) + * [Sharing an App Version](#sharing-an-app-version) + * [Promoting an App Version](#promoting-an-app-version) +- [Converting an Existing App](#converting-an-existing-app) +- [Authentication](#authentication) + * [Basic](#basic) + * [Digest](#digest) + * [Custom](#custom) + * [Session](#session) + * [OAuth1](#oauth1) + * [OAuth2](#oauth2) + * [OAuth2 with PKCE](#oauth2-with-pkce) + * [Connection Label](#connection-label) +- [Resources](#resources) + * [Resource Definition](#resource-definition) +- [Triggers/Searches/Creates](#triggerssearchescreates) + * [Return Types](#return-types) + + [Returning Line Items (Array of Objects)](#returning-line-items-array-of-objects) + * [Fallback Sample](#fallback-sample) +- [Input Fields](#input-fields) + * [Custom/Dynamic Fields](#customdynamic-fields) + * [Dynamic Dropdowns](#dynamic-dropdowns) + * [Search-Powered Fields](#search-powered-fields) + * [Computed Fields](#computed-fields) + * [Nested & Children (Line Item) Fields](#nested--children-line-item-fields) +- [Output Fields](#output-fields) + * [Nested & Children (Line Item) Fields](#nested--children-line-item-fields-1) +- [Z Object](#z-object) + * [`z.request([url], options)`](#zrequesturl-options) + * [`z.console`](#zconsole) + * [`z.dehydrate(func, inputData)`](#zdehydratefunc-inputdata) + * [`z.dehydrateFile(func, inputData)`](#zdehydratefilefunc-inputdata) + * [`z.stashFile(bufferStringStream, [knownLength], [filename], [contentType])`](#zstashfilebufferstringstream-knownlength-filename-contenttype) + * [`z.JSON`](#zjson) + * [`z.hash()`](#zhash) + * [`z.errors`](#zerrors) + * [`z.cursor`](#zcursor) + * [`z.generateCallbackUrl()`](#zgeneratecallbackurl) +- [Bundle Object](#bundle-object) + * [`bundle.authData`](#bundleauthdata) + * [`bundle.inputData`](#bundleinputdata) + * [`bundle.inputDataRaw`](#bundleinputdataraw) + * [`bundle.meta`](#bundlemeta) + * [`bundle.rawRequest`](#bundlerawrequest) + * [`bundle.cleanedRequest`](#bundlecleanedrequest) + * [`bundle.outputData`](#bundleoutputdata) + * [`bundle.targetUrl`](#bundletargeturl) + * [`bundle.subscribeData`](#bundlesubscribedata) +- [Environment](#environment) + * [Defining Environment Variables](#defining-environment-variables) + * [Accessing Environment Variables](#accessing-environment-variables) +- [Adding Throttle Configuration](#adding-throttle-configuration) +- [Making HTTP Requests](#making-http-requests) + * [Shorthand HTTP Requests](#shorthand-http-requests) + * [Manual HTTP Requests](#manual-http-requests) + + [POST and PUT Requests](#post-and-put-requests) + * [Using HTTP middleware](#using-http-middleware) + + [Error Response Handling](#error-response-handling) + * [HTTP Request Options](#http-request-options) + * [HTTP Response Object](#http-response-object) +- [Dehydration](#dehydration) + * [Merging Hydrated Data](#merging-hydrated-data) + * [File Dehydration](#file-dehydration) +- [Stashing Files](#stashing-files) +- [Logging](#logging) + * [Console Logging](#console-logging) + * [Viewing Console Logs](#viewing-console-logs) + * [Viewing Bundle Logs](#viewing-bundle-logs) + * [HTTP Logging](#http-logging) + * [Viewing HTTP Logs](#viewing-http-logs) +- [Error Handling](#error-handling) + * [General Errors](#general-errors) + * [Halting Execution](#halting-execution) + * [Stale Authentication Credentials](#stale-authentication-credentials) + * [Handling Throttled Requests](#handling-throttled-requests) +- [Testing](#testing) + * [Writing Unit Tests](#writing-unit-tests) + * [Using the `z` Object in Tests](#using-the-z-object-in-tests) + * [Mocking Requests](#mocking-requests) + * [Running Unit Tests](#running-unit-tests) + * [Testing & Environment Variables](#testing--environment-variables) + * [Testing in Your CI](#testing-in-your-ci) + * [Debugging Tests](#debugging-tests) +- [Using `npm` Modules](#using-npm-modules) +- [Building Native Packages with Docker](#building-native-packages-with-docker) +- [Using Transpilers](#using-transpilers) +- [FAQs](#faqs) + * [Why doesn't Zapier support newer versions of Node.js?](#why-doesnt-zapier-support-newer-versions-of-nodejs) + * [How do I manually set the Node.js version to run my app with?](#how-do-i-manually-set-the-nodejs-version-to-run-my-app-with) + * [When to use placeholders or curlies?](#when-to-use-placeholders-or-curlies) + * [Does Zapier support XML (SOAP) APIs?](#does-zapier-support-xml-soap-apis) + * [Is it possible to iterate over pages in a polling trigger?](#is-it-possible-to-iterate-over-pages-in-a-polling-trigger) + * [How do search-powered fields relate to dynamic dropdowns and why are they both required together?](#how-do-search-powered-fields-relate-to-dynamic-dropdowns-and-why-are-they-both-required-together) + * [What's the deal with pagination? When is it used and how does it work?](#whats-the-deal-with-pagination-when-is-it-used-and-how-does-it-work) + * [How does deduplication work?](#how-does-deduplication-work) + * [Why are my triggers complaining if I don't provide an explicit `id` field?](#why-are-my-triggers-complaining-if-i-dont-provide-an-explicit-id-field) + * [Node X No Longer Supported](#node-x-no-longer-supported) + * [What Analytics are Collected?](#what-analytics-are-collected) + * [What's the Difference Between an "App" and an "Integration"?](#whats-the-difference-between-an-app-and-an-integration) +- [Command Line Tab Completion](#command-line-tab-completion) +- [The Zapier Platform Packages](#the-zapier-platform-packages) + * [Updating These Packages](#updating-these-packages) +- [Get Help!](#get-help) +- [Developing on the CLI](#developing-on-the-cli) @@ -168,12 +166,12 @@ 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](#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](#triggerssearchescreates), which read data _from_ your API. These have their own section in the Zap Editor. -- [Creates](#triggerssearchescreates), which send data _to_ your API to create new records. These are listed under "Actions" in the Zap Editor. -- [Searches](#triggerssearchescreates), which find specific records _in_ your system. These are also listed under "Actions" in the Zap Editor. -- [Resources](#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. + * [Authentication](#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](#triggerssearchescreates), which read data *from* your API. These have their own section in the Zap Editor. + * [Creates](#triggerssearchescreates), which send data *to* your API to create new records. These are listed under "Actions" in the Zap Editor. + * [Searches](#triggerssearchescreates), which find specific records *in* your system. These are also listed under "Actions" in the Zap Editor. + * [Resources](#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? @@ -201,6 +199,7 @@ Firstly, by using a CI tool (like [Travis CI](https://travis-ci.org/) or [Circle Alternatively, you can change your local node version with tools such as [nvm](https://github.com/nvm-sh/nvm#installation-and-update). 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](https://zapier.com/app/editor). @@ -212,8 +211,7 @@ 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](https://developer.zapier.com/partner-settings/deploy-keys/) and create/copy a key, then run `zapier login` command with the --sso flag. +> 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](https://developer.zapier.com/partner-settings/deploy-keys/) 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! @@ -229,7 +227,6 @@ 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. ```bash @@ -255,6 +252,7 @@ zapier push > Go check out our [full CLI reference documentation](https://github.com/zapier/zapier-platform/blob/main/packages/cli/docs/cli.md#zapier-cli-reference) to see all the other commands! + ### Tutorial For a full tutorial, head over to our [Tutorial](https://platform.zapier.com/cli_tutorials/getting-started) for a comprehensive walkthrough for creating your first app. If this isn't your first rodeo, read on! @@ -279,12 +277,12 @@ 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 +* `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 @@ -297,15 +295,15 @@ $ tree . ├── index.js ├── package.json ├── triggers -│   └── contact-by-tag.js +│ └── contact-by-tag.js ├── resources -│   └── Contact.js +│ └── Contact.js ├── test -│   ├── basic.js -│   ├── triggers.js -│   └── resources.js +│ ├── basic.js +│ ├── triggers.js +│ └── resources.js ├── build -│   └── build.zip +│ └── build.zip └── node_modules ├── ... └── ... @@ -318,8 +316,8 @@ The core definition of your `App` will look something like this, and is what you ```js const App = { // both version strings are required - version: require("./package.json").version, - platformVersion: require("zapier-platform-core").version, + version: require('./package.json').version, + platformVersion: require('zapier-platform-core').version, // see "Authentication" section below authentication: {}, @@ -342,10 +340,12 @@ const App = { }; 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. @@ -362,12 +362,12 @@ zapier integrations 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 user@example.com admin` - add an admin to help maintain/develop your app -- `zapier users:add user@example.com 1.0.0` - invite a user try your app version 1.0.0 +* `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 user@example.com admin` - add an admin to help maintain/develop your app +* `zapier users:add user@example.com 1.0.0` - invite a user try your app version 1.0.0 ## Deploying an App Version @@ -383,20 +383,22 @@ 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. +* `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. @@ -466,12 +468,12 @@ If your app uses Basic auth with an encoded API key rather than a username and p ```js const authentication = { - type: "basic", + type: 'basic', // "test" could also be a function test: { - url: "https://example.com/api/accounts/me.json", + url: 'https://example.com/api/accounts/me.json', }, - connectionLabel: "{{username}}", // Can also be a function, check digest auth below for an example + 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 }; @@ -480,11 +482,12 @@ const App = { authentication, // ... }; + ``` ### Digest -_Added in v7.4.0._ +*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. @@ -499,10 +502,10 @@ const getConnectionLabel = (z, bundle) => { }; const authentication = { - type: "digest", + type: 'digest', // "test" could also be a function test: { - url: "https://example.com/api/accounts/me.json", + url: 'https://example.com/api/accounts/me.json', }, connectionLabel: getConnectionLabel, @@ -514,6 +517,7 @@ const App = { authentication, // ... }; + ``` ### Custom @@ -524,31 +528,31 @@ Custom auth is most commonly used for apps that authenticate with API keys, alth ```js const authentication = { - type: "custom", + type: 'custom', // "test" could also be a function test: { - url: "https://{{bundle.authData.subdomain}}.example.com/api/accounts/me.json", + url: 'https://{{bundle.authData.subdomain}}.example.com/api/accounts/me.json', }, fields: [ { - key: "subdomain", - type: "string", + key: 'subdomain', + type: 'string', required: true, - helpText: "Found in your browsers address bar after logging in.", + helpText: 'Found in your browsers address bar after logging in.', }, { - key: "api_key", - type: "string", + key: 'api_key', + type: 'string', required: true, - helpText: "Found on your settings page.", + helpText: 'Found on your settings page.', }, ], }; const addApiKeyToHeader = (request, z, bundle) => { - request.headers["X-Subdomain"] = bundle.authData.subdomain; + request.headers['X-Subdomain'] = bundle.authData.subdomain; const basicHash = Buffer.from(`${bundle.authData.api_key}:x`).toString( - "base64" + 'base64' ); request.headers.Authorization = `Basic ${basicHash}`; return request; @@ -560,6 +564,7 @@ const App = { beforeRequest: [addApiKeyToHeader], // ... }; + ``` ### Session @@ -571,8 +576,8 @@ Session auth gives you the ability to exchange some user-provided data for some ```js const getSessionKey = async (z, bundle) => { const response = await z.request({ - method: "POST", - url: "https://example.com/api/accounts/login.json", + method: 'POST', + url: 'https://example.com/api/accounts/login.json', body: { username: bundle.authData.username, password: bundle.authData.password, @@ -588,23 +593,23 @@ const getSessionKey = async (z, bundle) => { }; const authentication = { - type: "session", + type: 'session', // "test" could also be a function test: { - url: "https://example.com/api/accounts/me.json", + url: 'https://example.com/api/accounts/me.json', }, fields: [ { - key: "username", - type: "string", + key: 'username', + type: 'string', required: true, - helpText: "Your login username.", + helpText: 'Your login username.', }, { - key: "password", - type: "string", + key: 'password', + type: 'string', required: true, - helpText: "Your login password.", + 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 @@ -620,7 +625,7 @@ const authentication = { const includeSessionKeyHeader = (request, z, bundle) => { if (bundle.authData.sessionKey) { request.headers = request.headers || {}; - request.headers["X-Session-Key"] = bundle.authData.sessionKey; + request.headers['X-Session-Key'] = bundle.authData.sessionKey; } return request; }; @@ -631,13 +636,14 @@ const App = { 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`._ +*Added in `v7.5.0`.* Zapier's OAuth1 implementation matches [Twitter](https://developer.twitter.com/en/docs/tutorials/authenticating-with-twitter-api-for-enterprise/authentication-method-overview#oauth1.0a) and [Trello](https://developer.atlassian.com/cloud/trello/guides/rest-api/authorization/#using-basic-oauth) implementations of the 3-legged OAuth flow. @@ -645,17 +651,17 @@ Zapier's OAuth1 implementation matches [Twitter](https://developer.twitter.com/e 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: ![](https://zappy.zapier.com/117ECB35-5CCA-4C98-B74A-35F1AD9A3337.png) -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. + 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: ![](https://zappy.zapier.com/117ECB35-5CCA-4C98-B74A-35F1AD9A3337.png) + 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 + * `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. @@ -671,48 +677,48 @@ $ CLIENT_ID=1234 CLIENT_SECRET=abcd zapier test Your auth definition would look something like this: ```js -const _ = require("lodash"); +const _ = require('lodash'); const authentication = { - type: "oauth1", + type: 'oauth1', oauth1Config: { getRequestToken: { - url: "https://{{bundle.inputData.subdomain}}.example.com/request-token", - method: "POST", + 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}}", + 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}}", + oauth_signature_method: 'HMAC-SHA1', + oauth_callback: '{{bundle.inputData.redirect_uri}}', }, }, authorizeUrl: { - url: "https://{{bundle.inputData.subdomain}}.example.com/authorize", + url: 'https://{{bundle.inputData.subdomain}}.example.com/authorize', params: { - oauth_token: "{{bundle.inputData.oauth_token}}", + oauth_token: '{{bundle.inputData.oauth_token}}', }, }, getAccessToken: { - url: "https://{{bundle.inputData.subdomain}}.example.com/access-token", - method: "POST", + 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}}", + 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", + 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" }, + { 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: @@ -750,6 +756,7 @@ const App = { }; 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. @@ -766,18 +773,18 @@ If your app's OAuth2 flow uses a different grant type, such as `client_credentia 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: ![](https://zappy.zapier.com/83E12494-0A03-4DB4-AA46-5A2AF6A9ECCC.png) -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. + 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: ![](https://zappy.zapier.com/83E12494-0A03-4DB4-AA46-5A2AF6A9ECCC.png) + 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](https://platform.zapier.com/private_integrations/private-vs-public-integrations), 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. +> Note: When [building a public integration](https://platform.zapier.com/private_integrations/private-vs-public-integrations), 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 + * `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](#stale-authentication-credentials) for more details on handling auth refresh. @@ -796,44 +803,44 @@ Your auth definition would look something like this: ```js const authentication = { - type: "oauth2", + type: 'oauth2', test: { - url: "https://{{bundle.authData.subdomain}}.example.com/api/accounts/me.json", + 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", + 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", + 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", + 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: '{{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", + 'Content-Type': 'application/x-www-form-urlencoded', }, }, - scope: "read,write", + scope: 'read,write', }, // If you need any fields upfront, put them here fields: [ - { key: "subdomain", type: "string", required: true, default: "app" }, + { 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: @@ -857,6 +864,7 @@ const App = { }; 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. @@ -865,72 +873,72 @@ Also, `authentication.oauth2Config.getAccessToken` has access to the additional 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](https://auth0.com/docs/secure/attack-protection/state-parameters) 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). +> Note: The OAuth2 `state` param is a [standard security feature](https://auth0.com/docs/secure/attack-protection/state-parameters) 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._ +*Added in v14.0.0.* Zapier's OAuth2 implementation also supports [PKCE](https://oauth.net/2/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}}"` + 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. + 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: ```js const authentication = { - type: "oauth2", + type: 'oauth2', test: { - url: "https://{{bundle.authData.subdomain}}.example.com/api/accounts/me.json", + 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", + 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", + 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", + 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 + 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", + 'Content-Type': 'application/x-www-form-urlencoded', }, }, - scope: "read,write", + 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" }, + { 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: @@ -954,6 +962,7 @@ const App = { }; module.exports = App; + ``` The computed `code_verifier` uses this standard: [RFC 7636 Code Verifier](https://www.rfc-editor.org/rfc/rfc7636#section-4.1) @@ -972,6 +981,7 @@ When using a function, this "hoisting" of data to the top level is skipped, and **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` @@ -981,9 +991,9 @@ read, and search operations on that resource. ```js const Recipe = { // `key` is the unique identifier the Zapier backend references - key: "recipe", + key: 'recipe', // `noun` is the user-friendly name displayed in the Zapier UI - noun: "Recipe", + noun: 'Recipe', // `list` and `create` are just a couple of the methods you can define list: { // ... @@ -992,6 +1002,7 @@ const Recipe = { // ... }, }; + ``` The quickest way to create a resource is with the `zapier scaffold` command: @@ -1002,6 +1013,7 @@ 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. @@ -1013,27 +1025,28 @@ After those, there is a set of optional properties that tell Zapier what methods The complete list of available methods can be found in the [Resource Schema Docs](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#resourceschema). 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. + * `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 ```js const Recipe = { - key: "recipe", + key: 'recipe', // ... list: { display: { - label: "New Recipe", - description: "Triggers when a new recipe is added.", + label: 'New Recipe', + description: 'Triggers when a new recipe is added.', }, operation: { perform: { - url: "https://example.com/recipes", + url: 'https://example.com/recipes', }, }, }, }; + ``` The method is made up of two properties, a `display` and an `operation`. The `display` property ([schema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#basicdisplayschema)) holds the info needed to present the method as an available Trigger in the Zapier Editor. The `operation` ([schema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#resourceschema)) provides the implementation to make the API call. @@ -1042,28 +1055,29 @@ Adding a create method looks very similar. ```js const Recipe = { - key: "recipe", + key: 'recipe', // ... list: { // ... }, create: { display: { - label: "Add Recipe", - description: "Adds a new recipe to our cookbook.", + label: 'Add Recipe', + description: 'Adds a new recipe to our cookbook.', }, operation: { perform: { - method: "POST", - url: "https://example.com/recipes", + method: 'POST', + url: 'https://example.com/recipes', body: { - name: "Baked Falafel", - style: "mediterranean", + name: 'Baked Falafel', + style: 'mediterranean', }, }, }, }, }; + ``` Every method you define on a `resource` Zapier converts to the appropriate Trigger, Create, or Search. Our examples @@ -1071,6 +1085,7 @@ 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 @@ -1084,17 +1099,17 @@ 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 + 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.", + 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", + url: 'https://example.com/recipes', }, }, }, @@ -1103,6 +1118,7 @@ const App = { }, }, }; + ``` You can find more details on the definition for each by looking at the [Trigger Schema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#triggerschema), @@ -1111,16 +1127,15 @@ You can find more details on the definition for each by looking at the [Trigger > 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](https://github.com/zapier/zapier-platform/tree/main/example-apps). > 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](https://platform.zapier.com/docs/dedupe/) 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`](https://lodash.com/docs#isPlainObject) | +| Method | Return Type | Notes | +|---------|-------------|----------------------------------------------------------------------------------------------------------------------| +| Trigger | Array | 0 or more objects; passed to the [deduper](https://platform.zapier.com/docs/dedupe/) 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`](https://lodash.com/docs#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. @@ -1161,7 +1176,6 @@ A standard search would return just the inner array of users, and only the first 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](https://zapier.com/help/create/basics/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. ```js @@ -1191,15 +1205,15 @@ const App = { // an array of objects is the simplest way inputFields: [ { - key: "title", + key: 'title', required: true, - label: "Title of Recipe", - helpText: "Name your recipe!", + label: 'Title of Recipe', + helpText: 'Name your recipe!', }, { - key: "style", + key: 'style', required: true, - choices: { mexican: "Mexican", italian: "Italian" }, + choices: { mexican: 'Mexican', italian: 'Italian' }, }, ], perform: () => {}, @@ -1207,22 +1221,23 @@ const App = { }, }, }; + ``` 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, `