Editorial Workflows

Cookbook: Handle a deleted subject document

Apply an application-owned lifecycle policy when content watched by an Editorial Workflow is deleted.

Prerelease

Editorial Workflows stores document references on the workflow instance. Deleting the referenced content does not automatically delete, complete, or abort that instance. This is intentional: retaining the run for investigation may be correct for one application, while immediately aborting it may be correct for another.

This recipe uses a document-delete Sanity Function to find affected in-flight instances and abort them through the engine. The instance documents remain as audit records.

A document-delete event wakes the Delete Function. It asks the engine for affected instances and aborts each one through an engine verb, preserving the terminal instance and its audit history in the Content Lake.

Loading...

Choose the policy first

Choose one policy for the application:

  • Retain the affected workflow instances. Make no workflow mutation. Surfaces should explain that the referenced document is unavailable.
  • Abort each affected in-flight instance with abortInstance. This cancels pending effects, propagates termination to parent workflows, records the abort in history, and reconciles the instance’s guards.

Do not delete or raw-patch workflow instance documents. Every instance write belongs to a sanctioned engine verb.

Find instances watching the deleted document

instancesForDocument is the reverse lookup for an instance’s watch set. It accepts a resource-qualified global document reference, so the same bare document ID in two datasets cannot match accidentally. It returns in-flight instances only.

The Function receives the deleted document in event.data. Construct its GDR from the dataset where the Function runs:

// functions/handle-deleted-subject/index.ts
import {documentEventHandler} from '@sanity/functions'
import {gdrFromResource} from '@sanity/workflow-engine'
import {engine} from '../../engine'

interface DeletedDocument {
  _id: string
}

export const handler = documentEventHandler<DeletedDocument>(async ({context, event}) => {
  const projectId = context.clientOptions.projectId
  const dataset = context.clientOptions.dataset
  if (!projectId || !dataset) throw new Error('The Function event has no project or dataset')

  const contentResource = {
    type: 'dataset',
    id: `${projectId}.${dataset}`,
  } as const
  const document = gdrFromResource(contentResource, event.data._id)

  const instances = await engine.instancesForDocument({document})

  for (const instance of instances) {
    await engine.abortInstance({
      instanceId: instance._id,
      idempotencyKey: `subject-delete:${document}`,
      reason: `Referenced document deleted: ${document}`,
    })
  }
})

Configure the imported engine with the workflow resource that owns the instances and with a resource client for the content dataset. The Function must run with an execution identity allowed to update those instances and reconcile guards in every involved resource.

Trigger only on relevant deletions

Declare a document Function for delete events and narrow its filter to the document types that may act as workflow subjects:

// sanity.blueprint.ts
import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints'

export default defineBlueprint({
  resources: [
    defineDocumentFunction({
      name: 'handle-deleted-subject',
      event: {
        on: ['delete'],
        filter: '_type in ["article", "post"]',
      },
    }),
  ],
})

Follow the authoritative Functions documentation to create and deploy the Function, configure its client, and operate its timeouts, logs, and retries.

Retries and partial failures

Document Function delivery and service calls may be retried. The example supplies the same idempotencyKey for the same deletion on each instance. A successful abort is terminal; a retry either replays safely or no longer finds that instance in the in-flight lookup.

The loop is deliberately sequential. If one abort fails after earlier instances succeeded, the Function fails visibly. On retry, instancesForDocument returns the still in-flight remainder. Do not swallow an individual failure and report the Function as successful.

For cross-dataset subjects, run the delete trigger in the subject’s dataset and keep the GDR resource-qualified as shown. Configure the engine’s resource clients and service permissions for the workflow resource and every content resource whose guards may need reconciliation.

Audit and retention

abortInstance hard-stops the run in place. It stamps abortedAt and completedAt, cancels pending effects into history, records the reason, and propagates to ancestors. The instance remains stored as the audit record; this recipe does not reclaim workflow storage.

A product-wide default for subject deletion may be introduced later. Until then, deletion policy belongs to the application and should be explicit.

Next steps

  • Fields for document-reference field behavior.
  • Engine for instancesForDocument and abortInstance.
  • Cookbook for other runtime patterns.

Visiting agent?

Was this page helpful?