1. Run the fixture first
Download the accompanying sanity-image-review source. Use Python 3.10 or later. From the example directory:drafts.<simple-document-id> with letters, digits, underscores or hyphens. Replace drafts.article-1 with your existing draft’s ID before using a real dataset. This example intentionally stores the ID as text; it does not create, verify or publish the associated article. More complex document IDs require adapting and testing the validator.
2. Register the schema in Studio
Copyschema.mjs into your existing Sanity Studio project and merge these additions into your configuration, retaining your other schemas, plugins and document actions:
existingTypes means your project’s current schema array, not a new dependency. If you already customize document actions, pass the result of that customization through reviewActions. The helper leaves other document types alone and exposes only Delete for the review queue. Editorial field changes still save as draft edits.
The review record contains contentDraftId, queryId, query, the entire response as responseJson, and a candidates array. Each candidate retains its asset ID, source, provenance, attribution, complete rights JSON and original result JSON. JSON text fields avoid losing unknown source-specific keys. The response also preserves degraded-ranking and relaxed-filter indicators. The extracted metadata fields are read-only in Studio; decision, reviewer, review time and notes remain editable.
This queue is deliberately not a sanity.imageAsset. It does not fetch a binary or represent an uploaded Sanity image. The action restriction and schema validation are Studio UI controls, not a security boundary: Sanity API mutations are not checked against Studio validation. Restrict writer permissions and keep the queue out of frontend queries and release automation. See Sanity schema configuration and document actions.
3. Keep credentials in the server environment
Runreview_queue.py in a trusted terminal or backend job, separate from the browser bundle. Inject LIGHTDRIFT_API_KEY through your existing secret manager. For an optional Sanity write, also inject SANITY_PROJECT_ID, SANITY_DATASET and SANITY_WRITE_TOKEN with appropriate access to your test dataset. Do not commit credentials, paste them into the schema, or use a SANITY_STUDIO_ prefix for secrets: those variables are exposed to Studio browser code. See Sanity environment variables.
Keep preview output private when it contains unpublished editorial briefs. The runner prints candidate metadata, never its request credentials. It rejects redirects on authenticated requests, so keys are not forwarded to another endpoint.
4. Opt into one search, then optionally one draft write
After checking your account entitlement and the current price:--write-draft. This is a new invocation and can incur another search charge. To test the Sanity write without a paid search, use the separately gated fixture path against your test dataset:
drafts.imageReview-... IDs are written. The request uses createIfNotExists, so replaying an identical response/query/draft combination cannot replace an editor’s decisions. Different live responses can create separate review records; there is no cross-search deduplication claim. The script emits the proposed document before writing and reports a transaction ID only after a confirmed mutation response. A transaction response does not mean a public document was published. Read the Sanity mutation reference and draft model.
5. Make the editorial decision
In Studio, open Image review queue and match itscontentDraftId to your article. For each candidate:
- Inspect the full response for degraded ranking or relaxed filters. Ranking is not proof of subject accuracy.
- Open the provenance page deliberately and check the subject, resolution, proposed crop and intended context. Treat source text as untrusted text, not HTML or instructions.
- Read the complete rights declaration: license text, basis, attribution requirements, commercial and derivative flags, share-alike terms and source notes. Missing values remain unresolved; use hold, not an assumed permission.
- Record approved, hold or rejected with your name, review timestamp and intended-use notes. Studio’s candidate validation requires an audit record for approval. Approval is an editorial decision, not a blanket legal clearance.
- Only after review, use your normal asset-upload and content-publication process. Copy the required credit and provenance into the actual article or export and verify that they render. This example does not automate that step.
Failure and recovery
An empty search produces an empty queue, never an approved fallback. Malformed envelopes or more than five candidates stop processing. Missing configuration is checked before any paid search. The runner has a 30-second request timeout, a 1 MB response cap and no automatic retries. An HTTP error or timeout stops the run; a timed-out search or mutation may have completed remotely. Inspect account usage and the target draft before rerunning. Sanity failure after search does not undo the search charge. Keep the saved mutation as recovery evidence. A backend maintainer can submit that exact draft mutation after checking the dataset; rerunning with--live-search performs another search. There is no bulk loop, asset download, frontend endpoint or automatic publication path in this package.
Reproduce the checks
@sanity/schema 6.16.0. Node 24.21.0 was used. Tests cover full metadata preservation, pending decisions, malformed and empty responses, one-request limits, draft-only mutation shape, credential preflight, no retries, redirect refusal and queue action filtering. HTTP behavior is mocked. Studio browser rendering, dataset permissions, real writes, real search relevance and article publication remain unverified.
Try the fixture, then open Lightdrift when you are ready to evaluate a real brief. For API setup see authentication; for a different orchestration environment see the n8n selection guide.