How to use `sanity exec` to import content without browser globals?

9 replies
Last updated: Jan 28, 2026
Hello! Is there some sample code/examples/docs for how to run a script to import existing content using
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
window
global)
AI Update

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-token

The --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() from sanity/cli - it's configured for Node.js
  • Run with --with-user-token flag 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 thread
9 replies

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.

Was this answer helpful?