Communication & Collaboration8 min read

How to Integrate Discord with Your Headless CMS

Send publish-ready content from your headless CMS to Discord channels the moment it goes live, with structured fields mapped to messages, embeds, and bot workflows.

Published April 29, 2026
01 β€” Overview

What is Discord?

Discord is a communication platform organized around servers, channels, direct messages, voice, video, bots, slash commands, and webhooks. It’s widely used by gaming communities, open-source projects, developer relations teams, customer communities, and internal teams that need fast group communication. Discord apps connect through the Discord API, Gateway API, interactions, incoming webhooks, and SDKs like discord.js.


02 β€” The case for integration

Why integrate Discord with a headless CMS?

If your launch notes, changelog entries, event pages, or community updates live in one system while your audience lives in Discord, someone usually ends up copy-pasting. That creates small but real problems: the wrong version gets posted, links are missing, channel owners forget updates, and moderators spend time answering questions that were already covered in published content.


03 β€” Architecture

Architecture overview

A typical Discord integration starts when an editor publishes content in Sanity Studio. That write updates the structured document in the Content Lake. A GROQ-powered webhook filters for the exact event you care about, for example published documents where _type == "releaseNote" and notifyDiscord == true. The webhook can call your own endpoint, or a Sanity Function can run the server-side sync logic without a separate worker. The handler receives the mutation payload, uses @sanity/client and GROQ to fetch only the fields Discord needs, maps those fields into a Discord message or embed, and calls Discord’s REST API with a bot token. Discord then posts the message to the configured channel, where end users see it like any other server update. For simple one-way posts, an incoming Discord webhook can work. For channel routing, embeds, bot identity, retries, slash commands, or future edits, a Discord bot using discord.js is usually the better fit.


04 β€” Use cases

Common use cases

πŸ“£

Publish announcements to Discord

Post product launches, blog posts, or changelog entries to #announcements with the title, summary, author, and canonical URL pulled from Sanity.

πŸ§ͺ

Notify beta communities

Route beta release notes to a private Discord channel based on a Sanity field like audience: "beta-testers".

πŸ›Ÿ

Keep support teams current

Send docs updates to #support so moderators know when troubleshooting steps, pricing rules, or API limits change.

πŸ€–

Power Discord bot answers

Let a Discord bot answer slash commands by querying structured content, such as FAQs, event details, or product docs.


05 β€” Implementation

Step-by-step integration

  1. 1

    Create a Discord app and bot

    In the Discord Developer Portal, create an application, add a bot, copy the bot token, and invite it to your server with the bot scope. For posting embeds, grant View Channels, Send Messages, and Embed Links. Install the SDK with npm install discord.js @sanity/client.

  2. 2

    Model Discord-ready content in Sanity Studio

    Add fields that map cleanly to Discord, such as title, slug, excerpt, author, audience, discordChannelId, notifyDiscord, and publishedAt. Keep long body content in Portable Text, but send a short excerpt to Discord because plain messages have a 2,000 character limit.

  3. 3

    Create a publish trigger

    Use a GROQ-powered webhook or a Sanity Function. A useful webhook filter is _type == "post" && !(_id in path("drafts.**")) && notifyDiscord == true. Send a small projection, such as {"_id": _id}, then fetch the full message payload server-side.

  4. 4

    Map Sanity fields to Discord messages

    In your handler, fetch the published document with GROQ, build a Discord embed, and call Discord’s REST API with the bot token. Watch Discord limits: 10 embeds per message, 6,000 total embed characters, and rate limits returned as HTTP 429.

  5. 5

    Test in a private channel first

    Publish a test document, confirm the webhook fires once, check that links point to production URLs, and log Discord response IDs. If duplicate posts matter, write the Discord message ID back to Sanity or keep an idempotency record in your own system.

  6. 6

    Build the frontend experience from the same content

    Use the same structured document for your website, mobile app, and Discord update. For example, the Discord post can link to a Next.js route that renders the full article from the Content Lake with GROQ.


06 β€” Code

Code example

typescriptapp/api/sanity-to-discord/route.ts
import {createClient} from '@sanity/client'
import {REST, Routes} from 'discord.js'

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 discord = new REST({version: '10'}).setToken(
  process.env.DISCORD_BOT_TOKEN!
)

export async function POST(req: Request) {
  const {_id} = await req.json()

  const post = await sanity.fetch(
    `*[_id == $id][0]{
      title,
      excerpt,
      "url": "https://example.com/posts/" + slug.current,
      "author": author->name
    }`,
    {id: _id}
  )

  if (!post) return Response.json({ok: false}, {status: 404})

  await discord.post(
    Routes.channelMessages(process.env.DISCORD_CHANNEL_ID!),
    {
      body: {
        content: `New post: **${post.title}**`,
        embeds: [{
          title: post.title,
          description: post.excerpt,
          url: post.url,
          fields: [{name: 'Author', value: post.author || 'Editorial'}]
        }]
      }
    }
  )

  return Response.json({ok: true})
}

07 β€” Why Sanity

How Sanity + Discord works

Build your Discord integration on Sanity

Sanity gives you the structured content foundation, real-time event system, and flexible APIs to connect Discord to your AI content operations.

Start building free β†’

08 β€” Comparison

CMS approaches to Discord

CapabilityTraditional CMSSanity
Discord announcement payloadsContent is often tied to pages or HTML output, so Discord messages need cleanup before posting.Content Lake documents are typed JSON, and GROQ can return a Discord-ready payload with title, excerpt, URL, and referenced author data.
Publish-time notificationsTeams often rely on plugins, cron jobs, or manual copy-paste into Discord channels.GROQ-powered webhooks or Functions can trigger only on the content types and fields that should notify Discord.
Channel routingRouting usually lives outside the content model, so editors need a separate checklist for where to post.Sanity Studio can model fields like audience, discordChannelId, and notifyDiscord, with custom validation before publishing.
Bot-powered content lookupDiscord bots often search public pages, which can return outdated or poorly structured answers.Bots can query structured content with GROQ, or production AI agents can use Agent Context for scoped, read-only access.
Sync logic and operationsCustom servers are usually needed for retries, formatting, and Discord API calls.Functions can run server-side sync logic on content events. For very high-volume Discord bots, you may still want a dedicated queue.

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 Discord and 200+ other tools.