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-reviewdeployed, and asanity.workflow.tsthat exports thedevdeployment. - An API token with write access to the dataset, such as an Editor token. Authentication and tokens explains how to create one.
- The
_idof the publishedarticlefrom 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 tsxpnpm add @sanity/client
pnpm add --save-dev tsxyarn add @sanity/client
yarn add --dev tsxbun add @sanity/client
bun add --dev tsxCreate 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_IDThe 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 reviewCheck the result from the CLI. It reads the same partition, so it shows the instance your script started:
npx sanity-workflows show INSTANCE_IDpnpm dlx sanity-workflows show INSTANCE_IDyarn dlx sanity-workflows show INSTANCE_IDbunx sanity-workflows show INSTANCE_IDThe 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
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
- Explain why an action is blocked
- Read what happened to an instance
- Run the engine in production with Sanity Functions
- See every engine method in the engine reference
Was this page helpful?
Feedback submitted, thanks for helping us improve the docs. Have you tried these alternatives?