sanity_api
A pure Dart client for the Sanity.io Content Lake. Query with GROQ, create and patch documents, commit transactions, and upload assets — from Dart servers, CLI tools, Cloud Functions and Flutter apps alike.
No Flutter dependency. Existing Dart Sanity packages either declare the Flutter SDK — which pub refuses to resolve inside a plain Dart project — or are read-only. This one is neither.
import 'package:sanity_api/sanity_api.dart';
final client = SanityClient(SanityConfig(
projectId: 'abc123',
dataset: 'production',
apiVersion: '2024-05-03',
));
final exercises = await client.fetch<List<Object?>>(
r'*[_type == "exercise" && $tag in tags]{_id, title}',
params: {'tag': 'shoulder'},
);
Writing
Writes need a token with Editor rights, and should use useCdn: false so a read
straight after a write isn't served a stale cached copy.
final writeClient = SanityClient(SanityConfig(
projectId: 'abc123',
dataset: 'production',
apiVersion: '2024-05-03',
token: Platform.environment['SANITY_TOKEN'],
useCdn: false,
));
await writeClient.patchId('exercise-123')
.set({'title': 'Shoulder press'})
.inc({'revisions': 1})
.commit();
await writeClient.transaction()
.createIfNotExists({'_id': 'tag-a', '_type': 'tag', 'title': 'A'})
.patchId('exercise-123', (p) => p.append('tags', [
{'_type': 'reference', '_ref': 'tag-a'},
]))
.commit();
Assets
final asset = await writeClient.assets.upload(
SanityAssetType.image,
await File('slide.png').readAsBytes(),
filename: 'slide.png',
contentType: 'image/png',
extract: ['blurhash', 'palette'],
);
Image and file URLs
final url = client
.image(SanityAssetRef(doc['image']['asset']['_ref'] as String))
.width(800)
.quality(90)
.format(SanityImageFormat.auto)
.build();
Transform parameters are skipped for GIFs, which the CDN cannot transcode without losing animation.
Perspectives
SanityPerspective.drafts and any SanityPerspective.stack([...]) bypass the CDN
automatically, because the CDN cannot serve them.
await client.fetch<Object?>('*[_type == "exercise"]',
perspective: SanityPerspective.drafts);
Errors
Every failure throws. Nothing is swallowed, and an empty query result is a result, not an error.
| Exception | When |
|---|---|
SanityClientException |
4xx — bad query, missing permission, conflict |
SanityServerException |
5xx |
SanityTransportException |
no response at all: DNS, connection reset, timeout |
429, 502 and 503 are retried automatically on queries (including the POST
fallback for long queries); mutations are not retried, since they are not
idempotent unless you supply a transactionId.
GROQ syntax errors arrive with a rendered code frame:
GROQ query parse error:
> 1 | *[_type == "exercise"
| ^
> 2 | && bad]
| ^^ unexpected token
On servers
SanityClient is cheap to construct and holds a connection pool. Create one per
process and reuse it, so warm Cloud Function invocations reuse connections:
SanityClient? _client;
SanityClient sanityClient() => _client ??= SanityClient(SanityConfig(...));
Scope
Covered: queries, documents by id, mutations, patches, transactions, asset uploads, image and file URLs, draft and version id helpers.
Not covered: live/listen (SSE), Content Releases, AI Agent Actions, the Media Library, dataset/project/user administration, and the Studio visual-editing machinery (Content Source Maps and stega encoding). Open an issue if you need one of these.
Relationship to @sanity/client
This is a Dart port of the official JavaScript client, @sanity/client 7.20.0
(MIT). Request construction, GROQ parameter encoding, mutation and patch
semantics, and error message formatting follow that implementation, and the test
suite compares this client's output against fixtures captured from it. See
THIRD_PARTY_NOTICES.
Two deliberate differences:
- One result type for mutations. The JavaScript client returns four different
shapes depending on
returnDocumentsandreturnFirst.SanityMutationResultalways exposesdocumentIds,documents,documentIdanddocument, so nothing is lost and nothing needs casting. The request sent is unchanged. Future, notObservable. There is no parallel observable API;Streamis reserved for genuinely multi-valued results.
Libraries
- sanity_api
- A pure Dart client for the Sanity.io Content Lake.