Developer Tools8 min read

How to Integrate GitHub Actions with Your Headless CMS

Trigger builds, docs deployments, SDK generation, and release automation from structured content changes in your headless CMS.

Published April 30, 2026
01 โ€” Overview

What is GitHub Actions?

GitHub Actions is GitHub's built-in automation platform for CI/CD and event-driven developer workflows. Teams use YAML workflow files in .github/workflows to run jobs on push, pull request, schedule, workflow_dispatch, repository_dispatch, and other events. It runs jobs on GitHub-hosted runners or self-hosted runners, which makes it a common choice for teams that already keep application code, docs, or packages in GitHub.


02 โ€” The case for integration

Why integrate GitHub Actions with a headless CMS?

Content changes often need developer workflows. A new changelog entry may need to rebuild docs, a product update may need to regenerate a static site, and a schema change may need to run tests before release. If your editors publish in one system and your build pipeline lives in GitHub, somebody usually ends up copying a URL into Slack, rerunning a workflow by hand, or waiting for the next scheduled job.,Connecting GitHub Actions to a headless CMS turns those content events into build events. With Sanity, content is structured in the Content Lake as typed JSON, so the workflow can receive a document ID, fetch exactly the fields it needs with GROQ, and run a specific GitHub Actions workflow with workflow_dispatch. No HTML scraping. No page blob parsing. No nightly batch job that rebuilds everything when only one release note changed.,There are trade-offs. You need to handle GitHub rate limits, token permissions, retries, and workflow failures. You also need to avoid triggering expensive builds for every draft save. Sanity webhooks and Functions help by letting you trigger only on published mutations, filter by document type, and run server-side logic close to the content event before calling GitHub's API.


03 โ€” Architecture

Architecture overview

A typical setup starts when an editor publishes content in Sanity Studio. The published mutation lands in the Content Lake, then a Sanity webhook or Function runs only for the document types you care about, such as releaseNote, docsPage, or changelogEntry. The webhook payload can include the document _id, _type, and revision, while the Function can fetch the latest document with GROQ, including referenced products, authors, tags, or version data. From there, server-side code calls GitHub's REST API through Octokit, usually createWorkflowDispatch, with the repository owner, repository name, workflow file name, branch ref, and small inputs like docId, slug, and contentType. GitHub Actions picks up those inputs, runs the configured jobs, and ships the result to the end user, such as a rebuilt docs site, a generated SDK package, or an updated changelog page.


04 โ€” Use cases

Common use cases

๐Ÿ“š

Rebuild developer docs on publish

When a docsPage is published in Sanity, trigger a GitHub Actions workflow that builds and deploys only the docs site.

๐Ÿท๏ธ

Generate release notes

Publish a releaseNote document, then run a workflow that updates CHANGELOG.md, opens a pull request, or tags a release.

๐Ÿงช

Run content-aware tests

Trigger link checks, schema checks, screenshots, or fixture tests when product, pricing, or API reference content changes.

๐Ÿ“ฆ

Publish SDK or package docs

Use content changes in Sanity to start workflows that regenerate API docs, update examples, and publish package documentation.


05 โ€” Implementation

Step-by-step integration

  1. 1

    Create a GitHub Actions workflow

    Add a workflow file such as .github/workflows/content-build.yml. Include on.workflow_dispatch with inputs like docId, slug, and contentType, then add the build, test, or deploy jobs you want to run.

  2. 2

    Create a GitHub token

    Create a fine-grained personal access token or GitHub App token with Actions write access to the target repository. Store it as GITHUB_TOKEN_FOR_SANITY in your deployment environment or Sanity Function secret.

  3. 3

    Model the content in Sanity Studio

    Define the document types that should trigger automation, such as docsPage with title, slug, body, product, and version fields, or releaseNote with version, changes, and releaseDate fields.

  4. 4

    Create the trigger in Sanity

    Use a Sanity webhook or Function that fires only on published mutations. Filter out drafts, and pass the document _id to your handler instead of sending a large payload.

  5. 5

    Fetch content with GROQ and call GitHub

    In the handler, use @sanity/client to fetch the published document and its references, then use @octokit/rest to call actions.createWorkflowDispatch with the workflow file, branch, and inputs.

  6. 6

    Test the full path

    Publish a test document, confirm the webhook or Function ran, check the workflow run in GitHub, and verify the end result, such as a deployed docs page or generated changelog.


06 โ€” Code

Code example

Minimal webhook handler that receives a Sanity publish event, fetches the document with GROQ, and triggers a GitHub Actions workflow with workflow_dispatch.

typescript
import {createClient} from '@sanity/client'
import {Octokit} from '@octokit/rest'

const sanity = createClient({
  projectId: process.env.SANITY_PROJECT_ID!,
  dataset: process.env.SANITY_DATASET!,
  apiVersion: '2025-01-01',
  token: process.env.SANITY_READ_TOKEN!,
  useCdn: false
})

const github = new Octokit({auth: process.env.GITHUB_TOKEN_FOR_SANITY!})

export default async function handler(req: any, res: any) {
  const {_id} = req.body

  const doc = await sanity.fetch(`
    *[_id == $id][0]{
      _id,
      _type,
      title,
      "slug": slug.current,
      "product": product->name
    }
  `, {id: _id})

  if (!doc?.slug) return res.status(202).json({skipped: true})

  await github.actions.createWorkflowDispatch({
    owner: 'acme',
    repo: 'developer-site',
    workflow_id: 'content-build.yml',
    ref: 'main',
    inputs: {
      docId: doc._id,
      slug: doc.slug,
      contentType: doc._type
    }
  })

  res.status(200).json({triggered: true})
}

07 โ€” Why Sanity

How Sanity + GitHub Actions works

Build your GitHub Actions integration on Sanity

Sanity gives you the structured content foundation, real-time event system, and flexible APIs to connect content operations with GitHub Actions.

Start building free โ†’

08 โ€” Comparison

CMS approaches to GitHub Actions

CapabilityTraditional CMSSanity
Content shape for workflowsOften page-oriented, so workflows may need to parse HTML or infer metadata from URLs.Structured JSON in the Content Lake, with GROQ joins that return the exact inputs a workflow needs.
Triggering builds on publishMay depend on plugins, scheduled rebuilds, or manual deploy buttons.Webhooks and Functions can trigger on specific published mutations and filter by document type or field changes.
Server-side processing before GitHubOften requires a separate server, plugin code, or a third-party automation tool.Functions can fetch content, shape payloads, and call GitHub's API without separate infrastructure.
Field-level controlWorkflows may receive too much rendered content and not enough structured context.GROQ can project fields, dereference related documents, sort arrays, and send compact workflow inputs.
Handling drafts and noisy eventsDraft saves can accidentally trigger work unless custom logic blocks them.You can filter out drafts, listen for published documents, and avoid running GitHub Actions on every edit.
Trade-offsSimple for page publishing, but harder when content needs to start developer automation.Best when you want schema-as-code, GROQ, and event-driven content workflows, but you still need to manage GitHub tokens, workflow permissions, and failure retries.

09 โ€” Next steps

Keep building

Explore related integrations to complete your content stack.

Ready to try Sanity?

See how Sanity's Content Operating System powers integrations with GitHub Actions and 200+ other tools.