How to create a function

A function is a piece of JavaScript or TypeScript that Shopify Flow can run. This page walks through creating one from scratch, testing it, and getting it ready to use in a workflow.

Before you start

Nothing is required to create your first function. If you want your code to read or modify store data, open Permissions first and grant the resources you need (Products, Orders, Customers, Discounts). You can always come back and grant more later.

Step 1: Create the function

  1. Open Functions in the app navigation.
  2. Click Create function.

You land in the function editor with starter code already filled in. The editor has five tabs: General, Code, Variables, Test, and Settings.

Prefer to start from something that already works? Click Browse templates on the Functions page instead. Templates cover common jobs such as tagging a customer, hiding out-of-stock products, tagging high-value orders, creating a discount code, and forwarding data to a webhook. Choosing Use creates a real function immediately and drops you in this same editor.

Step 2: Name it (General tab)

  • Name is required. This is the name you will pick from the dropdown in Shopify Flow, so make it describe what it does, for example "Tag VIP customers".
  • Language is JavaScript or TypeScript. TypeScript types are stripped before execution, so both run the same way.
  • Description is optional and only for you and your team.

Step 3: Write the code (Code tab)

Your function must export one default async function that takes (input, ctx):

export default async function (input, ctx) {
  // input: the JSON sent by the Flow action
  // ctx:   helpers provided by the sandbox

  ctx.log("running with", input)

  return { ok: true, received: input }
}

What you get

Item What it does
input The JSON passed in from Flow, or from the Variables tab when testing
ctx.shop Your store domain, for example my-store.myshopify.com
ctx.log(...) Writes a line to the run logs. Accepts several arguments like console.log
ctx.fetch(url, options) Outbound HTTP request. Returns { status, ok, headers, text(), json() }
ctx.shopify.graphql(query, variables) Shopify Admin GraphQL. Only available when Needs Shopify data is on
secrets.NAME A value you saved on the Secrets page

What you return

Whatever you return becomes the output of the Flow action. Return something that can be serialized to JSON, such as a plain object:

return { tagged: true, total: 129.5 }

Returning nothing is fine too. The output will simply be empty.

Things that are not available

There is no require, no process, no filesystem, and no raw fetch. Nothing persists between runs. Every run starts from a clean sandbox, so if you need to remember state, write it to Shopify (a metafield or a tag) or to your own system.

Step 4: Add test data (Variables tab)

The Variables tab holds a JSON object that is passed to your code as input when you press Run test. It is saved with the function as a sample, so you can come back and re-test later.

{
  "customerId": "gid://shopify/Customer/123456789",
  "tags": ["vip"]
}

If your function has Needs Shopify data turned on, three buttons appear that fill in real ids from your store, so your first test works against real data:

  • Find product opens the Shopify resource picker and inserts the chosen productId.
  • Latest order inserts the most recent orderId.
  • Latest customer inserts the most recent customerId.

These variables are test data only. When the function runs from Flow, input comes from the Input (JSON) field on the Run Function action instead.

Step 5: Run a test (Test tab)

Click Run test from the Code or Variables tab. The Test tab shows:

  • Status: Success, Failed, or Timeout, with the duration in milliseconds.
  • Output: your return value, as a browsable tree.
  • Logs: everything you sent to ctx.log(...), with timestamps.
  • Error: the message and reason if the run failed.

Test runs do not count against your plan quota, so iterate as much as you like. They are recorded in History with the trigger Manual, so you can look back at them later.

If you have unsaved changes, clicking Run test or switching tabs asks you to save or discard first. Tests always run against saved code, so what you test is what Flow will run.

Step 6: Configure limits and access (Settings tab)

Setting Default Notes
Enabled On A disabled function cannot run from Flow. Existing workflows that use it will report an error
Needs Shopify data Off Turn on to get ctx.shopify.graphql(...). Also grant the matching permissions on the Permissions page
Timeout (ms) 10000 Maximum 30000. The run is stopped when the timeout is reached
Memory (MB) 128 Maximum 512. Raise it only if a run fails on memory

Keep the timeout as low as your function realistically needs. A tight timeout means a stuck external API fails fast instead of holding up your workflow.

Step 7: Save

Use Save in the save bar at the top of the page. Your function is now available in Shopify Flow.

Using store data

To read or write Shopify data:

  1. Turn on Needs Shopify data on the Settings tab.
  2. Open Permissions and grant the resources your code touches. Read and write are granted separately per resource.
  3. Call ctx.shopify.graphql(query, variables) in your code.
export default async function (input, ctx) {
  const { data } = await ctx.shopify.graphql(`
    query($id: ID!) {
      product(id: $id) { id title totalInventory status }
    }
  `, { id: input.productId })

  ctx.log("product", data.product.title)

  return { inventory: data.product.totalInventory }
}

Your store's access token never enters the sandbox. Calls are proxied server side and Shopify enforces exactly the permissions you granted. If a query needs a permission you have not granted, Shopify returns an access denied error in the response.

If you later revoke a permission on the Permissions page, every function that uses it is disabled automatically and shows a banner telling you which permission is missing. Granting it again re-enables them.

Calling an external API

ctx.fetch works like the browser fetch, with guardrails:

export default async function (input, ctx) {
  const res = await ctx.fetch("https://api.example.com/orders", {
    method: "POST",
    headers: {
      "content-type": "application/json",
      authorization: `Bearer ${secrets.PARTNER_API_KEY}`,
    },
    body: JSON.stringify(input),
  })

  if (!res.ok) {
    throw new Error(`Partner API returned ${res.status}`)
  }

  return await res.json()
}

Requests to internal or private network addresses are blocked. Response size, redirects, and per-request time are capped, and there is a limit on how many requests a single run can make.

Editing, deleting, and reusing

  • View run history: the primary action on a saved function opens History filtered to that function.
  • Save as template: turns the current code into a reusable template. Keep it private to your store, or publish it so other stores can use it. Never publish code that contains business logic or credentials you want to keep private.
  • Delete: permanently removes the function. Workflows using it will stop working. Run history is kept. You are asked to confirm first.

Common errors

Message What it means
Function must export a default async function Your code has no export default. Add export default async function (input, ctx) { ... }
Compile error A syntax error in your code. The message points at the line
TIMEOUT The run exceeded the timeout on the Settings tab. Usually a slow external API or an endless loop
No function configured for this action The Flow action has no function selected. Open the action in Flow and pick one
Function "..." is disabled The function is turned off on the Settings tab, or was auto-disabled because a permission it needs was revoked

Next steps

  • Set up the Run Function action in Shopify Flow to connect this function to a workflow.
  • Passing variables into your function to send real Flow data into your code.
  • Creating and using secrets to keep API keys out of your code.