> For AI agents: the complete Sanity documentation index is available at [https://www.sanity.io/docs/llms.txt](https://www.sanity.io/docs/llms.txt).

# Run the engine from your own code

Create a Workflows engine from your deployment config, then start and advance an instance from a TypeScript script.

In the [quick start](https://www.sanity.io/docs/workflows/getting-started), the Workflows CLI started and advanced an instance for you. The CLI is a wrapper around the engine, a TypeScript library you can call from your own code. Servers, workers, and Functions use the engine the same way.

This guide creates an engine from the quick start’s `dev` deployment, then starts an `article-review` instance and moves it to `review` from a script. Later guides import the `engine` module you create here.

You need:

- The finished [quick start](https://www.sanity.io/docs/workflows/getting-started): `article-review` deployed, and a `sanity.workflow.ts` that exports the `dev` deployment.
- An API token with write access to the dataset, such as an Editor token. [Authentication and tokens](https://www.sanity.io/docs/content-lake/http-auth) explains how to create one.
- The `_id` of the published `article` from the quick start.

## Install the client and a TypeScript runner

The engine needs a Sanity client to read and write workflow documents. `tsx` runs the TypeScript script without a build step.

**npm**

```shell
npm install @sanity/client
npm install --save-dev tsx
```

**pnpm**

```shell
pnpm add @sanity/client
pnpm add --save-dev tsx
```

**yarn**

```shell
yarn add @sanity/client
yarn add --dev tsx
```

**bun**

```shell
bun add @sanity/client
bun add --dev tsx
```

## Create the engine

Create `scripts/engine.ts`. It builds the client and the engine from the `dev` deployment, so the script and the CLI use the same dataset and partition:

**scripts/engine.ts**

```typescript
import {createClient} from '@sanity/client'
import {clientConfigFromResource, createEngine, ENGINE_API_VERSION} from '@sanity/workflow-engine'

import {dev} from '../sanity.workflow'

const token = process.env.SANITY_AUTH_TOKEN
if (!token) {
  throw new Error('Set SANITY_AUTH_TOKEN to a token with write access to the dataset')
}

export const client = createClient({
  ...clientConfigFromResource(dev.workflowResource),
  apiVersion: ENGINE_API_VERSION,
  token,
  useCdn: false,
})

export const engine = createEngine({
  client,
  workflowResource: dev.workflowResource,
  partition: dev.partition,
})
```

`clientConfigFromResource` turns the deployment’s `workflowResource` into the client’s `projectId` and `dataset`. `ENGINE_API_VERSION` is the API version the CLI uses. Set `useCdn: false` so every read returns the latest instance state.

`createEngine` takes the client, the resource that holds workflow documents, and the partition. The engine only sees instances in its own partition, so `partition` must match the deployment the instances were started under. [Deployments and partitions](https://www.sanity.io/docs/workflows/deployments-and-resources) covers running more than one.

The engine acts as the token’s identity. Every action it fires is recorded against that user or robot, and conditions such as `$actor` evaluate against it. [Actors, tokens, and what’s actually enforced](https://www.sanity.io/docs/workflows/actors-and-enforcement) explains what that identity can and can’t do.

## Start and advance an instance

Create `scripts/review-article.ts`. It starts an instance against the article you pass in, lists the actions the token can fire, and fires `write/submit`:

**scripts/review-article.ts**

```typescript
import {gdrRef} from '@sanity/workflow-engine'

import {dev} from '../sanity.workflow'
import {engine} from './engine'

async function main() {
  const documentId = process.argv[2]
  if (!documentId) {
    throw new Error('Pass the article _id: npx tsx scripts/review-article.ts DOCUMENT_ID')
  }

  const {instance} = await engine.startInstance({
    definition: 'article-review',
    initialFields: [
      {
        type: 'subject',
        name: 'subject',
        value: gdrRef({res: dev.workflowResource, documentId, type: 'article'}),
      },
    ],
  })
  console.log(`Started ${instance._id} at ${instance.currentStage}`)

  const evaluation = await engine.evaluate({instanceId: instance._id})
  for (const {activity, actions} of evaluation.currentStage.activities) {
    for (const {action, allowed} of actions) {
      console.log(`${activity.name}/${action.name}: ${allowed ? 'allowed' : 'not allowed'}`)
    }
  }

  const result = await engine.fireAction({
    instanceId: instance._id,
    activity: 'write',
    action: 'submit',
  })
  console.log(`Now at ${result.instance.currentStage}`)
}

main().catch((error) => {
  console.error(error)
  process.exit(1)
})
```

`gdrRef` builds the same [global document reference](https://www.sanity.io/docs/workflows/global-document-references) you typed into the quick start’s `start` command, from the deployment’s resource and the document `_id`.

`evaluate` reads the instance from the token’s point of view. Each action reports `allowed`, and a `disabledReason` when it isn’t. It doesn’t change the instance.

`fireAction` resolves the activity and runs the cascade, just like `npx sanity-workflows fire-action`. It returns the settled `instance`, the number of transitions it `cascaded`, and whether anything `changed`.

## Run the script

Pass the token as an environment variable and the article `_id` as an argument:

**CLI**

```sh
SANITY_AUTH_TOKEN=YOUR_TOKEN npx tsx scripts/review-article.ts DOCUMENT_ID
```

The script prints the new instance, the actions available in `drafting`, and the stage after the cascade:

**Output**

```text
Started dev.wf-instance.a1b2c3d4e5f6 at drafting
write/submit: allowed
Now at review
```

Check the result from the CLI. It reads the same partition, so it shows the instance your script started:

**npm**

```shell
npx sanity-workflows show INSTANCE_ID
```

**pnpm**

```shell
pnpm dlx sanity-workflows show INSTANCE_ID
```

**yarn**

```shell
yarn dlx sanity-workflows show INSTANCE_ID
```

**bun**

```shell
bunx sanity-workflows show INSTANCE_ID
```

The instance is in `review`, with `write` done. Fire `sign-off/approve` from the CLI or add it to the script to finish it.

> [!WARNING]
> Instance not found
> If a call fails with `InstanceNotFoundError` and `partition mismatch`, the engine’s `partition` doesn’t match the instance. Build the engine from the same deployment that started the instance.

## Next steps

- [Run outside work from an instance with effects](https://www.sanity.io/docs/workflows/effects-and-runtimes)
- [Explain why an action is blocked](https://www.sanity.io/docs/workflows/evaluation-insights)
- [Read what happened to an instance](https://www.sanity.io/docs/workflows/history-and-audit-trail)
- [Run the engine in production with Sanity Functions](https://www.sanity.io/docs/workflows/sanity-functions)
- [See every engine method in the engine reference](https://www.sanity.io/docs/workflows/engine)

