Sanity

Lightweight, dependency-free, in-memory fake of the Sanity content API for testing code that uses the real @sanity/client.

Default port: 4842

Quick start

import { SanityServer } from "./services/sanity/src/server.js";

const server = new SanityServer(4842);
await server.start();
// ... run your app/tests ...
await server.stop();

Point the real client at it (override the API host) or drive the HTTP API directly:

const res = await fetch(
  "http://127.0.0.1:4842/v2021-10-21/data/query/production?query=" + encodeURIComponent('*[_type == "post"]'),
  { headers: { Authorization: "Bearer parlel" } },
);
// { ms, query, result: [...] }

Access via MCP / preview URL

When run inside a parlel pool, reachable at its mapped preview URL (e.g. http://127.0.0.1:4842). MCP clients drive GROQ queries and mutations using SANITY_TOKEN, SANITY_PROJECT_ID, and SANITY_DATASET. Any non-empty Bearer token is accepted.

Implemented operations

State is in-memory and ephemeral. All routes require Authorization: Bearer <token>.

Query

Supported (minimal) GROQ:

QueryMeaning
*all documents
*[_type == "x"]documents whose _type equals x
*[_id == "x"]document(s) by id
...[0]trailing [0] picks the first match

Mutations

Documents

Service & inspection (parlel extensions)

Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

FeatureStatus
Document mutations: create / createIfNotExists / createOrReplace / patch (set/unset/inc) / delete✅ Supported
GROQ: *, *[_type == "x"], *[_id == "x"], trailing [0]✅ Supported
GET /data/doc/:dataset/:id✅ Supported
Bearer token validity / dataset ACLs✓ By design — Any non-empty credential is accepted — no real secrets needed
Full GROQ (projections, joins, ordering, functions, params)⟳ Roadmap
Assets API, listen (live) endpoint, history⟳ Roadmap
Real persistence✓ By design — In-memory by design — fast, isolated, resets cleanly between tests

Error shapes

Sanity uses { error: { description, type, statusCode } }:

StatusWhen
401missing/invalid Bearer token
400unsupported GROQ query/filter
404unknown route

Manifest

See services/sanity/manifest.json — name sanity, port 4842, protocol http, healthcheck /health, env SANITY_TOKEN, SANITY_PROJECT_ID, SANITY_DATASET, SANITY_BASE_URL.

<!-- parlel:testenv:start -->

Configuration — test.env

Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.

SANITY_TOKEN=parlel
SANITY_PROJECT_ID=parlel
SANITY_DATASET=production
SANITY_BASE_URL=http://parlel-bridge:4842
<!-- parlel:testenv:end -->