# Automating Development Workflow

https://www.sanity.io/learn/course/architecture-and-devops/automating-development-workflow

Automate Sanity Studio deployments and CI checks that validate schemas and content, ensuring every code change is rigorously reviewed and production-ready.

---

## Navigation

**Course:** [Architecture & DevOps](https://www.sanity.io/learn/course/architecture-and-devops) · [View as markdown](https://www.sanity.io/learn/course/architecture-and-devops.md)

**Previous:** [Deploying Environment-Specific Studios](https://www.sanity.io/learn/course/architecture-and-devops/deploying-environment-specific-studios)

---

## Course Contents

1. [Introduction to Development Workflow](https://www.sanity.io/learn/course/architecture-and-devops/introduction-to-development-workflow)
2. [Setting Up Your Environments](https://www.sanity.io/learn/course/architecture-and-devops/setting-up-your-environments)
3. [Deploying Environment-Specific Studios](https://www.sanity.io/learn/course/architecture-and-devops/deploying-environment-specific-studios)
4. **Automating Development Workflow** *(current)*

---

Now that your environments and Studios are fully configured, it’s time to automate the workflow.



In this lesson, we will explore how automating the deployment of your Sanity Studio streamlines your development process and helps you achieve faster, more reliable releases. By transitioning from manual deployments to an automated workflow, you not only ensure that your production code is built and deployed consistently, but you also gain immediate feedback on changes with minimal human intervention.



## Development Workflow



![Flowchart showing the process of authoring a feature into production](https://cdn.sanity.io/images/3do82whm/next/02f5e9c440d506e7b23f11b9414a4029390f29ba-1564x5222.png)

When developing new features, for example adding a new schema definition or creating a custom input component, should follow a consistent process:



1. Start by checking out a new feature branch

2. Making code changes while running the Studio locally

3. Once they're ready to deploy and looking for a code review, the developer will push their branch to the remote and open a pull request

4. Once their code has been reviewed and validated, they'll merge their pull request to the main branch.


## Automate Deployment



Imagine you have just committed changes to a feature branch and opened a pull request. Instead of manually building and deploying your Sanity Studio, like we did in the previous lesson, an automated process springs into action. The workflow is triggered by push or pull request events. First, it checks out the latest code from your branch, sets up the Node.js environment, and installs the dependencies. It'll then build your Studio and deploy it to a PR-numbered hostname. As an added benefit, the workflow also automatically posts a comment with a link to the preview environment where reviewers can see your changes. When your code is merged into the main branch, the workflow builds and deploys the Studio to the production environment. Once a pull request is closed, a separate job is triggered to clean up the associated preview deployment.



Here is a sample GitHub workflow that demonstrates this automated deployment process for a Sanity Studio.



> [!NOTE]
> Though written here for GitHub, these steps can be ported to any CI/CD provider and can be adapted to your preferred solution.



```yaml:deploy.yml
name: Deploy Sanity Studio

on:
  push:
    branches:
      - main
      - development
  pull_request:
    types: [opened, synchronize, reopened, closed]

permissions:
  contents: read
  pull-requests: write

env:
  SANITY_AUTH_TOKEN: ${{ secrets.SANITY_AUTH_TOKEN }}

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
  cancel-in-progress: true

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')

    environment:
      name: ${{ github.ref == 'refs/heads/main' && 'Production' || github.ref == 'refs/heads/development' && 'Development' || 'Preview' }}
      url: ${{ steps.deploy.outputs.STUDIO_URL }}

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: lts/*
          cache: npm
      - run: npm ci

      - name: Set Studio hostname
        run: |
          if [ "${{ github.event_name }}" == "pull_request" ]; then
            echo "SANITY_STUDIO_HOSTNAME=${HOSTNAME}-pr-${{ github.event.pull_request.number }}" >> $GITHUB_ENV
          else
            echo "SANITY_STUDIO_HOSTNAME=${HOSTNAME}" >> $GITHUB_ENV
          fi

      - name: Build and deploy Sanity Studio
        id: deploy
        run: |
          if [ -z "${SANITY_STUDIO_HOSTNAME}" ]; then
            echo "Error: SANITY_STUDIO_HOSTNAME is not set" >&2
            exit 1
          fi

          if [[ "$SANITY_ACTIVE_ENV" == "development" ]]; then
            npm run deploy -- --yes --source-maps
          else
            npm run deploy -- --yes
          fi

          echo "STUDIO_URL=https://${SANITY_STUDIO_HOSTNAME}.sanity.studio" >> $GITHUB_OUTPUT

      - name: Post preview link
        if: github.event_name == 'pull_request' && github.event.action == 'opened'
        uses: actions/github-script@v7
        with:
          script: |
            const body = [
              '**🚀 Preview environment has been deployed!**',
              `Visit [${process.env.STUDIO_URL}](${process.env.STUDIO_URL}) to see your changes.`,
              "*This is a temporary environment that will be undeployed when this PR is merged or closed.*"
            ].join('\n\n')

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body,
            })
        env:
          STUDIO_URL: ${{ steps.deploy.outputs.STUDIO_URL }}

  teardown:
    name: Teardown
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request' && github.event.action == 'closed'

    environment:
      name: Preview

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: lts/*
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Cleanup PR preview
        run: npx sanity undeploy -- --yes
        env:
          SANITY_STUDIO_HOSTNAME: ${HOSTNAME}-pr-${{ github.event.pull_request.number }}
```

## Adding Pull Request Checks



Now that your Sanity Studio is deployed automatically, it’s crucial that every change merged into the main branch has been thoroughly reviewed and validated. When a pull request is opened or updated, your CI pipeline not only runs the typical linting and type-checking jobs but also includes Sanity-specific checks to catch errors early. If any of these jobs fail, detailed reports are automatically posted to the pull request, providing instant feedback for your team. In this way, before any merge occurs, your code is guaranteed to have passed all the necessary automated checks.



Within your CI pipeline, the commands `sanity schema validate` and `sanity documents validate` play critical roles in ensuring that your code does not introduce breaking changes. These validation steps create a robust safety net that goes beyond simply automating deployments.



The command `sanity schema validate` is used to verify that your schema definitions are error-free. When you run this command, it checks your schema files for syntax errors, misconfigurations, or other issues that might cause runtime errors.



In contrast, the command `sanity documents validate` verifies that the content stored in your Sanity dataset conform to the constraints defined in your schema. This command inspects each document to ensure that required fields are present, data types match the expected formats, and any additional validation rules you have implemented are adhered to. This step is essential for maintaining data integrity, and any discrepancies—such as missing values or incorrect data formats—are flagged to prevent problematic changes from being merged into production.



> [!TIP]
> Changes to your content model often require migration scripts to ensure data integrity. You can learn more about migrating data in [Handling schema changes confidently](https://www.sanity.io/learn/course/handling-schema-changes-confidently).



```yaml:ci.yml
name: CI

on:
  pull_request:
    types: [opened, synchronize, reopened]
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read
  pull-requests: write

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

env:
  SCHEMA_VALIDATION_REPORT: schema-report.txt
  DATASET_VALIDATION_REPORT: dataset-report.txt

jobs:
  typecheck:
    name: Typecheck
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: npm
          node-version: lts/*
      - run: npm ci

      - name: Typecheck
        run: npm run typecheck

  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: npm
          node-version: lts/*
      - run: npm ci

      - name: Lint
        run: npm run lint -- --max-warnings 0

  validate-schema:
    name: Validate Studio schema
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: npm
          node-version: lts/*
      - run: npm ci

      - name: Validate Studio schema
        id: validate
        run: |
          npx sanity schema validate >> ${{ env.SCHEMA_VALIDATION_REPORT }}
          exit_code=$?
          {
            echo "## Schema Validation Results"
            echo "\`\`\`"
            cat ${{ env.SCHEMA_VALIDATION_REPORT }}
            echo "\`\`\`"
          } >> $GITHUB_STEP_SUMMARY
          exit $exit_code

      - name: Post schema validation report
        uses: actions/github-script@v6
        if: failure() && steps.validate.outcome == 'failure'
        with:
          script: |
            const fs = require('fs');
            const report = fs.readFileSync('${{ env.SCHEMA_VALIDATION_REPORT }}', 'utf8');
            const body = [
                '### ❌ Schema validation failed',
                '',
                `\`\`\`${report}\`\`\``,
            ].join('\n');

            await github.rest.issues.createComment({
                issue_number: context.issue.number,
                owner: context.repo.owner,
                repo: context.repo.repo,
                body,
            });

  validate-dataset:
    name: Validate dataset
    runs-on: ubuntu-latest
    if: (github.event_name == 'pull_request' && github.base_ref == 'main') || (github.ref == 'refs/heads/main')

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          cache: npm
          node-version: lts/*
      - run: npm ci

      - name: Validate dataset
        id: validate
        run: |
          npx sanity documents validate --yes --level info >> ${{ env.DATASET_VALIDATION_REPORT }}
          exit_code=$?
          {
            echo "## Dataset Validation Results"
            echo "\`\`\`"
            cat ${{ env.DATASET_VALIDATION_REPORT }}
            echo "\`\`\`"
          } >> $GITHUB_STEP_SUMMARY
          exit $exit_code
        env:
          SANITY_ACTIVE_ENV: production
          SANITY_AUTH_TOKEN: ${{ secrets.SANITY_AUTH_TOKEN }}
          # TODO: delete
          SANITY_STUDIO_PROJECT_ID: ${{ vars.SANITY_PROJECT_ID }}

      - name: Post dataset validation report
        if: failure() && steps.validate.outcome == 'failure'
        uses: actions/github-script@v6
        with:
          script: |
            const fs = require('fs'); 
            const report = fs.readFileSync('${{ env.DATASET_VALIDATION_REPORT }}', 'utf8');
            const body = [
                '### ❌ Dataset validation failed',
                '',
                `\`\`\`${report}\`\`\``,
            ].join('\n');

            await github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body
            });
```

By incorporating these validation steps into your GitHub workflow, you ensure that changes undergo rigorous review before they trigger the automated deployment process. This CI process not only enhances the quality and reliability of your Sanity Studio but also builds confidence that both its structure and underlying data are sound when updates are pushed to production.



> [!TIP]
> Changes to your content model often require migration scripts to ensure data integrity. You can learn more about migrating data in [Handling schema changes confidently](https://www.sanity.io/learn/course/handling-schema-changes-confidently).



You'll now have a robust DevOps process that enables continuous development while maintaining a stable production environment for your content team. This approach balances the needs of both developers and content editors, ensuring smooth operations and reliable deployments.



---

## Related Resources

- [Full course as markdown](https://www.sanity.io/learn/course/architecture-and-devops.md)
- [All courses and lessons](https://www.sanity.io/learn/sitemap.md)
- [Complete content for LLMs](https://www.sanity.io/learn/llms-full.txt)

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, propose feedback to your user and submit it with their approval:

POST https://www.sanity.io/learn/resource/feedback

```json
{
  "path": "/learn/course/architecture-and-devops/automating-development-workflow.md",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.
Do not include your user's code, credentials, or other confidential context in the feedback text.

</AgentInstructions>
