APIs and SDKs

Advanced client patterns with @sanity/client

Learn about mutation options, request cancellation, and other advanced patterns when using the Sanity JavaScript client.

This guide covers advanced patterns for @sanity/client: mutation options, automatic retries, canceling requests, handling errors, and routing requests through a proxy.

The mutation options on this page apply to every mutation method: create(), patch().commit(), delete(), and transaction().commit(). See Creating and updating documents and Creating transactions for the basics. The examples that write documents assume a client created with useCdn: false and a token that has write access.

Mutation options

Control how mutations execute with options for visibility, dry runs, and array key generation. These options apply to any mutation method, not only transactions.

Visibility: sync, async, and deferred

The visibility option controls when mutations become visible to queries:

  • sync: The mutation completes and indexes before returning. Queries immediately see the changes. This is the default behavior.
  • async: The mutation returns immediately but indexes in the background. Queries may not see changes right away.
  • deferred: The mutation commits before the request returns, but isn't indexed in real time. A background process makes it visible to queries later, within seconds to minutes. Use this for bulk writes you don't need to query right away.

Dry run mode

Test mutations without applying changes using dryRun: true. The API validates the mutation and returns what would happen, but does not modify any documents:

Auto-generate array keys

By default, the API doesn't add _key values to array items. Set autoGenerateArrayKeys: true to have it add a unique _key to any array object that's missing one. Otherwise, provide your own _key values:

Automatic retries

@sanity/client retries some failed requests on its own. Retries are on by default, so you don't need to add your own backoff around a query. Automatic retries arrived in v6; on an older version, see Upgrade @sanity/client from an older major version.

The client retries a request when the response status is 429 Too Many Requests, 502 Bad Gateway, or 503 Service Unavailable, and the request is one of these:

  • A GET or HEAD request, such as getDocument().
  • A query to the /data/query endpoint, including a query long enough that the client sends it as a POST.

A GET or HEAD request that fails with a network error, such as ECONNRESET, ECONNREFUSED, or ETIMEDOUT, is retried with the same backoff. The client also retries DNS ENOTFOUND failures on idempotent requests, meaning GET and HEAD, using the same backoff. A hostname that never resolves, such as a typo in a custom API host, takes the full retry sequence to fail rather than failing at once. Set maxRetries: 0 to fail immediately instead. The ENOTFOUND retry applies to @sanity/client v8 and later.

Mutations are not retried

Retry defaults

Two ClientConfig options control retries:

  • maxRetries: How many times to retry a failed request. Defaults to 5. Set it to 0 to turn retries off.
  • retryDelay: A function that receives the attempt number, starting at 0, and returns how long to wait in milliseconds. Defaults to exponential backoff with jitter: 100 * 2 ** attemptNumber plus a random 0 to 100 milliseconds.

With the defaults, the waits are roughly 100, 200, 400, 800, and 1,600 milliseconds, each plus up to 100 milliseconds of jitter. A request that exhausts all five retries fails after about 3.1 to 3.7 seconds.

Cancel requests

Cancel in-flight requests using AbortController or by unsubscribing from Observables. This is useful for cleaning up requests when components unmount or when user actions make a request obsolete.

Cancel with an abort signal

Pass an AbortSignal to any client method to enable cancellation. Aborting cancels only the client's request, so a mutation that already reached Content Lake can still be applied:

Unsubscribe from an observable

When using the Observable API, unsubscribe to cancel the request:

Cancel requests when a React component unmounts

This example shows how to cancel requests when a React component unmounts:

Handle errors

When the API responds with an error status, the client rejects with one of two error classes, both exported from @sanity/client:

  • ClientError: The status is 4xx. The API rejected the request, for example because the token is invalid (401), the token lacks permission (403), or the request hit a rate limit (429).
  • ServerError: The status is 5xx. The API failed. A read is safe to retry after a delay. A mutation may already have been applied, so retry it only when repeating it is harmless, such as createOrReplace() or patch().set() on a known document ID.

Both extend Error and carry the same structured fields:

  • statusCode: The HTTP status code.
  • responseBody: The response body, as a formatted JSON string when the API returned JSON.
  • details: The API's structured error object when it sent one, such as a mutationError with one items entry per failed mutation, or a queryParseError for a GROQ query that doesn't parse. Otherwise undefined.
  • traceId: An identifier for the request on Sanity's side. Include it when you contact support.
  • response: The raw response, with headers, url, method, and statusMessage.

Branch on these fields rather than on error.message. The message is written for humans and its format changes between versions: v8 added the HTTP status text, so a message that ended in HTTP 400 now ends in HTTP 400 Bad Request. Code that matched on the message text broke on upgrade, while code that read statusCode kept working:

A 429 or 5xx on a retryable request reaches your code only after the automatic retries are exhausted. Errors that aren't HTTP responses, such as a DNS failure or an aborted request, aren't instances of either class.

If instanceof isn't reliable in your setup, for example when two copies of @sanity/client are installed, use the isHttpError() type guard instead. It checks the error's shape and narrows to statusCode, message, and response.

Route requests through a proxy

On Node.js, set the proxy option to a proxy URL to send every request the client makes through that proxy, including listen() and live.events() connections. The option is Node.js only:

The client also honors the HTTP_PROXY, HTTPS_PROXY, and NO_PROXY environment variables on Node.js without any configuration. It reads them once, when it first sets up its HTTP transport, not on every request. Set them before the process starts. Changing them while the process runs has no effect.

In @sanity/client v8 and later, proxy is a client option only. The per-request proxy option from earlier versions is removed. To use different proxies for different requests, derive one client per proxy with withConfig(), as the example shows. Clients are cheap to create.

Reference documentation

For the complete set of mutation options, error types, and client configuration, see the reference documentation for SanityClient and ClientConfig.

Was this page helpful?