---
title: "How to create a function in Workflow Functions"
description: "Step by step guide to creating, writing, testing, and saving a function in Workflow Functions for Shopify Flow."
canonical: "https://crm-tool.k8s-gcp-dev.eu.codecreationlabs.cloud/h/flow-functions/how-to-create-a-function"
---

# 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](https://crm-tool.k8s-gcp-dev.eu.codecreationlabs.cloud/h/flow-functions/permissions-and-store-data-access.md).

## 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](https://crm-tool.k8s-gcp-dev.eu.codecreationlabs.cloud/h/flow-functions/using-templates.md).

## 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)`:

```js
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:

```js
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.

```json
{
  "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](https://crm-tool.k8s-gcp-dev.eu.codecreationlabs.cloud/h/flow-functions/passing-variables-into-your-function.md).

## 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.

```js
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](https://crm-tool.k8s-gcp-dev.eu.codecreationlabs.cloud/h/flow-functions/permissions-and-store-data-access.md).

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:

```js
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](https://crm-tool.k8s-gcp-dev.eu.codecreationlabs.cloud/h/flow-functions/creating-and-using-secrets.md).

## 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](https://crm-tool.k8s-gcp-dev.eu.codecreationlabs.cloud/h/flow-functions/run-history-and-troubleshooting.md).
- **Save as template**: turns the current code into a reusable template, kept **private to your store** (public sharing is coming soon). See [Using templates](https://crm-tool.k8s-gcp-dev.eu.codecreationlabs.cloud/h/flow-functions/using-templates.md). 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:

```js
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

- [Set up the Run Function action in Shopify Flow](https://crm-tool.k8s-gcp-dev.eu.codecreationlabs.cloud/h/flow-functions/set-up-the-run-function-action-in-shopify-flow.md) - connect this function to a workflow.
- [Passing variables into your function](https://crm-tool.k8s-gcp-dev.eu.codecreationlabs.cloud/h/flow-functions/passing-variables-into-your-function.md) - send real Flow data into your code.
- [Creating and using secrets](https://crm-tool.k8s-gcp-dev.eu.codecreationlabs.cloud/h/flow-functions/creating-and-using-secrets.md) - keep API keys out of your code.
- [Run history and troubleshooting](https://crm-tool.k8s-gcp-dev.eu.codecreationlabs.cloud/h/flow-functions/run-history-and-troubleshooting.md) - inspect runs and fix failures.
