On each trigger, search, or create in the operation directive, you can provide fields as an array of objects under inputFields.

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.

TypeBehavior
stringAccepts text input.
textDisplays large, <textarea>-style entry box, accepts text input.
codeDisplays large, <textarea>-style box with a fixed-width font, accepts text input.
integerAccepts integer number values.
numberAccepts any numeric value, including decimal numbers.
booleanDisplays dropdown menu offering true and false options. Passes along true or false.
datetimeAccepts both precise and human-readable date-time values. Passes along an ISO-formatted time string.
fileAccepts 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.
passwordDisplays entered characters as hidden, accepts text input. Does not accept input from previous steps.
copyDoes 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

See Dynamic Dropdowns.

Computed Fields

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

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

Note: Only OAuth and Session Auth support computed fields.

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

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

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

Nested & Children (Line Item) Fields

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

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

Defining extra input field metadata

Since version 15.19.0, input fields may define a meta property containing an object with string keys and string, integers or boolean values. This meta object will then be made available as part of bundle.meta, under the inputFields key. For example, if an input field with a create_recipe key defines a meta object, then this object will be available during the perform method in: bundle.meta.inputFields['create_recipe'].

This context storage is particularly useful when using dynamically-generated fields, as you may want to use this extra data to change certain logic in the perform method of your triggers or actions.

const fetchStringOrDatetimeFields = async (z, bundle) => {
  // Should return an array like [{'key':'field_1'},{'key':'field_2_datetime'}]
  const response = await z.request("https://example.com/api/v2/fields.json");

  const resultingFields = [];

  response.data.forEach((field) => {
    if (field.key.endsWith("_datetime")) {
      resultingFields.push({
        key: field.key,

        // Set the meta to be parsed as a datetime
        meta: { internalType: "datetime" },
      });
    } else {
      resultingFields.push({
        key: field.key,

        // Set the meta to string (default)
        meta: { internalType: "string" },
      });
    }
  });

  return resultingFields;
};

const App = {
  // ...
  creates: {
    create_agenda: {
      // ...
      operation: {
        inputFields: [fetchStringOrDatetimeFields],
        perform: async (z, bundle) => {
          for (const [key, value] of Object.entries(bundle.inputData)) {
            const fieldMeta = bundle.meta[key] || {};

            if (fieldMeta.internalType === "datetime") {
              // process value as a datetime
            } else {
              // process value as a string
            }
          }
        },
      },
    },
  },
};