
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeThis 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:
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.
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
})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"]If you must preserve the ID structure in the _id itself, use underscores or hyphens instead:
prefix_old__idprefix-old--idThese will work as public document IDs without authentication.
The dot-based restriction exists because Sanity uses dots for internal document paths:
drafts.documentId for draft documentsversions.release-name.documentId for release versionsDocuments 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 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.
Content operations
Content backend


The only platform powering content operations
By Industry


Tecovas strengthens their customer connections
Build and Share

Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag store