自定义触发器
内置触发器涵盖了大多数商家关注的变更。自定义触发器则涵盖其余情况:您只需选择一个Shopify事件,编写几行JavaScript代码来判断该事件是否重要以及需要发送哪些数据,它就会成为您可以在Shopify Flow中使用的触发器。
它解决了内置触发器无法解决的三个问题:
- **仅在满足您设定的条件时才触发。**例如,“只有当订单金额超过500欧元时,才为其添加
vip标签”只需一行代码,而不需要创建一个针对每笔订单运行并进行筛选的工作流。 - 在状态转换时触发,而非在某个状态下。你的代码会看到变化前后的值,因此“状态从草稿变为活跃”或“库存跌破5”这种情况是可能的。而基于当前值的条件无法表达这一点。
- **仅发送您需要的字段。**将事件重新塑造成您的工作流实际使用的值,而不是逐个读取它们。
自定义触发器甚至不需要Shopify事件:它也可以按计划运行,并自行判断发生了哪些变化。
工作原理
- 选择触发条件:一次Shopify事件,还是一个计划任务。
- 对于某个事件,您可以选择它监听的事件 - - 即应用程序已经接收到的任何Shopify事件 - - 或者从模板开始,系统会自动选择事件并填充代码。
- **捕获真实的有效载荷。**在您的商店中进行相应修改后,该应用便会捕获确切的事件正文。
- 编写一个转换函数 - - 若要触发,则返回一个对象;若要跳过,则返回
null。 - 将其与捕获的事件进行对比测试,从而准确了解您的工作流会收到什么内容。
- 打开它。

编写转换规则
您的触发器是一个 JavaScript 模块,该模块导出一个名为 transform 的函数。该函数接收四个参数:
payload- 原始的Shopify事件主体,完全按其接收时的原样呈现。topic- 触发了哪个事件,例如PRODUCTS_UPDATE。shop- 您的 myshopify 域名。ctx-ctx.log(...)会在编辑器旁边的日志面板中输出内容,ctx.shopify(...)会执行一个 Admin GraphQL 查询,而ctx.fetch(...)会调用互联网上的某个 URL。
你返回的内容决定了接下来会发生什么:
- 返回一个对象,触发器随即触发,并携带该对象。
- 返回
null,该事件将被跳过。这就是过滤机制的工作原理 - - 无需学习单独的过滤语言。 - 将该文件留空,它就会在该类型的每个事件发生时触发。
/**
* Only fire for high-value orders carrying the vip tag.
*/
export async function transform(payload, topic, shop, ctx) {
const total = parseFloat(payload.total_price || "0");
const tags = (payload.tags || "").split(",").map(t => t.trim());
if (total < 500) return null;
if (!tags.includes("vip")) return null;
ctx.log("firing for", payload.name, total);
return {
orderId: payload.admin_graphql_api_id,
orderNumber: payload.name,
total,
currency: payload.currency,
customerEmail: payload.email,
};
}变更前的数值
Shopify
在**“产品更新**”、“订单更新”和**“客户更新**”中,payload._changes 会列出此次更新所修改的每个受监控字段,并分别提供 oldValue 和 newValue:
- 产品:
title、handle、description、status、vendor、productType、tags - 订单:
financialStatus、fulfillmentStatus、tags、note、lineItems以及customAttributes.<name> - 客户:
tags、note、state(ENABLED、DISABLED、INVITED、DECLINED)
当更新内容不涉及这些字段(例如库存变动)时,或者应用程序首次读取该记录时,该列表为空。你在编辑器中捕获的示例事件会显示该字段,因此你可以在对其进行写入操作之前,先查看其真实结构。
/**
* Fire only when a product BECOMES active - not on every later edit of an
* active product, which is what a condition on the current status would do.
*/
export async function transform(payload, topic, shop, ctx) {
const change = (payload._changes || []).find((c) => c.field === "status");
if (!change) return null;
if (change.oldValue !== "draft" || change.newValue !== "active") return null;
return {
productId: payload.admin_graphql_api_id,
title: payload.title,
oldStatus: change.oldValue,
newStatus: change.newValue,
};
}更具体的事件本身就包含新旧值,因此那里无需使用_changes:价格变更包含oldPrice、newPrice和percentChange;库存变更包含_oldAvailable、_newAvailable和_delta;元字段变更包含previousValue,以及与其对应的metafield.value。
从模板开始
在编辑器中打开“Fires on”后,选择**“从模板开始**”即可选定事件并生成可运行的代码。编辑顶部的常量,进行测试,然后保存。每个模板都会读取更改前后的值:
- 某产品、订单或客户字段的值发生了变化 - - 状态从“草稿”变为“有效”,订单变为“已付款”,账户变为“已启用”。
- 价格下降了超过N个百分点 - - 这才是真正的降价,并非每次价格调整都是如此。
- 库存跌破阈值 - - 触发一次,即在库存刚好跌破该阈值时触发,而非在库存已处于低位时每次销售都触发。
- 产品元字段数值超过阈值 - - 评分低于3,毛利率高于40。
- “高价值客户已付款的订单” - - 读取客户在您店铺的累计消费金额,仅当金额超过您设定的阈值时触发。

使用 ctx.shopify 获取额外数据
Webhook 正文仅包含 Shopify 发送的字段。如果您需要其他信息(例如客户的订单数量、变体的库存、元字段等),请在转换中直接查询 Admin API:
export async function transform(payload, topic, shop, ctx) {
const data = await ctx.shopify(`
query($id: ID!) {
customer(id: $id) { numberOfOrders tags }
}
`, { id: payload.customer.admin_graphql_api_id });
// Only fire for repeat customers
if (data.customer.numberOfOrders < 5) return null;
return {
orderId: payload.admin_graphql_api_id,
orderCount: data.customer.numberOfOrders,
};
}它会返回查询的data,如果查询出现错误,则会抛出异常,这样错误就会在测试中显现出来,而不是悄无声息地返回空结果。
三个值得了解的限制:
- **每次执行最多处理 10 个调用。**数据增强只需处理少量数据;超过这个数量通常需要使用循环。只要可能,请尽量通过单个查询获取所需数据。
- **它只读取数据,不写入数据。**它使用您授予的权限,且该应用仅会请求读取权限。除非在“权限”页面上授予了“订单数据访问”权限,否则查询订单数据将失败。若要在您的商店中进行更改,请在触发器后面的“流程”操作中进行。
- **您店铺的凭据绝不会进入您的代码。**该查询是由应用代表您执行的,因此沙盒内不存在会泄露的访问令牌。
ctx.fetch(url, options) 其工作原理类似于浏览器中的 fetch,适用于公共互联网上的任何内容:供应商的库存数据流、汇率,或是您自己的 API。内部和私有网络地址将被拒绝。如果您的触发代码已保存到 GitHub,请勿在其中包含 API 密钥。
按计划运行的触发器
Shopify Flow 当发生某些情况时,它会做出反应。如果什么都没发生,它就不会做出反应,而且它也无法察觉商店之外的任何情况。为此,请在**“触发条件**”下选择**“按计划**执行”。此类触发器背后没有Shopify事件:您的代码会按一定间隔运行(从每30秒一次到每天一次不等),并自行判断什么情况算作变化。
这与transform函数相同,只是payload不同,且返回值也不同:
payload.state- 无论您的代码在上一次运行时返回了什么state值。首次运行时返回的是null。payload.now以及payload.lastRunAt- 时间戳。- 返回
{ state, events }:待保存的新状态(最多 32 KB)以及待触发的事件列表(每次运行最多 100 个)。每个事件都会触发 Custom Trigger 一次;如果设置了事件的resourceId,该值将成为 Flow 中的记录 ID。若无需报告的内容,则返回null。
**确保首次运行时仅进行记录,切勿触发。**在首次运行时,你的代码没有可供比较的对象,因此一切看起来都是新的。将观察到的内容存储起来,且不返回任何事件;这样,即使启用触发器,也不会导致工作流被大量事件淹没。所有模板都是这样处理的。

// Fires when a value at a JSON URL changes, with the value before and after.
const URL = "https://api.example.com/stock/1042"
export async function transform(payload, topic, shop, ctx) {
const res = await ctx.fetch(URL, { headers: { accept: "application/json" } })
if (!res.ok) throw new Error("The URL answered with HTTP " + res.status)
const value = String((await res.json()).stock)
// The first run only remembers the value.
const previous = payload.state ? payload.state.value : undefined
if (previous === undefined || previous === value) return { state: { value } }
return {
state: { value },
events: [{ oldValue: previous, newValue: value }],
}
}“计划”卡片中**“基于模板开始**”下的计划触发器模板:
- 产品、订单或客户已 N 天未更新 - - 过期的商品评论、滞留订单、客户回流。每个记录在每个“静默期”内触发一次,且在启用该功能时已逾期的记录不会一次性全部触发。
- Shopify 之外的某个值发生了变化 - - 供应商库存数据、汇率、价格表,其中数值会显示差额和百分比。
- RSS 或 Atom 源中的新项目 - - 供应商新闻、商品列表源、状态页面。
- N小时内无订单 - - 这是您店铺的“心跳”。当订单停止时触发一次,订单恢复时再触发一次。
“测试”会将您的代码运行一次,在此过程中既不会触发任何操作,也不会保存状态,因此您可以随心所欲地运行它。
在 Shopify Flow 中使用它
每个自定义触发器在 Flow 中都会以相同的名称显示:自定义触发器。将其添加到工作流中,然后添加一个条件:触发器标识符等于您的触发器标识符。
该标识会显示在触发器的页面上,并且永远不会改变,即使你重命名了该触发器 - - 因此你的工作流仍能正常运行。
转换返回的每个键都会成为触发器上的一个字段,如果你更愿意自己解析该对象,整个对象也可以以 JSON 格式获取。
将代码保存在 GitHub 上
您可以连接一个 GitHub 仓库,让触发器代码置于版本控制之下。这样,更改内容将作为拉取请求供人审查,您可以查看是谁做了哪些修改,并且可以回滚不再起作用的转换。
请在“开发者”页面下的“连接”部分进行连接。您可以选择应用可以访问哪些仓库,并且可以随时在 GitHub 上撤销该访问权限。
一旦选定了一个存储库,所有现有的触发器会立即写入其中,并且该存储库在两个方向上都能保持同步:
| 你的工作内容 | 会发生什么 |
|---|---|
| 在应用中创建、编辑或复制触发器 | 该文件已提交到您的代码库 |
| 在应用中删除触发器 | 该文件已从您的代码库中删除 |
| 将更改推送到关联的分支 | 应用中更新了触发器的代码 |
每个触发器都是一个以该触发器句柄命名的文件,其中包含的正是你在编辑器中看到的那个模块 - - 没有任何额外的包装。这意味着你可以像处理其他任何 JavaScript 文件一样,在自己的编辑器中打开它、运行它并对其进行代码检查。
恢复到较早的版本
无需了解 Git 即可撤销更改。一旦连接了代码库,编辑器会显示一个**“版本**”下拉菜单,其中列出了该文件的所有历史版本,并附有日期和作者信息。选择其中一个版本,它就会以未保存的更改形式加载到编辑器中,这样你就可以先阅读内容 - - 保存操作才会将其恢复。
在本地编辑时进行测试
如果您正在自己的编辑器中编辑该文件,则无需先将其保存到应用中,即可直接使用该文件处理捕获的样本事件。请使用开发者页面中具有**“执行**”权限级别的 API 密钥:
curl -X POST https://shopify.workflow-trigger-extensions.app/api/v1/triggers/custom/high-value-vip-order/test \
-H "Authorization: Bearer ftk_your_key_here" \
-H "Content-Type: application/json" \
-d "$(jq -Rn --rawfile c flow-triggers/high-value-vip-order.js '{code:$c}')"你会得到与应用程序“测试”按钮显示的结果完全一致的内容:是否会触发、输出对象、你的ctx.log语句以及运行时间。
该端点既不会触发任何操作,也不会保存任何内容,且不占用任何计划配额 - - 因此,在文件监视器每次保存时运行它都是安全的。(应用内的**“运行测试**”按钮会触发您的 Flow 工作流,以便您观察其端到端的运行过程;此操作同样免费。)
您还可以使用读取密钥单独获取示例有效载荷,将其保存到本地,并完全在离线状态下运行该文件:
curl https://shopify.workflow-trigger-extensions.app/api/v1/triggers/custom/high-value-vip-order/test \
-H "Authorization: Bearer ftk_your_key_here"自定义触发器会占用我的套餐配额吗?▾
是的,它会统计所有监听到的事件 - - 而不仅仅是实际触发的那些。如果你的触发器监听“产品更新”事件,而你的商店每月有 60,000 次产品更新,那么即使你的代码只在其中 100 次更新时被触发,这也仍算作 60,000 个事件。
我们会接收、去重并排队处理每个事件,然后在隔离的沙箱中运行您的代码 - - 所有这些操作都在您的代码决定是否触发之前完成。该计数反映了上述工作量。
换句话说:其费用与同一事件的内置触发器相同。您无需为过滤功能支付额外费用,且测试始终免费。定时触发器每次运行仅计费一次。
检测是免费的吗?▾
是的。无论是应用中的“运行测试”按钮,还是/test API 端点,均不计入配额,尽管“运行测试”确实会触发您的 Flow 工作流,以便您观察其运行过程。只有实际执行才会消耗您的配额,因此您可以随心所欲地对转换进行迭代。
为什么 payload._changes 为空?▾
要么是更新内容超出了受监控字段的范围 - - 例如产品的库存或变体变更 - - 要么是该应用尚未为该记录建立基线。基线是在应用首次检测到某条记录时存储的,因此,对于应用从未见过的记录,其首次更新不会包含历史值。此后每次更新都会包含历史值。
我的代码能否修改存储中的数据?▾
不,ctx.shopify 仅使用您授予的读取权限运行,该应用绝不会请求写入权限。这是有意为之:如果某个触发器修改了其监听的记录,它会重新触发自身,而这正是 Shopify 禁用工作流的循环原因。请在紧随该触发器之后的“流程”操作中修改数据。
我以后可以更换把手吗?▾
不,这是有意为之。你的 Flow 工作流会根据处理程序名称进行过滤,因此更改处理程序名称会导致该工作流在后台停止运行。你可以随意重命名触发器 - - 处理程序名称保持不变。
该用户名同时也是您 GitHub 仓库中的文件名,因此它也不会发生变化。
如果我的代码有错误,会怎么样?▾
该事件将被跳过,且错误会记录在触发器中,因此您可以查看出了什么问题。转换失败绝不会阻塞其他任何操作 - - 您的其他触发器(无论是自定义的还是内置的)都会照常运行,不受影响。
我的代码在哪里运行?▾
在一个与应用程序其余部分隔离的沙箱环境中,该环境设有短暂的时间限制,且无法访问您商店的凭据。它仅能看到您捕获的事件有效载荷,以及您通过 ctx.shopify 或 ctx.fetch 获取的数据。
如果我同时在 GitHub 和应用中编辑该文件会怎样?▾
最后保存的那一方将胜出。在应用中保存会将更改提交到文件,而推送到关联的分支则会覆盖应用中保存的代码。如果你主要在代码库中工作,请将应用的编辑器视为只读模式,以免给自己造成意外。
我仓库中的文件能否创建一个新的触发器?▾
不。触发器还需要知道它监听的是哪个Shopify事件,而该文件只包含代码 - - 如果凭猜测确定事件,可能会将其与错误的对象关联起来。请先在应用中创建触发器,然后再自由编辑其文件。
它能否在应用程序尚未接收到的事件发生时触发?▾
不。自定义触发器会监听应用已为您商店订阅的事件,具体取决于您授予的权限。只要授予某项资源的权限,该资源的事件也会对自定义触发器开放。对于其他情况,请按计划执行。
下一步
- 开发者 API、MCP、GitHub 和触发器模拟 - 通过您自己的代码或 AI 助手来管理和测试触发器。
- 使用人工智能生成触发代码 - 用通俗易懂的语言描述触发条件,然后让 AI 编写代码。
- 套餐与使用情况 - 哪些情况算作“事件”,以及津贴是如何发放的。
- 触发器的工作原理 - 内置触发器及其触发机制。

