Behavior difference between Sanity client and vision tool when referencing documents with a modified _id.
This is a known behavior in Sanity related to how document IDs with periods (dots) are handled. Based on the IDs and Paths documentation, here's what's happening:
The Issue
When you create document IDs with the format prefix.old__id, you're creating what Sanity considers a subpath document. According to the documentation:
Any document ID containing a dot is considered private and has restricted accessibility
All documents that contain a
.in their _id can only be accessed when a user is logged in or a valid authentication token is provided for client and HTTP API calls (minimumreadpermission required).
This explains why Vision works but your client returns null - Vision runs in an authenticated context within the Studio, while your client queries may not be properly authenticated.
Why Vision Works But Client Doesn't
- Vision Tool: Runs within the Studio with your authentication credentials automatically included
- Sanity Client: Needs explicit authentication to access documents with dots in their IDs
Solutions
Option 1: Add Authentication to Your Client (If you need private documents)
import {createClient} from '@sanity/client'
const client = createClient({
projectId: 'your-project-id',
dataset: 'your-dataset',
apiVersion: '2024-01-01',
token: 'your-read-token', // Add a token with at least read permissions
useCdn: false, // Important: CDN doesn't work with authenticated requests
})Option 2: Use a Custom Field Instead (Recommended)
Instead of using dots in the _id, store the old ID as a custom field in your schema. This is the recommended approach in the documentation:
// In your schema
defineField({
name: 'oldId',
title: 'Old ID',
type: 'string',
})Then during migration, set the _id to a standard Sanity UUID and store your prefix.old__id value in the oldId field:
{
_id: uuid(), // Standard Sanity ID
oldId: 'prefix.old__id', // Your migration reference
// ... other fields
}Query it like this:
*[oldId == "prefix.old__id"]Option 3: Replace Dots with Another Character
If you must preserve the ID structure in the _id itself, use underscores or hyphens instead:
prefix_old__idprefix-old--id
These will work as public document IDs without authentication.
Why This Restriction Exists
The dot-based restriction exists because Sanity uses dots for internal document paths:
drafts.documentIdfor draft documentsversions.release-name.documentIdfor release versions
Documents with dots in their IDs fall under the same access control rules as these internal documents for security reasons.
Bottom line: For your migration use case, I'd strongly recommend Option 2 (custom field) as it's more flexible, publicly accessible without authentication, and follows Sanity's best practices. You can still easily query and reference documents by their old IDs without the authentication complications.
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.