# Functions

Source: https://developers.swell.is/apps/functions

Functions in Swell are powered by Cloudflare Workers and executed at the edge for optimal performance. Functions have no cold boots and do not require extra time to start up before executing your code. Since they run on the edge, they are optimal both for server-side webhooks and for serving personalized content directly to end-users.

You can create a new function by adding a JavaScript or TypeScript file in the app's `functions/` folder, or by using the following CLI command:

```txt
swell create function
```

→ See the [CLI reference](https://developers.swell.is/apps/cli) for more details and options.

### Runtime

Functions run with the [Cloudflare Worker runtime](https://developers.cloudflare.com/workers/runtime-apis), a high-performance environment designed for efficient and secure server-side execution. It offers a subset of Web APIs, including fetch, Request, and Response, optimized for minimal latency and quick execution. The runtime is not a complete Node.js environment and does not support certain Node.js APIs, such as reading or writing to the filesystem.

### App webhooks

The platform can trigger functions in response to various platform events, providing a powerful way to implement App-specific logic within Swell. When an event occurs, the corresponding app webhook is triggered, executing the associated Function. This allows developers to handle a wide range of use cases, such as sending custom notifications, processing data, or integrating with third-party services.

### Limitations

Due to their nature, functions don't have access to all Node.js and Browser APIs. Developers need to use ES modules and avoid libraries that rely on dynamic code execution or unsupported Node.js APIs. Function restrictions include limits on initial response time, memory usage, code size, and environment variable sizes. Additionally, request properties such as URL length, request body length, and request headers are subject to constraints. As the Workers runtime evolves, it is expected that more Node.js APIs and functionality will become available, further enhancing the capabilities of app functions.

Functions must respond within 10 seconds by default. The `timeout` configuration accepts values from 1,000 to 20,000 milliseconds, and values above 10,000 require the `functions.long_timeout` feature to be enabled for the store. For work that outlasts the response window, respond immediately and continue processing in the background with `req.context.waitUntil()`, or split large jobs into smaller batches triggered by cron schedules or workflows.

### Usage and pricing

Pricing for functions is based on the number of executions. There is no limit on the total number of function executions an app can make, but charges apply for executions above the store’s plan tier.

### Local development

During local development, you'll be able to run app functions on your machine and quickly test changes using the following CLI command:

```txt
swell app dev
```

This will perform several functions:

- Start a local proxy using localtunnel.me.
- Add the local proxy URL to your account session, which informs Swell that your app dev server is running and should receive events instead of a deployed version.
- Note: This command will also start a development server for an app [frontend](https://developers.swell.is/apps/frontend), if one exists.

*You should only run* *`app dev`* *on one app at a time, otherwise your account session will only remember the last app that was started.*

Events triggered by your Swell store will be routed to your local server, allowing you to observe and iterate on app logic.

## Trigger types

There are a variety of triggers that can be utilized with functions that support a wide variety of applications.

#### Model event triggers

Functions can be triggered by events from your data models. To connect a function to a model event, specify the `model.events` property with one or more event names. In the following example, we’re using the `conditions` property to narrow function invocations where the event `stock_level` property is being changed. Note the function is called asynchronously.

**functions/product-updates.ts**

```typescript
export const config: SwellConfig = {
	description: 'Update product records when stock levels change',
	model: {
		events: ['product.created', 'product.updated', 'product.deleted'],
		// conditions are checked against the record before calling the function
		conditions: {
			stock_level: { $exists: true },
		},
		// optional sequence number to prioritize among other function calls
		// a lower number means higher priority
		sequence: 1
	},
};

export default function (req: SwellRequest) {
	const { swell, data, session, store } = req;
	...
}
```

For a full list of standard model events, see the [event types reference](https://developers.swell.is/backend-api/events/event-types).

#### Model schedule triggers

You can run scheduled tasks at a later date in response to record data. Swell uses this capability natively to schedule invoices, payments, cart abandonment, and many other platform tasks. Use the `model.schedule` property to indicate the future date for a scheduled task.

**functions/scheduled-payment.ts**

```typescript
export const config: SwellConfig = {
	description: 'Capture a custom scheduled payment',
	model: {
		events: ['payment.created', 'payment.updated'],
		conditions: {
			// in this example, 'date_capture_scheduled' is an App-defined field
			date_capture_scheduled: { $exists: true },
		},
		schedule: {
			// formula evaluated in context of the record
			// this will cause your function to be called on the date in the field
			// updates any time the field 'date_capture_scheduled' is set or changed
			formula: 'date_capture_scheduled',
		}
	},
};

export default function (req: SwellRequest) {
	const { swell, data, session, store } = req;
	...
}
```

#### Cron schedule triggers

Instead of using a record field to indicate the date to execute your function, you can use a simple Cron schedule. Functions executed by a Cron schedule do not have any particular record context.

**functions/updare-product-popularity-daily.ts**

```typescript
export const config: SwellConfig = {
	description: 'Update product popularity on a daily basis',
	cron: {
		schedule: '0 0 * * *' // call this function every day at midnight
	}
};

export default function (req: SwellRequest) {
	const { swell, data, session, store } = req;
	...
}
```

#### HTTP triggers

Functions can also be exposed as custom API endpoints by exporting one or more route handler methods, `get`, `put`, `post`, or `delete`. These functions are deployed to an edge network for optimal end-user performance.

By default, you must pass an API secret key to invoke the function from an external server, or set the `route.public` property to indicate the function should be publicly accessible without a key.

**functions/analyze-subscriptions.ts**

```typescript
export const config: SwellConfig = {
  description: 'Retrieve subscription analytics',
  route: {
    methods: ['get'], // any of get, put, post, or delete
    headers: { ... }, // specify static headers, such as an authentication secret
    cache: {
      timeout: 5000, // cache the output for 5 seconds
    },
    public: false // true to allow requests without a secret key
  }
};

export async function get (req: SwellRequest) {
	const { swell } = req;

	return await swell.get('/subscriptions', {
		$aggregate: [
			// ... aggregation pipeline
		],
	});
}
```

In this example, a GET request to `https://<secret_key>@<store_id>.swell.store/functions/<app-id>/analyze-subscriptions` will trigger the `analyze-subscriptions.js` function and return the result.

#### Calling functions from external services

Route functions are also served through the store gateway at `https://<store-id>.swell.store/functions/<app-id>/<function-name>`. External requests to this URL must include an app API key in the `Authorization` header. The key identifies both the app and the target environment — a test key routes the request to the Test environment, and a live key to the Live environment. Without a valid key, the gateway cannot resolve the app and responds with a 404 error.

Note that query string parameters sent through the gateway arrive in `req.data` when the request has no body. To handle both direct and gateway invocations consistently, read parameters from both `req.query` and `req.data`.

#### Action triggers

Functions can run from buttons and menu items in the Swell dashboard. Set `action: true` to let the app's [actions](https://developers.swell.is/apps/actions) run the function when an admin user clicks them. Action functions only run from the dashboard, and cannot be called through the API or the function gateway.

**functions/send-product.ts**

```typescript
export const config: SwellConfig = {
	description: 'Send a product to the warehouse',
	action: true,
};

export default async function (req: SwellRequest) {
	const { swell, data } = req;
	const { record_id } = data.$action;
	...
	return { message: 'Sent to the warehouse' };
}
```

The function receives the action context in `req.data.$action`, and can return `{ message }` to show a message to the admin user. See [Actions](https://developers.swell.is/apps/actions) for where actions appear and how to declare them.

> **Warning:** A function must only specify one of the following trigger types: `model`, `route`, `cron`, or `action`, but not more than one.

## Reference

The following table outlines functions available for use within Swell App development.

| Attribute | Description |
| --- | --- |
| description | A brief description of the function. |
| model | Configure this function to be called by model events. |
| model.events | An array of model events, for example [”product.updated”]. |
| model.conditions | Optional object with operators to filter function invocation based on specific properties in a request. |
| model.sequence | Optional number to indicate priority among other App triggers for the same model events. A lower number is considered higher priority. |
| model.schedule | Optional schedule. |
| model.schedule.formula | A specific field or formula to derive the scheduled task date from. |
| route | External route configuration. |
| route.public | Optionally indicate this function should be accessible without an API key. |
| route.methods | Optional array of methods to allow the function to be called by. |
| route.cache | Configure the cache behavior for get requests to the function. |
| route.cache.timeout | Number of milliseconds to cache the result of a get request. Defaults to 5000. |
| cron | Configure this function to be called by a Cron schedule. |
| cron.schedule | A string describing a Cron schedule to invoke this function independently of an model event or external request. |
| action | Set to true to run this function from app actions in the Swell dashboard. |

## Architecture

Function architecture is meant to simplify your app code by deeply integrating with Swell and leveraging the Cloudflare Worker platform to the greatest extent possible.

![apps functions diagram](https://cdn.sanity.io/images/ecf33d4l/production/50dd98b9224fb2c45bee44688695b84cbda019f5-4608x7648.png)

### Workflows

Workflows are durable, multi-step background functions that persist across restarts and support long-duration pauses. A workflow breaks its logic into retriable steps using `step.do()` and can pause with `step.sleep()` without consuming execution time. Use them for multi-step background work that needs to survive failure between steps — syncing to an external system, extended fulfillment operations, scheduled follow-ups, imports, and batch jobs that exceed normal function timeouts.

#### Declaring a workflow

A workflow is a function file whose config sets `kind: 'workflow'`. Besides `description`, the only key a workflow config accepts is `action`. Workflows can't declare `route`, `model`, `cron`, `extension`, or `timeout`, so a route, a model event, or a cron schedule can't start them. They're started from another function, or by an app action.

The config must be a static object literal — spreads, computed keys, and imported constants are rejected. The file must have exactly one default export, and it must be a class declared in the same file with an instance method named `run`. The class is constructed with no arguments.

**functions/sync-order.ts**

```typescript
export const config = {
	kind: 'workflow',
	description: 'Sync an order to the warehouse',
};

export default class SyncOrder {
	async run(req: SwellWorkflowRequest, step: SwellWorkflowStep) {
		const order = await step.do('load order', async () => {
			return req.swell.get(`/orders/${req.data.order_id}`);
		});

		await step.sleep('cool off', '30 seconds');

		await step.do('mark synced', async () => {
			return req.swell.put(`/orders/${order.id}`, {
				$app: { [req.appId]: { synced: true } },
			});
		});

		return { ok: true };
	}
}
```

#### Steps

Each `step.do(name, fn)` executes once and its result is durably recorded, so a step that already completed isn't re-run when the workflow retries. Keep side effects inside steps and return values that survive serialization; anything you do between steps runs again on every retry.

Use `step.sleep(name, duration)` to pause for a relative duration, either a string like `'30 seconds'` or a number of milliseconds, and `step.sleepUntil(name, date)` to pause until a specific time, given as a `Date`, an ISO string, or a timestamp. A sleeping workflow isn't running, so long pauses don't consume execution time.

An optional second argument tunes retry behavior for a single step. Within `retries`, `limit` and `delay` are both required, and `backoff` accepts `'constant'`, `'linear'`, or `'exponential'`. When an API call inside a step fails with a non-retryable error, the step stops retrying immediately rather than exhausting its limit.

**Tune retries for a step**

```typescript
await step.do(
	'call warehouse',
	{
		retries: { limit: 3, delay: '10 seconds', backoff: 'exponential' },
		timeout: '30 seconds',
	},
	async () => {
		// ...
	},
);
```

#### Starting a workflow

Start a workflow from any ordinary app function with `req.swell.workflows.create()`, passing the workflow's name and its parameters. It returns the new instance's `id` (prefixed `wf_inst_`) and a `status` of `active`. The call returns as soon as the instance is created — it doesn't wait for the workflow to finish. The workflow must already be deployed, or the call fails with `workflow_not_deployed`.

Parameters must be JSON-safe and no larger than 128 KB once serialized. Only null, finite numbers, strings, booleans, plain arrays, and plain objects are accepted — a `Date`, a class instance, or `undefined` is rejected rather than coerced, with the code `workflow_params_unserializable`; oversized params fail with `workflow_params_too_large`. Pass identifiers rather than records, and re-fetch the data you need inside the workflow.

**Start a workflow from a function**

```typescript
const run = await req.swell.workflows.create('sync-order', {
	order_id: id,
});

// { id: 'wf_inst_...', status: 'active' }
```

An [app action](https://developers.swell.is/apps/actions) can also start a workflow that sets `action: true` in its config. The run receives the action's dialog values and `$action` in `req.data`.

#### Inside a workflow

A workflow request is narrower than an ordinary function request. You get `req.data` (the parameters it was started with), `req.swell.get/post/put/delete`, and `req.swell.settings()`, which takes no argument and always returns your own app's settings. Instance metadata lives on `req.workflow` as `workflow_id`, `workflow_name`, `workflow_instance_id`, `trigger`, and `request_id` — the last being the id of the function request that started it, which is how workflow logs correlate back to the caller.

`req.workflow.trigger` is `function` when a function started the run with `workflows.create()`, and `action` when an app action started it.

Some things available elsewhere are not available here. There's no `req.appValues()` helper — write app-namespaced fields explicitly as `{ $app: { [req.appId]: { ... } } }`, as in the example above. A workflow also can't start another workflow, and requests to meta endpoints are blocked: any path whose first segment begins with `:` is rejected with `workflow_operation_blocked`, with a single exception for `/:batch`, whose child operations are checked under the same rules.

A workflow can read and update its own app's settings with `req.swell.get` and `req.swell.put` on `/settings/${req.appId}`. These requests cannot include a query string, and requests for the store's settings or another app's settings are rejected with `workflow_operation_blocked`.

#### Deploying and inspecting

Workflows are deploy-only. They don't run under `swell app dev` — your other functions still do, but a workflow only runs after `swell app push`. The iteration loop is to push, trigger the function that starts the workflow, then inspect the result. A function running locally can still call `workflows.create()`, as long as the workflow it names has already been pushed.

App actions that name a workflow also start its pushed version under `swell app dev`.

**Deploy and inspect a workflow**

```txt
# Scaffold a workflow function
swell create function sync-order --type workflow

# Validate the file before pushing
swell schema function functions/sync-order.ts

# Workflows only run once deployed
swell app push

# Deployed workflows, and their recent run summary
swell inspect workflows --app=.

# Recent instances, newest first (default limit 10)
swell inspect workflow-runs --app=. --status failed

# A single run in detail
swell inspect workflow-runs wf_inst_... --app=. --json

# Step-level logs, scoped to your app
swell logs --type workflow --app my-app
```

A run can end as `completed`, `terminated`, or `failed`, and a failed run records whether it failed while being created or while running. There's no retry command: to re-run a failed instance you start a new one with the same parameters, so it's worth making workflows idempotent on their params. There is no dedicated termination command either — `swell inspect workflow-runs wf_inst_...` prints a ready-to-run `swell api post` command for an active run. Note that run parameters are redacted from CLI output, so if you need to see what a run received, log it yourself inside a step.

Runs also appear in the Workflows tab of the app's page in the dashboard, with their steps and errors. Users with manage access to Apps can cancel an active run there.

#### Limits

- An app can define at most 25 workflows.
- Instance creation is rate limited to 60 per minute per store, environment, and app. Exceeding it returns `workflow_create_rate_limited`, which is safe to retry.
- Parameters are capped at 128 KB of serialized JSON.
- Workflow logs are retained for 30 days.

Limits on step retries, total workflow duration, and maximum sleep length come from the underlying durable execution runtime rather than from Swell.
