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, 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: 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 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 install @sanity/client
npm install --save-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:

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

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

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:

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:

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

Instance not found

Next steps

Was this page helpful?

sanity.ioAll Systems Operational© Sanity 2026
Change site theme