How to use `sanity exec` to import content without browser globals?
sanity exec? Everything I’ve found so far seems to be code meant to run in a browser (and when trying to import my schema I get errors looking for the
windowglobal)
The "window is not defined" error you're encountering happens because sanity exec runs your script in a Node.js environment, not a browser. When you try to import schema files or other code that references browser globals like window, it fails.
Here's how to write import scripts that work with sanity exec:
Basic Import Script Structure
Create a script file (e.g., scripts/import.ts) that uses the Sanity client in a Node environment:
// scripts/import.ts
import {getCliClient} from 'sanity/cli'
const client = getCliClient()
async function importContent() {
console.log(`Importing to:`)
console.log(`Project ID: ${client.config().projectId}`)
console.log(`Dataset: ${client.config().dataset}`)
// Your import logic here
const documents = [
{
_type: 'post',
_id: 'my-post-id',
title: 'My Post Title',
}
]
const transaction = client.transaction()
documents.forEach(doc => {
transaction.createOrReplace(doc)
})
await transaction.commit()
.then(() => console.log('Import complete!'))
.catch(err => console.error('Import failed:', err))
}
importContent()Run it with:
npx sanity exec scripts/import.ts --with-user-tokenThe --with-user-token flag provides authentication using your credentials.
Avoiding Schema Import Issues
If you need to reference your schema types but get window is not defined errors, you have a few options:
Option 1: Don't import the full schema
Instead of importing schema files directly, just use plain objects that match your schema structure. The client doesn't need the schema definitions to create documents.
Option 2: Import only type definitions
If you're using TypeScript, import just the generated types (not the schema):
import type {Post} from '../sanity.types'Option 3: Use Schema.compile for validation (as shown in the demo content guide)
If you need schema validation, compile it specifically for Node:
import {Schema} from '@sanity/schema'
import {schemaTypes} from '../schemas'
const defaultSchema = Schema.compile({types: schemaTypes})
const postSchema = defaultSchema.get('post')Complete Working Example
Here's a more complete example that imports content with transactions in batches to avoid rate limits:
// scripts/import.ts
import {getCliClient} from 'sanity/cli'
import type {SanityDocumentLike} from 'sanity'
const client = getCliClient()
// Your source data (could come from an API, file, etc.)
const sourceData = [
{title: 'Post 1', slug: 'post-1'},
{title: 'Post 2', slug: 'post-2'},
// ... more data
]
async function importContent() {
console.log(`Starting import...`)
// Transform your source data to Sanity documents
const documents: SanityDocumentLike[] = sourceData.map(item => ({
_type: 'post',
_id: item.slug, // Use source ID for idempotency
title: item.title,
}))
// Create in batches to avoid rate limits
const BATCH_SIZE = 100
for (let i = 0; i < documents.length; i += BATCH_SIZE) {
const batch = documents.slice(i, i + BATCH_SIZE)
const transaction = client.transaction()
batch.forEach(doc => transaction.createOrReplace(doc))
await transaction.commit()
console.log(`Imported batch ${i / BATCH_SIZE + 1}`)
}
console.log('Import complete!')
}
importContent()Key Points
- Use
getCliClient()fromsanity/cli- it's configured for Node.js - Run with
--with-user-tokenflag for authenticated operations - Use
createOrReplace()for idempotent imports (can run multiple times safely) - Batch operations into transactions to reduce API calls and avoid rate limits
- Don't import browser-dependent code (like full Studio schemas with custom components)
More Resources
The demo content creation guide I linked above has extensive examples including:
- Uploading images during imports
- Converting HTML to Portable Text
- Creating references between documents
- Handling asynchronous operations
For more complex migrations with validation and better tooling, consider using sanity migration instead, which provides built-in batching, dry-run capabilities, and schema validation. The migration tooling is often preferred for content migrations as it provides additional conveniences and safety features.
Show original thread9 replies
Was this answer helpful?
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.