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. See Permissions and store data access.

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. See Using templates.

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. See Passing variables into your function.

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. Full detail in Permissions and store data access.

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. To keep the API key out of your code, store it as a secret - see Creating and using secrets.

Editing, deleting, and reusing

  • View run history: the primary action on a saved function opens History filtered to that function. See Run history and troubleshooting.
  • Save as template: turns the current code into a reusable template, kept private to your store (public sharing is coming soon). See Using templates. Remember that function code is readable by anyone with access to the app, so keep credentials in secrets rather than in code.
  • Delete: permanently removes the function. Workflows using it will stop working. Run history is kept. You are asked to confirm first.

Common errors

Expand a message to see 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.

A 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