Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ npx sanity dev # Start Studio dev server
npx sanity build # Build Studio for production
npx sanity deploy # Deploy Studio to Sanity hosting

# Blueprints (infrastructure as code)
npx sanity blueprints init # Initialize a Blueprint and create a Stack
npx sanity blueprints plan # Preview infrastructure changes (read-only)
npx sanity blueprints deploy # Apply the Blueprint to the Stack
npx sanity blueprints info # Verify Stack status and deployed resources
npx sanity blueprints logs # View Stack deployment logs

# Help
npx sanity docs search "query" # Search Sanity documentation
npx sanity --help # List all CLI commands
Expand Down Expand Up @@ -55,7 +62,7 @@ If the Sanity MCP server (`https://mcp.sanity.io`) is available, use `list_sanit
| **GROQ** | `groq`, `query`, `defineQuery`, `projection`, `filter`, `order` | `skills/sanity-best-practices/references/groq.md` |
| **TypeGen** | `typegen`, `typescript`, `types`, `infer`, `satisfies`, `type generation` | `skills/sanity-best-practices/references/typegen.md` |
| **App SDK** | `app sdk`, `custom app`, `useDocuments`, `useDocument`, `DocumentHandle`, `SanityApp`, `sdk-react` | `skills/sanity-best-practices/references/app-sdk.md` |
| **Blueprints** | `blueprints`, `IaC`, `infrastructure`, `stack`, `defineBlueprint` | `skills/sanity-best-practices/references/blueprints.md` |
| **Blueprints** | `blueprints`, `IaC`, `infrastructure`, `stack`, `defineBlueprint`, `sanity.blueprint.ts`, `blueprints deploy` | `skills/sanity-best-practices/references/blueprints.md` |
| **Sanity Functions** | `functions`, `serverless`, `event handler`, `documentEventHandler`, `defineDocumentFunction`, `sanity.blueprint.ts` | `skills/sanity-best-practices/references/functions.md` |

### Using the Knowledge Router
Expand Down
2 changes: 1 addition & 1 deletion skills/sanity-best-practices/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Reference these guidelines when:
- `hydrogen` - Shopify Hydrogen with Sanity
- `project-structure` - Standalone Studio and monorepo patterns
- `app-sdk` - Custom applications with Sanity App SDK
- `blueprints` - Infrastructure as Code with Sanity Blueprints
- `blueprints` - Infrastructure as Code: blueprint files, stacks, plan/deploy workflow, error recovery, CI deploys
- `functions` - Automating content workflows with Sanity Functions

### Topic Guides
Expand Down
272 changes: 226 additions & 46 deletions skills/sanity-best-practices/references/blueprints.md
Original file line number Diff line number Diff line change
@@ -1,88 +1,268 @@
---
title: Sanity Blueprints
description: Rules for Sanity Blueprints, the Infrastructure as Code solution for managing Sanity resources declaratively.
description: Rules for Sanity Blueprints, the Infrastructure as Code solution for managing Sanity resources declaratively. Covers blueprint files, stacks and scope, the plan/deploy workflow, deletion policies, error recovery, and CI deployment.
---

# Sanity Blueprints

Sanity's Infrastructure as Code (IaC) solution. Define resources declaratively in `sanity.blueprint.ts`, track in version control, deploy with a single command.
Sanity's Infrastructure as Code (IaC) solution. Define resources declaratively in `sanity.blueprint.ts`, track the file in version control, preview changes with `plan`, apply them with `deploy`. Blueprints is the only way to deploy Sanity Functions.

The CLI is built for unattended use: every command accepts `--json`, all IDs can be supplied via flags or environment variables, and `--help` on any command is the authoritative reference. Always run via `npx sanity@latest blueprints <command>`.

## Mental Model

```
Blueprint (code) → Stack (deployed state) → Resources (real infrastructure)
Blueprint file (code, intent) → Stack (deployed state) → Resources (real infrastructure)
```

| Concept | What it is |
|---------|------------|
| **Blueprint** | A declarative configuration file (`sanity.blueprint.ts`) that describes your desired infrastructure |
| **Stack** | The deployed, real-world collection of resources managed by Blueprints |
| **Resources** | Individual Sanity components: CORS origins, webhooks, datasets, functions, roles, robots |
| **Operation** | A deployment execution that applies Blueprint changes to resources in a Stack |
| **Blueprint file** | `sanity.blueprint.ts`, the declarative manifest of desired resources. The source of truth: if it's not in the file, it's not deployed |
| **Resource** | One managed thing (function, CORS origin, webhook, role, robot token, dataset). Identified by a unique `name` |
| **Definer** | Typed helper from `@sanity/blueprints` (e.g. `defineCorsOrigin`) that declares a resource and validates input as you write |
| **Stack** | The deployed counterpart of the file: the live resources on Sanity's side. One file can deploy to many Stacks (e.g. `staging`, `production`) |
| **Config file** | `.sanity/blueprint.config.json`, links the local file to a Stack and records scope. Gitignored by `init` automatically; not secret |
| **Scope** | `project` or `organization`. Determines which resource types a Stack can manage |

### Key behaviors

- Resources are matched **by `name`** between file and Stack. `plan` buckets every resource as create, update, no-op, or destroy.
- **There is no rename.** Changing a resource's `name` destroys the old resource and creates a new one under the new name.
- **Removal is destruction.** Deleting a resource from the file destroys it on the next deploy (subject to its deletion policy). `blueprints destroy` is equivalent to deploying an empty file.
- Deploys apply in dependency order. On partial failure, completed changes are rolled back best-effort in reverse order. Rollback is **not atomic**; some actions may be irreversible.

## Three different "deploy" commands

Do not confuse these:

| Command | What it deploys |
|---------|-----------------|
| `sanity blueprints deploy` | Infrastructure resources defined in `sanity.blueprint.ts` |
| `sanity schema deploy` | Studio schema to Content Lake (for MCP and editor access) |
| `sanity deploy` | The Studio application to Sanity hosting |

## The Blueprint File

`sanity.blueprint.ts` default-exports a `defineBlueprint` call. TypeScript is idiomatic (definers validate inline and provide editor help); `.js` and hand-written `.json` also work but new files should use TypeScript.

```typescript
import {defineBlueprint, defineCorsOrigin, defineDocumentFunction} from '@sanity/blueprints'

export default defineBlueprint({
values: {
corsOrigin: 'https://studio.example.com',
},
resources: [
defineCorsOrigin({
name: 'studio-cors',
origin: '$.values.corsOrigin',
}),
defineDocumentFunction({
name: 'first-published',
event: {
on: ['create', 'update'],
filter: "_type == 'post' && !defined(firstPublished)",
},
}),
],
})
```

- `resources` is an array of definer outputs. Every resource requires a unique `name`.
- `values` holds reusable file-level constants (always strings), referenced as `$.values.<key>`.
- Resource-type-specific fields are documented in the typed reference: https://reference.sanity.io/_sanity/blueprints/

### Discovering available resources

The definers exported by `@sanity/blueprints` are the canonical list of what Blueprints can manage. Discover them by inspecting the package's exports (every `define*` function is a resource definer) and reading their TSDoc for fields, defaults, and examples, or browse the typed reference at https://reference.sanity.io/_sanity/blueprints/. Do not rely on a memorized list; new definers ship additively.

When the Stack is organization-scoped, project-contained resources (CORS origins, webhooks, datasets, functions) must set their `project` field explicitly.

For the semantics of an individual resource type (what a CORS origin, webhook, or role does and which values make sense), consult that feature's own Sanity documentation. This reference covers how Blueprints manages resources, not the resources themselves.

## References and the Resource Graph

Resources reference values and each other with `$` string paths:

| Syntax | Resolves | When |
|--------|----------|------|
| `$.values.<key>` | A constant from the `values` block | At file evaluation |
| `$.resources.<name>` | Another resource in the file | At deploy time; creates a dependency edge |
| `$.resources.<name>.id` | The generated ID of that resource, as a string | At deploy time |

Blueprints builds the dependency graph from these references and orders work so dependencies are created first. Unresolved references and cycles fail validation before anything is deployed. To force ordering without a data reference, use `lifecycle: {dependsOn: '$.resources.<name>'}`.

### Referencing an existing project

The recommended pattern is an environment variable surfaced through `values`:

```typescript
import 'dotenv/config' // the CLI does not read .env files itself

export default defineBlueprint({
values: {
projectId: process.env.SANITY_PROJECT_ID ?? '',
},
resources: [
defineCorsOrigin({
name: 'app-cors',
project: '$.values.projectId',
origin: 'https://www.example.com',
}),
],
})
```

## Deletion Policies

### How it works
Set `lifecycle.deletionPolicy` on a resource to control what removal means:

1. Initialize and edit a Blueprint file describing desired resources
2. Run `sanity blueprints deploy` to apply changes to resources in a Stack
3. Blueprints creates/updates a Stack with your resources
4. The Stack persists — future deploys update it based on Blueprint changes
| Policy | Removed from file | Stack destroyed | Notes |
|--------|-------------------|-----------------|-------|
| `allow` | Resource destroyed | Resource destroyed | Default for stateless resources |
| `retain` | **Deployment fails** | Resource detached, kept alive | Default for stateful resources like datasets |
| `replace` | Resource destroyed | Resource destroyed | Updates become destroy + recreate |
| `protect` | **Deployment fails** | **Deployment fails** | Resource is never updated or destroyed |

**Key insight:** The Blueprint is your *intent*. The Stack is *reality*. Blueprints reconciles the two.
Use `protect` for production-critical stateful resources:

## Available Resources
```typescript
defineDataset({
name: 'production',
lifecycle: {deletionPolicy: 'protect'},
})
```

A deletion-policy violation is a pre-deploy validation failure, not a partial deploy: fix the file (or consciously change the policy) and re-run `plan`.

## Stacks, Environments, and Scope

One blueprint file deploys to many Stacks. The `--stack <name-or-id>` flag (on `deploy`, `plan`, `info`, `logs`) selects the target; without it, the Stack recorded in `.sanity/blueprint.config.json` is used.

Blueprints can manage these Sanity components:
- **Only `init` creates Stacks.** `--stack` never creates a Stack on a miss, so CI cannot accidentally provision infrastructure. Re-run `blueprints init .` to add another Stack for the same file.
- Accounts are currently limited to **three Stacks per project scope**.
- For per-environment differences, read an environment variable inside the file (e.g. `process.env.SANITY_ENV`) and deploy with the env var and `--stack` together. `SANITY_ENV` is a convention your file reads, not a CLI feature.

- Document Functions
- Media Library Asset Functions
### Project vs organization scope

More resources are coming soon.
| | Project scope | Organization scope |
|---|--------------|--------------------|
| Stored in config as | `projectId` | `organizationId` |
| Resources default to | The project | Nothing; each project-contained resource must name its `project` |
| Org-scoped resource types | Not available | Available |

Organization scope is recommended for anything beyond a single-project experiment. Convert an existing Stack with `blueprints promote` (safe, idempotent, additive, but **one-way**; requires `organization-update` admin permission).

## CLI Commands

```bash
sanity blueprints init <name> # Initialize a new blueprint project
sanity blueprints info # Show current stack status and resources
sanity blueprints plan # Preview changes before deploying
sanity blueprints deploy # Deploy the blueprint (creates/updates stack)
sanity blueprints config # Configure the blueprint (edit project and stack)
sanity blueprints logs # View deployment logs
sanity blueprints doctor # Check for potential issues
sanity blueprints stacks # List all stacks for the project
sanity blueprints destroy # Destroy all resources in the stack
npx sanity@latest blueprints <command>
```

## Basic Workflow
| Command | Purpose |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Could let agent know they can npx sanity@latest blueprints help to learn more if they need / they run into issues (helps if skill ever gets out of sync with deployments)

|---------|---------|
| `init [dir]` | Create a blueprint file and provision a remote Stack. The only command that creates Stacks |
| `plan` | Preview the diff against the Stack. Read-only, always safe |
| `deploy` | Apply the file to the Stack (`-m` message, `--no-wait` to queue and return) |
| `info` | Show Stack status and deployed resources. Remote read; works without local files |
| `stacks` | List Stacks (`--include-projects` with `--organization-id` for org-wide audit) |
| `config` | View or edit `.sanity/blueprint.config.json` (`--edit` with ID flags for scripting) |
| `logs` | Deployment logs (`--watch`, `--limit 1-500`, `--since`/`--before` ISO timestamps) |
| `promote` | Convert a Stack from project to organization scope. One-way (`--force` to skip confirmation) |
| `destroy` | Destroy the Stack and its resources (`--force` to skip confirmation). Local files remain |
| `doctor` | Diagnose local/remote configuration issues (`--fix` to resolve interactively) |
| `mint-deploy-token` | Mint a long-lived robot token for CI deploys (`--print` for shell pipelines) |

Function scaffolding, local testing, secrets, and function logs live under `npx sanity@latest functions <command>` (`add`, `dev`, `test`, `env add|list|remove`, `logs`). See the `functions` reference.

### 1. Initialize
### Standard workflow

```bash
sanity blueprints init my-infra
cd my-infra
sanity blueprints info
npx sanity@latest blueprints init . # once: create file + Stack
# edit sanity.blueprint.ts
npx sanity@latest blueprints plan # preview the diff, catch validation errors safely
npx sanity@latest blueprints deploy # apply
npx sanity@latest blueprints info # verify deployed state
```

This creates a `sanity.blueprint.ts` file and links it to a Sanity project.
Always run `plan` before `deploy`. It is read-only and surfaces validation errors, reference problems, scope mismatches, and deletion-policy violations without touching anything.

### Exit codes

`deploy` and `destroy` return: **`0`** succeeded, **`2`** failed, **`75`** accepted but completion could not be confirmed.

**✅ Correct — treat exit 75 as unknown, then verify:**
```bash
npx sanity@latest blueprints deploy || status=$?
if [ "${status:-0}" -eq 75 ]; then
npx sanity@latest blueprints info --json # confirm actual Stack state
fi
```

**❌ Incorrect — treating any nonzero exit as failure and retrying.** Exit 75 means the deployment was accepted; blindly redeploying can queue duplicate operations (deploy also refuses to start while another operation is in progress).

### Scope resolution and environment variables

### 2. Define resources
For `organizationId`, `projectId`, and `stackId`, the first source that resolves wins: CLI flags → environment variables → the blueprint file module → the local config file.

Edit `sanity.blueprint.ts` to add resources using typed helper functions from `@sanity/blueprints`.
| Variable | Purpose |
|----------|---------|
| `SANITY_AUTH_TOKEN` | Auth token (in CI, the minted deploy token) |
| `SANITY_ORGANIZATION_ID` / `SANITY_PROJECT_ID` | Scope |
| `SANITY_BLUEPRINT_STACK_ID` | Target Stack |
| `SANITY_BLUEPRINT_PATH` | Path to the blueprint file or its directory |
| `SANITY_ASSET_TIMEOUT` | Seconds to wait for resource asset processing (default 60) |

### 3. Preview and deploy
The CLI does not load `.env` files. Export variables in the shell or import `dotenv/config` inside the blueprint file.

## Errors and Recovery

Errors include a name, a human-readable message, and the **resource path**, which maps to a specific resource in your file. Five failure kinds:

| Failure | Caught | What to do |
|---------|--------|------------|
| Validation error (missing/invalid field, malformed file) | Before deploy | Fix the file at the reported resource path |
| Unresolved reference or dependency cycle | Before deploy | Fix the `$.resources.<name>` reference or break the cycle |
| Scope mismatch (org-scoped resource on project Stack, or no resolvable `project`) | Before deploy | Set the resource's `project` field, or `promote` the Stack |
| Deletion-policy violation (change would remove a `retain`/`protect` resource) | Before deploy | Restore the resource, or deliberately relax its policy |
| Execution failure (underlying service rejected the change) | During deploy | Read `blueprints logs`, fix the cause, redeploy |

Recovery procedure after any failure:

1. Read the error message and resource path (`blueprints logs` re-reads failure output).
2. Fix the blueprint file (or the external condition).
3. Run `blueprints plan` to confirm the corrected diff.
4. Run `blueprints deploy` again.

Retrying without changing anything only makes sense for transient execution failures; the first four failure kinds are deterministic and will fail identically until the file changes. If local and remote state seem inconsistent, run `blueprints doctor` (and `doctor --fix`) before attempting anything destructive. After a partial-deploy rollback, `blueprints info` shows what is actually live; resources owned by other Stacks are never touched by rollback.

## CI/CD

Recommended workflow: `plan` on pull requests, `deploy` on merge to main. Official GitHub Actions: https://github.com/sanity-io/blueprints-actions

```bash
sanity blueprints plan # See what will change
sanity blueprints deploy # Apply changes
# one-time, by a human with access:
npx sanity@latest blueprints mint-deploy-token --label "ci-deploy" --print
```

### 4. Iterate
The minted robot token has exactly the role needed to plan, deploy, and destroy (`blueprints-deployer` for project scope, `blueprints-deployer-robot` for organization scope) and is revocable under Robots in Sanity Manage. Store it as a CI secret, then in the pipeline:

Modify your Blueprint and redeploy. Blueprints handles creating, updating, or removing resources to match your definition.
```bash
export SANITY_AUTH_TOKEN=<the minted token>
export SANITY_ORGANIZATION_ID=<orgId> # or SANITY_PROJECT_ID
export SANITY_BLUEPRINT_STACK_ID=<stackId>
npx sanity@latest blueprints deploy --json --message "CI deploy"
```

## Key Behaviors
**✅ Correct — secrets via server-side function env vars:**
```bash
npx sanity@latest functions env add my-function API_KEY sk-...
```

- **Additive by default** — New resources in the Blueprint are created
- **Updates in place** — Changed resources are updated when possible
- **Removal = destruction** — Resources removed from the Blueprint are destroyed from the Stack
- **References** — Resources can reference each other (e.g., a webhook can reference a dataset)
- **Rollback on failure** — If a deployment fails partway through, Blueprints attempts to rollback
**❌ Incorrect — secrets in the blueprint file's `env` block, which is committed to git:**
```typescript
defineDocumentFunction({
name: 'my-function',
env: {API_KEY: 'sk-...'}, // committed to version control!
})
```
Loading