---
title: "Product triggers, step by step"
description: "Step-by-step Shopify Flow tutorials for every product trigger: status, title, handle, description, vendor, type, tags and sales channel changes, each with old and new values."
canonical: "https://docs.workflow-trigger-extensions.app/product-trigger-tutorials"
---

# Product triggers, step by step

Shopify Flow can tell you that a product was updated. It cannot tell you **what** changed, and it cannot tell you what the value was before. These eleven triggers do both: each one fires for one kind of change and hands your workflow the value before and after.

Every section below is a small tutorial: what makes the trigger fire, what arrives in Flow, and a workflow you can rebuild in a few minutes.

**On this page:** [Product Update](#product-update), [Product Status Changed](#product-status-changed), [Product Title Changed](#product-title-changed), [Product Handle Changed](#product-handle-changed), [Product Description Changed](#product-description-changed), [Product Vendor Changed](#product-vendor-changed), [Product Type Changed](#product-type-changed), [Product Tags Added](#product-tags-added), [Product Tags Removed](#product-tags-removed), [Product Published to Channel](#product-published-to-channel), [Product Unpublished from Channel](#product-unpublished-from-channel)

> [!TIP]
> **New to Shopify Flow?**
> Every example follows the same five moves: pick the trigger, add a condition, add an action, turn the workflow on, check Event History. [Your first workflow, step by step](https://docs.workflow-trigger-extensions.app/first-workflow-tutorial.md) shows each of them with screenshots.

![Shopify Flow's trigger picker inside Workflow Trigger Extensions, searched for Product](https://cdn-dev.eu.codecreationlabs.cloud/crm-tool/uploads/SjJ78gKLnLlocopCui3Cs62dJbGZ7F1n/0137ff5cb49ad179.png)
*Where you find them: in Shopify Flow choose Select a trigger, open Workflow Trigger Extensions and search for "Product".*

## Product Update

Fires on every save of a product, whatever changed. Since 2.10.0 it also carries `changes`: the tracked fields that changed (title, handle, description, status, vendor, type, tags) with the value before and after.

| | |
| --- | --- |
| **Fires from** | Shopify webhook plus change detection |
| **Permission** | **Product, Variant & Collection Data Access** |
| **Switched on** | Turning on a Flow workflow that uses it switches it on. Its own switch is on the app's **Triggers** page |

**Use it for**

- One workflow that reacts to several kinds of product edits and branches on `changes.fields`.
- An audit trail: write every tracked change, old and new, to a sheet or a Slack channel.
- A catch-all while you find out which narrower trigger you really need.

### What Product Update sends to your workflow

| Variable in Flow | Type | What it holds |
| --- | --- | --- |
| `product` | Product (Shopify) | The product that was updated. A full Shopify product, so all of its fields and Flow's own product actions are available. |
| `payload` | Text | JSON object containing the GID of the product that was updated (e.g. {"id":"gid://shopify/Product/123"}) |
| `changes` | Object | Which tracked fields changed (title, handle, description, status, vendor, type, tags), with the old and new value of each |
| `changes.fields` | List of text | Names of the tracked fields that changed |
| `changes.items` | List of objects | Old and new value per changed field |
| `changes.items (each).field` | Text | The field that changed: title, handle, description, status, vendor, productType or tags |
| `changes.items (each).oldValue` | Text | The value before the update. Long values are shortened |
| `changes.items (each).newValue` | Text | The value after the update. Long values are shortened |
| `eventId` | Text | The id of this event. Search for it in the app's Event History to see exactly what was sent. |

### Example: Log every tracked product change with its old and new value

1. In Shopify Flow choose **Select a trigger**, open **Workflow Trigger Extensions** and pick **Product Update**.
2. Add a **Condition**: `changes.fields` **Not empty and exists**. Updates outside the tracked fields (a price edit, an image) arrive with an empty list and stop here.
3. Add **Send internal email** (or **Send HTTP request** to Slack). Flow's text fields accept Liquid, so a `for` loop over `changes.items` can print one line per change: the entry's `field`, then its `oldValue` and `newValue`, next to `{{product.title}}`.
4. Turn the workflow on. That switches the trigger on in the app as well.

![Flow's variable picker inside changes / items of Product Update, showing field, oldValue and newValue](https://cdn-dev.eu.codecreationlabs.cloud/crm-tool/uploads/SjJ78gKLnLlocopCui3Cs62dJbGZ7F1n/60ee5e39b1cae131.png)
*Product Update in Flow: every entry of changes.items names the field and carries its value before and after.*

![A Flow condition on Product Update: at least one of changes / items has field equal to status](https://cdn-dev.eu.codecreationlabs.cloud/crm-tool/uploads/SjJ78gKLnLlocopCui3Cs62dJbGZ7F1n/c6ab01bc2d485a70.png)
*A condition on it: continue only when the status is among the fields that changed.*

> [!NOTE]
> **Good to know**
> - This is the highest-volume product trigger: a bulk edit or an import fires it once per product. If you only care about one field, use that field's own trigger below - it costs one event instead of one per save.
> - You can narrow it without a condition: the settings icon next to **Product Update** on the app's Triggers page opens **Which changes fire this**. An update that touches none of the ticked fields is skipped before it counts as an event. See [The Triggers page (formerly Settings)](https://docs.workflow-trigger-extensions.app/triggers-page.md).
> - `oldValue` and `newValue` are shortened to 500 characters. For a full description use the `product` reference.
> - `changes` is empty for the first update of a product the app has not seen yet: there is nothing to compare with.

## Product Status Changed

Fires when a product moves between **active**, **draft**, **archived** and **unlisted**. A save that leaves the status alone does not fire it.

| | |
| --- | --- |
| **Fires from** | Shopify webhook plus change detection |
| **Permission** | **Product, Variant & Collection Data Access** |
| **Switched on** | Turning on a Flow workflow that uses it switches it on. Its own switch is on the app's **Triggers** page |

**Use it for**

- Tell the marketing team the moment a product goes live.
- Remove a product from a feed or a bundle when it is archived.
- Catch products that were set back to draft by mistake.

### What Product Status Changed sends to your workflow

| Variable in Flow | Type | What it holds |
| --- | --- | --- |
| `product` | Product (Shopify) | The product whose status changed. A full Shopify product, so all of its fields and Flow's own product actions are available. |
| `oldStatus` | Text | The previous product status (active, draft, archived, or unlisted) |
| `newStatus` | Text | The new product status (active, draft, archived, or unlisted) |
| `eventId` | Text | The id of this event. Search for it in the app's Event History to see exactly what was sent. |

### Example: Announce a product that just went live

1. Pick **Product Status Changed** as the trigger.
2. Add a **Condition**: `newStatus` **Equal to** `active`, and a second criterion `oldStatus` **Equal to** `draft`.
3. Under **Then** add **Send internal email**: subject `Now live: {{product.title}}`, body with `{{product.onlineStoreUrl}}`.
4. Optional: add **Add product tags** with `new-arrival` so a collection picks it up.
5. Turn the workflow on.

> [!NOTE]
> **Good to know**
> - Status values are lower case: `active`, `draft`, `archived`, `unlisted`.
> - Publishing to a sales channel is a different thing from the status. For that use **Product Published to Channel** further down.

## Product Title Changed

Fires when the title of a product changes. Nothing else on the product fires it.

| | |
| --- | --- |
| **Fires from** | Shopify webhook plus change detection |
| **Permission** | **Product, Variant & Collection Data Access** |
| **Switched on** | Turning on a Flow workflow that uses it switches it on. Its own switch is on the app's **Triggers** page |

**Use it for**

- Keep an external catalogue, a PIM or a translation tool in sync with renamed products.
- Ask for an SEO review whenever a title changes on a best seller.
- Spot accidental renames from bulk edits.

### What Product Title Changed sends to your workflow

| Variable in Flow | Type | What it holds |
| --- | --- | --- |
| `product` | Product (Shopify) | The product whose title changed. A full Shopify product, so all of its fields and Flow's own product actions are available. |
| `oldTitle` | Text | The previous title of the product |
| `newTitle` | Text | The new title of the product |
| `eventId` | Text | The id of this event. Search for it in the app's Event History to see exactly what was sent. |

### Example: Send a rename to your team with both titles

1. Pick **Product Title Changed** as the trigger.
2. Add **Send internal email** with the subject `Product renamed` and the body `"{{oldTitle}}" is now "{{newTitle}}" - {{product.onlineStoreUrl}}`.
3. If only some products matter, put a **Condition** in front: `product.vendor` **Equal to** your brand, or `product.tags` **Includes** `hero`.
4. Turn the workflow on.

> [!NOTE]
> **Good to know**
> A title change does not change the handle (the URL). If you also rename the URL, **Product Handle Changed** fires separately.

## Product Handle Changed

Fires when the URL handle of a product changes, which is the part of the address after `/products/`.

| | |
| --- | --- |
| **Fires from** | Shopify webhook plus change detection |
| **Permission** | **Product, Variant & Collection Data Access** |
| **Switched on** | Turning on a Flow workflow that uses it switches it on. Its own switch is on the app's **Triggers** page |

**Use it for**

- Create a redirect from the old URL automatically, so links and search results keep working.
- Tell whoever maintains ads or email campaigns that a landing page moved.

### What Product Handle Changed sends to your workflow

| Variable in Flow | Type | What it holds |
| --- | --- | --- |
| `product` | Product (Shopify) | The product whose handle changed. A full Shopify product, so all of its fields and Flow's own product actions are available. |
| `oldHandle` | Text | The previous URL handle of the product |
| `newHandle` | Text | The new URL handle of the product |
| `eventId` | Text | The id of this event. Search for it in the app's Event History to see exactly what was sent. |

### Example: Create a URL redirect from the old handle

1. Pick **Product Handle Changed** as the trigger.
2. Add the action **Create URL redirect**.
3. Set **Path** to `/products/{{oldHandle}}` and **Target** to `/products/{{newHandle}}`.
4. Turn the workflow on, then change a test product's handle and open the old URL.

> [!NOTE]
> **Good to know**
> - Shopify's product editor offers a redirect checkbox when you change a handle by hand. Bulk edits, imports and apps do not, and that is where this workflow earns its place.
> - If the merchant ticked the checkbox as well, the redirect already exists and Flow reports the action as failed. That is harmless.

## Product Description Changed

Fires when the description (body HTML) of a product changes.

| | |
| --- | --- |
| **Fires from** | Shopify webhook plus change detection |
| **Permission** | **Product, Variant & Collection Data Access** |
| **Switched on** | Turning on a Flow workflow that uses it switches it on. Its own switch is on the app's **Triggers** page |

**Use it for**

- Re-run a translation or an AI summary only when the source text really changed.
- Ask legal or compliance to re-check regulated products after an edit.
- Refresh a product feed that carries the description.

### What Product Description Changed sends to your workflow

| Variable in Flow | Type | What it holds |
| --- | --- | --- |
| `product` | Product (Shopify) | The product whose description changed. A full Shopify product, so all of its fields and Flow's own product actions are available. |
| `oldDescription` | Text | The previous description (truncated to 4000 characters; fetch the full description via the product reference if needed) |
| `newDescription` | Text | The new description (truncated to 4000 characters; fetch the full description via the product reference if needed) |
| `eventId` | Text | The id of this event. Search for it in the app's Event History to see exactly what was sent. |

### Example: Flag an edited description for review

1. Pick **Product Description Changed** as the trigger.
2. Add **Add product tags** with `needs-copy-review`.
3. Add **Send internal email** to the reviewer with `{{product.title}}` and a link to the product in the admin.
4. When the review is done the reviewer removes the tag. Pair it with **Product Tags Removed** if something should happen then.

> [!NOTE]
> **Good to know**
> - `oldDescription` and `newDescription` are cut at 4,000 characters. For the full text add a **Get product data** step or use `product.descriptionHtml`.
> - The comparison is on the HTML. An editor that rewrites markup without changing the visible text still counts as a change.

## Product Vendor Changed

Fires when the vendor of a product changes.

| | |
| --- | --- |
| **Fires from** | Shopify webhook plus change detection |
| **Permission** | **Product, Variant & Collection Data Access** |
| **Switched on** | Turning on a Flow workflow that uses it switches it on. Its own switch is on the app's **Triggers** page |

**Use it for**

- Move products between vendor-based collections or price lists.
- Notify purchasing when a product is reassigned to another supplier.

### What Product Vendor Changed sends to your workflow

| Variable in Flow | Type | What it holds |
| --- | --- | --- |
| `product` | Product (Shopify) | The product whose vendor changed. A full Shopify product, so all of its fields and Flow's own product actions are available. |
| `oldVendor` | Text | The previous vendor |
| `newVendor` | Text | The new vendor |
| `eventId` | Text | The id of this event. Search for it in the app's Event History to see exactly what was sent. |

### Example: Swap a vendor tag when the vendor changes

1. Pick **Product Vendor Changed** as the trigger.
2. Add **Remove product tags** with `vendor:{{oldVendor}}`.
3. Add **Add product tags** with `vendor:{{newVendor}}`.
4. Turn the workflow on.

> [!NOTE]
> **Good to know**
> An empty vendor arrives as an empty text, so `oldVendor` can be blank for products that never had one.

## Product Type Changed

Fires when the product type changes. This is the free-text **Type** field, not Shopify's standard product category.

| | |
| --- | --- |
| **Fires from** | Shopify webhook plus change detection |
| **Permission** | **Product, Variant & Collection Data Access** |
| **Switched on** | Turning on a Flow workflow that uses it switches it on. Its own switch is on the app's **Triggers** page |

**Use it for**

- Re-sort products into type-based automated collections or shipping profiles.
- Keep a tax or customs mapping in an external system current.

### What Product Type Changed sends to your workflow

| Variable in Flow | Type | What it holds |
| --- | --- | --- |
| `product` | Product (Shopify) | The product whose type changed. A full Shopify product, so all of its fields and Flow's own product actions are available. |
| `oldType` | Text | The previous product type |
| `newType` | Text | The new product type |
| `eventId` | Text | The id of this event. Search for it in the app's Event History to see exactly what was sent. |

### Example: Notify operations when a product changes type

1. Pick **Product Type Changed** as the trigger.
2. Add a **Condition** if only some types matter: `newType` **Equal to** `Hazardous`.
3. Add **Send internal email**: `{{product.title}} moved from type "{{oldType}}" to "{{newType}}"`.
4. Turn the workflow on.

## Product Tags Added

Fires when at least one tag is added to a product. `addedTags` holds only the new ones, so you do not have to compare lists yourself.

| | |
| --- | --- |
| **Fires from** | Shopify webhook plus change detection |
| **Permission** | **Product, Variant & Collection Data Access** |
| **Switched on** | Turning on a Flow workflow that uses it switches it on. Its own switch is on the app's **Triggers** page |

**Use it for**

- Start a process from a tag: `photo-ready`, `approved`, `clearance`.
- Let staff trigger automation from the product page without opening Flow.

### What Product Tags Added sends to your workflow

| Variable in Flow | Type | What it holds |
| --- | --- | --- |
| `product` | Product (Shopify) | The product whose tags changed. A full Shopify product, so all of its fields and Flow's own product actions are available. |
| `addedTags` | Text | Comma-separated list of tags that were added to the product |
| `removedTags` | Text | Comma-separated list of tags that were removed from the product in the same update (may be empty) |
| `currentTags` | Text | Comma-separated list of all current tags on the product |
| `previousTags` | Text | Comma-separated list of all previous tags on the product before this update |
| `eventId` | Text | The id of this event. Search for it in the app's Event History to see exactly what was sent. |

### Example: Publish a product when it gets the tag approved

1. Pick **Product Tags Added** as the trigger.
2. Add a **Condition**: `addedTags` **Includes** `approved`.
3. Under **Then** add **Update product status** with `Active` (or **Publish product** to a channel).
4. Add **Remove product tags** with `approved` if the tag is only a signal.
5. Turn the workflow on.

> [!NOTE]
> **Good to know**
> - The tag fields are comma-separated text, and **Includes** is a text search: `vip` also matches `vip-gold`. Pick tag names that are not part of another tag.
> - `previousTags` and `currentTags` hold the complete lists before and after, in case you need them.

## Product Tags Removed

Fires when at least one tag is removed from a product. `removedTags` holds exactly the ones that went away.

| | |
| --- | --- |
| **Fires from** | Shopify webhook plus change detection |
| **Permission** | **Product, Variant & Collection Data Access** |
| **Switched on** | Turning on a Flow workflow that uses it switches it on. Its own switch is on the app's **Triggers** page |

**Use it for**

- End a process when a status tag is taken off: a sale ends when `clearance` is removed.
- Undo what the matching **Tags Added** workflow did.

### What Product Tags Removed sends to your workflow

| Variable in Flow | Type | What it holds |
| --- | --- | --- |
| `product` | Product (Shopify) | The product whose tags changed. A full Shopify product, so all of its fields and Flow's own product actions are available. |
| `addedTags` | Text | Comma-separated list of tags that were added to the product in the same update (may be empty) |
| `removedTags` | Text | Comma-separated list of tags that were removed from the product |
| `currentTags` | Text | Comma-separated list of all current tags on the product |
| `previousTags` | Text | Comma-separated list of all previous tags on the product before this update |
| `eventId` | Text | The id of this event. Search for it in the app's Event History to see exactly what was sent. |

### Example: End a sale when the clearance tag is removed

1. Pick **Product Tags Removed** as the trigger.
2. Add a **Condition**: `removedTags` **Includes** `clearance`.
3. Add the actions that undo the sale, for example **Remove product tags** `sale-badge` and **Send internal email** to merchandising.
4. Turn the workflow on.

> [!NOTE]
> **Good to know**
> Replacing all tags in one save fires **both** tag triggers once: one with what was added, one with what was removed.

## Product Published to Channel

Fires when a product is published to one or more sales channels, markets or B2B catalogs. `channels.added` lists where it just appeared, `channels.current` everywhere it is now.

| | |
| --- | --- |
| **Fires from** | Shopify webhook plus change detection |
| **Permission** | **Publications Data Access** + **Product, Variant & Collection Data Access** |
| **Switched on** | Turning on a Flow workflow that uses it switches it on. Its own switch is on the app's **Triggers** page |

**Use it for**

- Start a launch checklist when a product reaches the Online Store.
- Push a product to a marketplace feed only once it is published to that channel.
- Tell the retail team when something becomes available on Point of Sale.

### What Product Published to Channel sends to your workflow

| Variable in Flow | Type | What it holds |
| --- | --- | --- |
| `product` | Product (Shopify) | The product that was published. A full Shopify product, so all of its fields and Flow's own product actions are available. |
| `channels` | Object | The channels added / removed / currently published, each a Publication (id, name, type, catalogId) |
| `channels.added` | List of objects | Channels the product was just published to in this event. |
| `channels.added (each).id` | ID | The publication GID (gid://shopify/Publication/...) |
| `channels.added (each).name` | Text | The channel / context name (e.g. Online Store, Point of Sale, Google & YouTube, a market name, or a B2B location name) |
| `channels.added (each).type` | Text | The kind of selling context: Sales Channel, Market, or B2B Location |
| `channels.added (each).catalogId` | ID | The catalog GID backing this publication (AppCatalog, MarketCatalog, or CompanyLocationCatalog) |
| `channels.removed` | List of objects | Channels the product was just unpublished from in this event. |
| `channels.removed (each).id` | ID | The publication GID (gid://shopify/Publication/...) |
| `channels.removed (each).name` | Text | The channel / context name (e.g. Online Store, Point of Sale, Google & YouTube, a market name, or a B2B location name) |
| `channels.removed (each).type` | Text | The kind of selling context: Sales Channel, Market, or B2B Location |
| `channels.removed (each).catalogId` | ID | The catalog GID backing this publication (AppCatalog, MarketCatalog, or CompanyLocationCatalog) |
| `channels.current` | List of objects | All channels the product is currently published to (after this change). |
| `channels.current (each).id` | ID | The publication GID (gid://shopify/Publication/...) |
| `channels.current (each).name` | Text | The channel / context name (e.g. Online Store, Point of Sale, Google & YouTube, a market name, or a B2B location name) |
| `channels.current (each).type` | Text | The kind of selling context: Sales Channel, Market, or B2B Location |
| `channels.current (each).catalogId` | ID | The catalog GID backing this publication (AppCatalog, MarketCatalog, or CompanyLocationCatalog) |
| `eventId` | Text | The id of this event. Search for it in the app's Event History to see exactly what was sent. |

### Example: Alert the retail team when a product reaches Point of Sale

1. Pick **Product Published to Channel** as the trigger.
2. Add a **Condition**: **At least one of** `channels.added` has `name` **Equal to** `Point of Sale`.
3. Add **Send internal email**: `{{product.title}} is now available on POS`.
4. Turn the workflow on.

> [!NOTE]
> **Good to know**
> - Needs **Publications Data Access** and **Product Data Access**, because the event carries the product as a reference.
> - A product that is created already published counts as published: the app seeds its baseline at creation so the first real publish is not lost.

## Product Unpublished from Channel

Fires when a product is removed from one or more sales channels. `channels.removed` lists where it disappeared.

| | |
| --- | --- |
| **Fires from** | Shopify webhook plus change detection |
| **Permission** | **Publications Data Access** + **Product, Variant & Collection Data Access** |
| **Switched on** | Turning on a Flow workflow that uses it switches it on. Its own switch is on the app's **Triggers** page |

**Use it for**

- Catch products that silently dropped off the Online Store.
- Pause ads for a product the moment it leaves the channel the ads point to.

### What Product Unpublished from Channel sends to your workflow

| Variable in Flow | Type | What it holds |
| --- | --- | --- |
| `product` | Product (Shopify) | The product that was unpublished. A full Shopify product, so all of its fields and Flow's own product actions are available. |
| `channels` | Object | The channels added / removed / currently published, each a Publication (id, name, type, catalogId) |
| `channels.added` | List of objects | Channels the product was just published to in this event. |
| `channels.added (each).id` | ID | The publication GID (gid://shopify/Publication/...) |
| `channels.added (each).name` | Text | The channel / context name (e.g. Online Store, Point of Sale, Google & YouTube, a market name, or a B2B location name) |
| `channels.added (each).type` | Text | The kind of selling context: Sales Channel, Market, or B2B Location |
| `channels.added (each).catalogId` | ID | The catalog GID backing this publication (AppCatalog, MarketCatalog, or CompanyLocationCatalog) |
| `channels.removed` | List of objects | Channels the product was just unpublished from in this event. |
| `channels.removed (each).id` | ID | The publication GID (gid://shopify/Publication/...) |
| `channels.removed (each).name` | Text | The channel / context name (e.g. Online Store, Point of Sale, Google & YouTube, a market name, or a B2B location name) |
| `channels.removed (each).type` | Text | The kind of selling context: Sales Channel, Market, or B2B Location |
| `channels.removed (each).catalogId` | ID | The catalog GID backing this publication (AppCatalog, MarketCatalog, or CompanyLocationCatalog) |
| `channels.current` | List of objects | All channels the product is currently published to (after this change). |
| `channels.current (each).id` | ID | The publication GID (gid://shopify/Publication/...) |
| `channels.current (each).name` | Text | The channel / context name (e.g. Online Store, Point of Sale, Google & YouTube, a market name, or a B2B location name) |
| `channels.current (each).type` | Text | The kind of selling context: Sales Channel, Market, or B2B Location |
| `channels.current (each).catalogId` | ID | The catalog GID backing this publication (AppCatalog, MarketCatalog, or CompanyLocationCatalog) |
| `eventId` | Text | The id of this event. Search for it in the app's Event History to see exactly what was sent. |

### Example: Warn when a product leaves the Online Store

1. Pick **Product Unpublished from Channel** as the trigger.
2. Add a **Condition**: **At least one of** `channels.removed` has `name` **Equal to** `Online Store`.
3. Add **Send internal email** or a Slack request with `{{product.title}}` and the admin link.
4. Turn the workflow on.

> [!NOTE]
> **Good to know**
> Archiving a product unpublishes it everywhere, so this can fire together with **Product Status Changed**.

## Next steps

- [Complete trigger reference](https://docs.workflow-trigger-extensions.app/trigger-reference.md) - every trigger on one page.
- [Choosing the right trigger](https://docs.workflow-trigger-extensions.app/choosing-the-right-trigger.md) - which one fits your case.
- [Custom triggers](https://docs.workflow-trigger-extensions.app/custom-triggers.md) - when no built-in trigger fits, write your own.
- [Event history and troubleshooting](https://docs.workflow-trigger-extensions.app/event-history-and-troubleshooting.md) - see what fired and what it carried.
- [Trigger events on a record's admin page](https://docs.workflow-trigger-extensions.app/admin-blocks.md) - the latest events of one record, on its own page in the Shopify admin.
