Ready to maximize your revenue?Book a Demo
TOPCODE
Shopify Flow

Creating a custom Shopify Flow trigger from your app

Last updated on June 2, 2026

Creating a custom Shopify Flow trigger from your app

TOPCODE

Shopify Flow is a visual automation builder built into the Shopify admin. Out of the box it ships triggers for standard commerce events — order created, customer tagged, product added to collection — but its real power comes from the ability for third-party apps to register their own triggers. Once registered, merchants can use your app's trigger to build any Flow automation they can imagine, without writing code. This tutorial walks through the complete process: defining a trigger payload schema, registering the trigger via the Partner API, and firing it from your app whenever the relevant event occurs.

What You'll Build

You will register a custom Flow trigger called "Low Inventory Alert" that fires whenever one of your app's tracked products drops below a configurable threshold. The trigger exposes three payload fields — product_id, variant_id, and inventory_quantity — which merchants can reference in subsequent Flow actions. By the end you'll also fire a test event so you can verify the trigger appears in the Flow editor.

Before You Start

You need a Shopify Partner account and a development store running Shopify plan or higher (Flow is not available on Basic). Your app must be installed on the store and must hold the write_flow_actions OAuth scope. You also need a publicly reachable HTTPS URL for the trigger endpoint — during development, shopify app dev tunnels your local server automatically. Node.js 18+ with @shopify/shopify-api v9+ is assumed.

1

Add the write_flow_actions scope

Open your app's shopify.app.toml file and locate the [access_scopes] section. Append write_flow_actions to the scopes list. After saving, run shopify app deploy and then reinstall the app on your development store so the new scope is included in the OAuth grant.

shopify.app.tomlbash
# shopify.app.toml (excerpt)
[access_scopes]
scopes = "read_products,write_products,write_flow_actions"

Expected outcome: Your app's OAuth grant now includes the write_flow_actions scope. You can confirm this in the Partner Dashboard under App setup > App scopes.

2

Define the trigger payload schema

Flow triggers are registered via the Admin REST API's /admin/api/2024-01/flow_trigger_definitions.json endpoint. The payload schema is described as a flat list of fields, each with a name, description, and type. Supported types include id, string, integer, boolean, and float. Resource reference types like product_id and customer_id allow Flow to resolve the full resource and expose its properties to downstream actions — this is more powerful than passing a raw integer ID.

Create a TypeScript module that encodes your trigger definition. Keeping the definition in code (rather than a JSON file) means you can generate it programmatically and share types between the registration call and the event-firing code.

lib/flow/triggerDefinition.tsts
// lib/flow/triggerDefinition.ts
export const LOW_INVENTORY_TRIGGER = {
  title: 'Low Inventory Alert',
  description: 'Fires when a tracked product variant falls below the reorder threshold.',
  fields: [
    {
      name: 'product_id',
      label: 'Product ID',
      description: 'The Shopify product GID that triggered the alert.',
      type: 'product_id',
    },
    {
      name: 'variant_id',
      label: 'Variant ID',
      description: 'The specific variant GID whose inventory dropped.',
      type: 'product_variant_id',
    },
    {
      name: 'inventory_quantity',
      label: 'Current Inventory Quantity',
      description: 'Inventory on-hand at the moment the alert fired.',
      type: 'integer',
    },
  ],
} as const;

Expected outcome: You have a typed definition object that you'll reference in both the registration API call and when constructing event payloads.

3

Register the trigger via the Admin API

Triggers are registered per-shop at install time. In your OAuth callback or app subscription webhook handler, call the POST /admin/api/2024-01/flow_trigger_definitions.json endpoint using the shop's access token. Flow deduplicates registrations by title + app combination, so it's safe to call this on every install without creating duplicates. However, if you change the fields schema you must first delete the old definition and re-register — there is no PUT endpoint.

The handle field returned in the response is your stable identifier for the trigger — store it in your database alongside the shop's access token so you can reference it when firing events.

lib/flow/registerTrigger.tsts
// lib/flow/registerTrigger.ts
import { shopifyApi } from '@shopify/shopify-api';
import { LOW_INVENTORY_TRIGGER } from './triggerDefinition';

export async function registerFlowTrigger(
  shop: string,
  accessToken: string,
): Promise<string> {
  const client = new shopifyApi.clients.Rest({ session: { shop, accessToken } });

  const response = await client.post({
    path: 'flow_trigger_definitions',
    data: {
      flow_trigger_definition: {
        title: LOW_INVENTORY_TRIGGER.title,
        description: LOW_INVENTORY_TRIGGER.description,
        fields: LOW_INVENTORY_TRIGGER.fields,
      },
    },
    type: DataType.JSON,
  });

  const { handle } = response.body.flow_trigger_definition;
  console.log(`Registered Flow trigger with handle: ${handle}`);
  return handle;
}

Expected outcome: The trigger is registered for the shop. In the Flow editor you should now see 'Low Inventory Alert' in the trigger picker under your app's section.

4

Fire the trigger from your app

To fire the trigger, POST to /admin/api/2024-01/flow_trigger_definitions/{handle}/trigger.json with a payload body whose keys map to the field names declared in your definition. The payload values must match the declared types — Shopify validates them and will return a 422 if the types don't match. Product and variant IDs must be passed as numeric IDs (not GIDs) in this REST endpoint.

Call this function in whatever part of your app logic detects the low inventory condition. For example, if you run a periodic inventory sync job, call fireFlowTrigger after detecting inventory has dropped below the threshold.

lib/flow/fireTrigger.tsts
// lib/flow/fireTrigger.ts
import { shopifyApi, DataType } from '@shopify/shopify-api';

export async function fireFlowTrigger(params: {
  shop: string;
  accessToken: string;
  triggerHandle: string;
  productId: number;
  variantId: number;
  inventoryQuantity: number;
}): Promise<void> {
  const { shop, accessToken, triggerHandle, productId, variantId, inventoryQuantity } = params;
  const client = new shopifyApi.clients.Rest({ session: { shop, accessToken } });

  await client.post({
    path: `flow_trigger_definitions/${triggerHandle}/trigger`,
    data: {
      payload: {
        product_id: productId,
        variant_id: variantId,
        inventory_quantity: inventoryQuantity,
      },
    },
    type: DataType.JSON,
  });
}

Expected outcome: Any Flow automations that use this trigger as their starting event will now execute. You can watch runs in the Flow editor under Activity.

5

Build a test Flow automation

To verify the end-to-end path works, create a simple test Flow in your development store. In the Shopify admin, navigate to Apps > Flow and click "Create workflow". Select your app's trigger "Low Inventory Alert" as the starting event. Add a single action — for example, "Send an internal email" or "Add a tag to product" — and publish the workflow.

Now fire the trigger manually using a curl command or a small test script. If the Flow runs, you'll see an entry in the Activity tab within a few seconds. Note that Flow actions may have a short processing delay (typically under 5 seconds) even in development stores.

You can reference the trigger payload fields in your Flow action configuration using the "Insert variable" picker. For example, in the email action body you can insert {{ trigger.product.title }} because the product_id field was typed as product_id, allowing Flow to resolve the full product resource.

# Quick test — fire the trigger from the terminal
curl -X POST \
  "https://{shop}.myshopify.com/admin/api/2024-01/flow_trigger_definitions/{handle}/trigger.json" \
  -H "X-Shopify-Access-Token: {access_token}" \
  -H "Content-Type: application/json" \
  -d '{"payload":{"product_id":1234567890,"variant_id":9876543210,"inventory_quantity":2}}'

Expected outcome: The test Flow automation runs and you can see the activity entry in the Flow editor. The email action (or whichever action you chose) contains the resolved product title from the payload.

What's Next

Custom Flow triggers become significantly more powerful when combined with custom Flow actions — where your app also handles the downstream action step, receiving a structured webhook payload from Shopify when the Flow runs. Together, trigger + action lets you build fully self-contained automation primitives that merchants can compose visually. See the Shopify Flow triggers documentation for the full field type reference and guidance on versioning trigger schemas without breaking existing merchant workflows.

Senior Shopify Engineer

Frances Chen

Keep reading