Skip to Content
BindingsShopify

Shopify

Connecting a store does two things:

  1. Workflows get ctx.SHOPIFY, which reads and changes that store’s products, orders, customers, subscriptions, and discounts.
  2. Store events, such as an order, a new customer, a checkout, or a subscription change, can start a workflow.

One organization connects one store. Every Shopify call a loop makes goes to that store and no other.

Connect

  1. Go to Settings → Shopify.
  2. Enter your store’s domain, for example your-store.myshopify.com, and click Connect Shopify.
  3. Approve the app in Shopify. You come back to the same page, now showing the store as connected and the access it granted.

Installing from the Shopify App Store lands you on the same page with the store filled in. Sign in, then click Connect Shopify.

To disconnect, uninstall the app from your Shopify admin. Loops can’t call the store from then on. Reconnect runs the approval again, which you need after we add new access.

Access the app asks for

AccessUsed for
read_products, read_inventoryProduct search, product details, stock
read_orders, write_ordersOrder lookups, order notes and tags, order and checkout events
read_customers, write_customersCustomer lookups, customer notes and tags, sign-up events
read_own_subscription_contracts, write_own_subscription_contractsSubscription lookups, pause, resume, skip, and cancel
read_discounts, write_discountsSingle-use discount codes

Shopify only lets an app manage subscription contracts that app created. Contracts sold through another subscription app are not visible to ctx.SHOPIFY.

Start a workflow from a store event

Open a workflow loop, go to Settings, and under Run this loop when… pick the events:

EventShopify topic
An order is placedorders/create
A customer signs upcustomers/create
A checkout is started or abandonedcheckouts/update
A subscription changessubscription_contracts/update

Each event starts one run of every loop that picked it. The run’s event.body is:

type ShopifyEvent = { source: "shopify"; topic: "orders/create" | "customers/create" | "checkouts/update" | "subscription_contracts/update"; shop: string; webhookId: string | null; triggeredAt: string | null; customerId: string | null; payload: unknown; };

payload is the webhook body exactly as Shopify sent it. When the event names a customer, the run is linked to the user shopify:<customerId> in Users.

checkouts/update fires every time a checkout changes, not only when it is abandoned.

import { workflow } from "@loops/sdk"; type ShopifyEvent = { source: "shopify"; topic: string; shop: string; webhookId: string | null; triggeredAt: string | null; customerId: string | null; payload: unknown; }; export const firstOrderWorkflow = workflow<ShopifyEvent>({ id: "first-order", execute: async ({ step, event }) => { const { customerId } = event.body; if (!customerId) return { skipped: "no customer" }; const orders = await step.do("Count their orders", ({ ctx }) => ctx.SHOPIFY.listCustomerOrders({ customerId, first: 2 }), ); if (orders.length > 1) return { skipped: "returning customer" }; return step.do("Tag a first-time customer", ({ ctx }) => ctx.SHOPIFY.updateCustomer({ id: customerId, addTags: ["first-order"] }), ); }, });

Call the store

ctx.SHOPIFY is on ctx inside step.do. Ids accept a number (7208609448069) or a Shopify GraphQL id (gid://shopify/Customer/7208609448069). Every call throws if no store is connected or Shopify rejects it.

MethodWhat it does
shop()The connected store’s domain, or null
searchProducts({ query?, first? })Products matching Shopify search syntax, for example title:*magnesium* or tag:sleep
getProduct({ id })One product, every variant, and the subscription selling plans it offers
getInventory({ variantId })Stock for one variant at each location
getOrder({ id })One order with line items, status, and tracking
listCustomerOrders({ customerId, first? })A customer’s orders, newest first
getCustomer({ id })Contact details, tags, note, order count, total spent
searchCustomers({ query, first? })Customers matching Shopify search syntax, for example email:ana@example.com
updateCustomer({ id, addTags?, removeTags?, note? })Tag a customer or replace their note
updateOrder({ id, addTags?, removeTags?, note? })Tag an order or replace its note
listSubscriptions({ customerId, first? })A customer’s subscription contracts
getSubscription({ id })Status, next billing date, cadence, and lines
pauseSubscription({ id })Pause a subscription
resumeSubscription({ id })Resume a paused subscription
skipSubscriptionCycle({ id, cycleIndex?, date? })Skip the next billing cycle, or the one you name
cancelSubscription({ id })Cancel a subscription
createDiscountCode({ percentage?, amount?, code?, title?, customerId?, endsAt?, minimumSubtotal? })A single-use code. Pass exactly one of percentage or amount
refundOrder({ orderId, amount?, note?, notify? })Refund to the original payment. Leave out amount to refund what is left
verifyCustomer({ customerId, signature })Check a signed Shopify customer id

createDiscountCode and refundOrder return { status: "pending_approval", approvalId, message } instead of acting when Discounts or Refunds need approval in Controls.

Let an agent use the store

An agent reaches the store through a workflow. Give it a tool that triggers one:

subscriptions.ts
import { workflow } from "@loops/sdk"; type Change = { contractId: string; choice: "skip" | "pause" | "cancel" }; export const changeSubscriptionWorkflow = workflow<Change>({ id: "change-subscription", execute: async ({ step, event }) => { const { contractId, choice } = event.body; return step.do(`Apply ${choice}`, ({ ctx }) => { if (choice === "skip") return ctx.SHOPIFY.skipSubscriptionCycle({ id: contractId }); if (choice === "pause") return ctx.SHOPIFY.pauseSubscription({ id: contractId }); return ctx.SHOPIFY.cancelSubscription({ id: contractId }); }); }, });

See Calling a workflow for the tool. Check who is asking before a tool changes a customer’s orders or subscriptions.

Customer privacy

Shopify sends the app its privacy requests, and Loops handles them:

  • Customer data request. Recorded in your audit log so you can answer it.
  • Customer erasure. Deletes the user shopify:<customerId> with their timeline, and clears the input of every run that store events started for that customer.
  • Shop erasure, 48 hours after uninstall. Deletes the store connection and clears the input of every run a store event started.
Last updated on