---
title: "How to create a function in Workflow Functions"
description: "Create, write, test and save a function: the code editor, the test bench, settings, version history and the Run it tab."
canonical: "https://docs.workflow-functions.app/how-to-create-a-function"
---

# How to create a function

A function is a piece of JavaScript or TypeScript that runs from Shopify Flow, from your own systems over the API, or on a schedule. This page walks through creating one, testing it and saving it.

## Step 1: Create the function

Open **Functions** and select **Create function**. You land in the editor with starter code filled in.

Prefer to start from something that works? Select **Browse templates** instead - a template becomes your own function in one click. See [Using templates](https://docs.workflow-functions.app/using-templates.md). With an AI provider connected, **Create function** can also draft the whole function from a description. See [Generate functions with AI](https://docs.workflow-functions.app/generate-with-ai.md).

![The Functions list with name, status, runs in the last 30 days, success rate and last run per function](https://cdn-dev.eu.codecreationlabs.cloud/crm-tool/uploads/SjJ78gKLnLlocopCui3Cs62dJbGZ7F1n/90fdc3ceee02bbc3.png)
*The Functions list shows runs, success rate and the last run of every function over 30 days. The icon on each row duplicates the function.*

## Step 2: Write the code

The editor has three tabs: **Code and test**, **Settings** and **Run it**. On **Code and test**, the code sits on the left and a test bench on the right.

- **Name** is what you pick in Shopify Flow and what the API shows, so make it describe the job, for example "Tag VIP customers".
- **Language** is JavaScript or TypeScript. TypeScript types are removed before the run, so both behave the same.

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

```js
export default async function (input, ctx) {
  ctx.log("running with", input)
  return { ok: true, received: input }
}
```

The editor completes `ctx.`, `ctx.shopify.`, `secrets.` (the names of your secrets) and `input.` (the keys of your test variables) as you type.

### What you get

| Item | What it does |
| --- | --- |
| `input` | The JSON the function is called with |
| `ctx.shop` | Your store domain, for example `my-store.myshopify.com` |
| `ctx.log(...)` | Writes a line to the run's logs, like `console.log` |
| `ctx.fetch(url, options)` | HTTPS request to another system. Returns `{ status, ok, headers, text(), json() }` |
| `ctx.shopify.graphql(query, variables)` | Shopify Admin GraphQL, when *Needs Shopify data* is on |
| `ctx.storage` | `get`, `set`, `delete` and `list` for data kept between runs. See [Storage for your functions](https://docs.workflow-functions.app/storage.md) |
| `secrets.NAME` | A value saved on the Secrets page |

Whatever you return becomes the output. Return a plain object, a string, a number, or nothing.

![The Insert snippet menu of the code editor with ready-made snippets](https://cdn-dev.eu.codecreationlabs.cloud/crm-tool/uploads/SjJ78gKLnLlocopCui3Cs62dJbGZ7F1n/0310414bbfd5712f.png)
*Insert snippet adds ready-made code at the cursor: calling an API with a secret, remembering a position between runs, handling an event only once, a dry-run switch and input validation.*

## Step 3: Test it

The test bench runs the code **as it is in the editor, saved or not**, so you can try a change before it goes live.

1. Put the input you want to test with into **Variables (JSON), passed to your code as input**.
2. Select **Run test**.
3. The **Result** card shows the status, how long the run took against the 5-second limit, the output (as a tree or as JSON), the fields Flow will offer from it, and your logs.

Test runs are free: they never count toward your plan. They are kept in **History** with the trigger **Manual**.

![The function editor: code on the left, the test bench with variables and a successful result on the right](https://cdn-dev.eu.codecreationlabs.cloud/crm-tool/uploads/SjJ78gKLnLlocopCui3Cs62dJbGZ7F1n/3efb315b114f9006.png)
*Code and test: the code on the left, the test bench on the right. This test ran against the saved variables and returned in 129 ms.*

The variables are saved with the function. They are only test data for Flow, but a **scheduled** run receives them as its input - that is where you set a location or a threshold for a schedule.

## Step 4: Settings

| Setting | Default | Notes |
| --- | --- | --- |
| Description | Empty | Shown in the function list and in the Flow action's function picker |
| Enabled | On | A disabled function cannot run from Flow, the API or a schedule |
| Needs Shopify data | Off | Gives the code `ctx.shopify.graphql(...)`. Grant the matching permissions on the Permissions page |
| Run on a schedule | Off | Runs the function on a crontab (UTC) without a Flow trigger. See [Run a function on a schedule](https://docs.workflow-functions.app/scheduled-functions.md) |

**Limits are fixed by the platform:** every run gets 5 seconds and 128 MB, and can make up to 20 outbound requests.

![The Settings tab of a function with Details, Behaviour, Schedule and Limits](https://cdn-dev.eu.codecreationlabs.cloud/crm-tool/uploads/SjJ78gKLnLlocopCui3Cs62dJbGZ7F1n/8f3748a3a09f63ef.png)
*The Settings tab. A function that needs store data has Needs Shopify data on; a schedule is optional.*

## Step 5: Save

Select **Save** in the save bar at the top. Saved code is live right away: Flow, the API and the schedule all run the saved version.

## Run it from somewhere

The **Run it** tab shows every way to start the function:

- **From your ERP or any other system (API)** - the function's endpoint and a ready `curl` call. See [Connect your ERP or warehouse system](https://docs.workflow-functions.app/erp-integration.md).
- **From Shopify Flow** - the steps, and the Input (JSON) to paste into the Run Function action, built from your test variables. See [Set up the Run Function action in Shopify Flow](https://docs.workflow-functions.app/set-up-the-run-function-action-in-shopify-flow.md).
- **On a schedule** - a shortcut to the schedule setting.

![The Run it tab with the function's API endpoint, an example curl request and the Flow setup steps](https://cdn-dev.eu.codecreationlabs.cloud/crm-tool/uploads/SjJ78gKLnLlocopCui3Cs62dJbGZ7F1n/be5b0d25fafb5b79.png)
*Run it: the API endpoint with a ready curl call, and the Input (JSON) for the Flow action.*

## Version history

Every time the code changes, the previous version is kept - the last 25 versions of each function, whether the change came from the editor, a template, the API or GitHub. Pick one in **Version history** above the code to load it into the editor as an unsaved change; test it, then save to restore it.

## Using store 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.
3. Call `ctx.shopify.graphql(query, variables)`.

```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 })

  return { inventory: data.product.totalInventory }
}
```

Your store's access token never enters the sandbox, and Shopify enforces exactly the permissions you granted. Full detail in [Permissions and store data access](https://docs.workflow-functions.app/permissions-and-store-data-access.md).

## Calling an external API

```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, and a run can make up to 20 requests. Keep the key in a secret - see [Creating and using secrets](https://docs.workflow-functions.app/creating-and-using-secrets.md).

## Duplicate, save as template, delete

- **Duplicate** (the icon on the Functions list) copies a function, enabled and without its schedule.
- **Save as template** keeps the code as a template, private to your store. See [Using templates](https://docs.workflow-functions.app/using-templates.md).
- **Delete** removes the function after you confirm. Workflows that use it stop working; its run history is kept.

## 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 **5-second** limit. Usually a slow external API or an endless loop - keep external calls quick and offload long-running work to your own system.

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

- [Example functions](https://docs.workflow-functions.app/examples.md) - copy-paste example functions.
- [Set up the Run Function action in Shopify Flow](https://docs.workflow-functions.app/set-up-the-run-function-action-in-shopify-flow.md) - connect this function to a workflow.
- [Connect your ERP or warehouse system](https://docs.workflow-functions.app/erp-integration.md) - call it from your ERP.
- [Run history and troubleshooting](https://docs.workflow-functions.app/run-history-and-troubleshooting.md) - inspect runs and fix failures.
