Automating Development Workflow
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.
When developing new features, for example adding a new schema definition or creating a custom input component, should follow a consistent process:
- Start by checking out a new feature branch
- Making code changes while running the Studio locally
- 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
- Once their code has been reviewed and validated, they'll merge their pull request to the main branch.
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.
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 }}
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.
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.
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.