Run works on their own
Run a work when something happens in the store, on a schedule, or from a Shopify Flow workflow.
Most works run when something calls them: a storefront request, a button on a merchant page, or an agent. Automations are the other half of most builds, such as “when this happens” or “every morning”. A published work can run when a Shopify event happens, on a schedule in the store’s time zone, or from a merchant’s Shopify Flow workflow.
| Trigger | Runs | Example |
|---|---|---|
{"type": "event", "topic": …, "when"?: …} | when something happens in the store | email a waiting list when a product is back in stock |
{"type": "schedule", "cron": …} | on a schedule, in the store’s time zone | a weekday 8:00 digest of new quote requests |
{"type": "flow"} | from the “Run a Littleworks work” action in Shopify Flow | award loyalty points in a merchant’s order workflow |
Shopify events
An event work names a Shopify topic and, optionally, a when filter on the event’s payload. Events that don’t match are skipped before a run starts and cost nothing.
{
"name": "back_in_stock.notify",
"feature": "back_in_stock",
"title": "Notify signups when back in stock",
"trigger": { "type": "event", "topic": "inventory_levels/update", "when": { "available": { "gt": 0 } } },
"permissions": { "shopify": true }
}export default async ({ input, db, shopify, log }) => {
const { payload } = input;
// Inventory events carry the inventory item, not the product: look it up.
const found = await shopify.admin.graphql(
`query ($id: ID!) { inventoryItem(id: $id) { variant { product { id title } } } }`,
{ id: "gid://shopify/InventoryItem/" + payload.inventory_item_id });
const product = found.data?.inventoryItem?.variant?.product;
if (!product) return { notified: 0 };
const signups = db.collection("signups");
const waiting = await signups.list({ where: { productId: product.id, status: "waiting" }, limit: 100 });
for (const signup of waiting.items) {
// Send the email through the merchant's provider with http.fetch, then mark the signup
// so a repeated delivery never emails anyone twice.
await signups.patch(signup.id, { set: { status: "notified", notifiedAt: new Date().toISOString() } }, { revision: signup.revision });
}
log.info("Notified", waiting.items.length, "people that", product.title, "is back in stock");
return { product: product.title, notified: waiting.items.length };
};| Topic | When | Shopify permission |
|---|---|---|
inventory_levels/update | inventory changes at a location | read_inventory |
products/create, products/update | a product is created or changes | read_products |
collections/update | a collection changes | read_products |
orders/create, orders/paid, orders/fulfilled, orders/cancelled, refunds/create | order events | read_orders |
fulfillments/create | a fulfillment is created | read_fulfillments |
customers/create | a customer is created | read_customers |
- Permissions. Publishing checks that the store granted the topic’s permission, and links to Shopify permissions if not.
- Protected customer data. Order, fulfillment, refund and customer events carry protected customer data. They become available once Littleworks has Shopify’s approval to handle it; until then, publishing them explains why it can’t.
- `when`. One to five conditions on payload fields, all of which must hold. Each is a value to equal, or an operator:
in,gt,gte,lt,lte,neorexists. - What the work receives.
inputis{event: {topic, id, triggeredAt, shop, attempt, previousRunIds}, payload}, wherepayloadis Shopify’s event body.context.triggeris"event". - Subscriptions. Littleworks subscribes the store when the first published work for a topic appears, keeps the subscription healthy, and removes it when no work needs it.
How delivery works
- Once per work. Littleworks records each Shopify delivery once and runs each matching published work at most once for it. Shopify can still send separate deliveries for the same change, so mark what a run has processed, such as
notifiedAtabove, and skip it next time. - Retries. A run that fails retries after 1 minute, 10 minutes, 1 hour and 6 hours. Each retry carries
event.attemptand the earlierpreviousRunIds, so the work can check what already happened before repeating a change. A deliberate error response counts as handled. - Order. Events can arrive out of order. Compare
event.triggeredAtor the payload’supdated_atwith what you stored. - Paused works. Events that arrive while a work is paused are dropped and counted, not queued, so resuming never replays a burst of stale events.
- Bursts and allowances. Runs count against the store’s run allowance, and skipped events are free. A burst of events, such as an inventory import, drains at most four runs at a time per store. If the allowance runs out, events wait up to 24 hours, then expire with a notice.
Schedules
"trigger": { "type": "schedule", "cron": "0 8 * * 1-5" }- Format. Five cron fields (minute, hour, day of month, month, day of week) in the store’s time zone. A schedule can run at most every 5 minutes; a 5-minute schedule uses 288 runs a day, so choose the least frequent one that does the job.
- What the work receives.
inputis{schedule: {scheduledFor, timezone, cron}}, and each slot runs once. - Downtime and the clocks. After downtime, the latest missed slot runs once. When the clocks go forward, a skipped time runs at the next valid minute; when they go back, a repeated time runs once.
Shopify Flow
Shopify Flow is the merchant’s own automation tool, available on every paid plan. Littleworks adds one action to it, Run a Littleworks work. Agents can’t create Flow workflows, so the merchant adds the action in Flow.
"trigger": { "type": "flow" }- Run a Littleworks work. The action runs a published work whose trigger is
{"type": "flow"}. The merchant enters the work’s name in Work and its fields in Input. The work receives those fields plusflow: {actionRunId}. It runs once per Flow action run, even when Flow sends the action again, and its result returns to Flow. - Input. Write one field per line, such as
points: 50, using Flow variables as you like. Pass the workflow’s Shopify objects as fields:order_id: {{order.id}},product_id: {{product.id}}orcustomer_id: {{customer.id}}.true,false,nulland numbers keep their type, values starting with[or{are JSON, a value in double quotes stays text, and dotted keys such ascustomer.emailnest. Blank lines and lines starting with#are ignored. A JSON object works too. Flow fills in variables first, so a product title with quotes in it is just text. - Checked when you save. Littleworks checks the action when the workflow is saved: the work must exist, be published and run from Flow, and Input must have fields that fit what the work takes. Problems show on the Work or Input field in the Flow editor. Values from variables are checked when the workflow runs.
- Setting it up. Agents that build a Flow work tell the merchant exactly what to add: the Run a Littleworks work action, the Work name, and example Input lines, such as
points: 50andorder_id: {{order.id}}. Littleworks → Automations lists the works Flow can run, with each name to copy. - Starting workflows. Littleworks has no Flow triggers, so works can’t start Flow workflows. Start a workflow from one of Shopify’s triggers, such as Order created, and run a work from it.
Test, publish and watch
- Test as a draft.
invoke_functionwithtrigger: {event: {topic, payload?}}or{schedule: {scheduledFor?}}builds the input exactly as a real delivery would. A missing payload uses a realistic sample for the topic. From the CLI:littleworks invoke back_in_stock.notify --event inventory_levels/update --payload restock.jsonor--schedule --at 2026-10-05T08:00:00-04:00. Test runs are recorded as tests, but Shopify calls and external requests are real. - Publish.
publish_functionsubscribes the store or schedules the next run.get_functionshows the subscription and the last seven days of events, or the next scheduled run. - Watch.
list_events(CLI:littleworks events) shows each delivery and what it did for each work. A work can run, be skipped bywhen, be dropped while paused, wait for a retry, fail after retries, or expire. In Shopify admin, Littleworks → Automations lists every automation and its recent activity, and Runs shows what started each run.