Marketing & Advertising8 min read

How to Integrate Meta Ads with Your Headless CMS

Connect Meta Ads to structured content so campaign creatives, landing page copy, product messages, and launch dates stay aligned without copy-pasting fields into Ads Manager.

Published April 29, 2026
01 โ€” Overview

What is Meta Ads?

Meta Ads is Meta's advertising platform for running paid campaigns across Facebook, Instagram, Messenger, and Audience Network. Teams use it to create campaigns, ad sets, ads, custom audiences, and product catalog promotions through Ads Manager or the Meta Marketing API. It's one of the main paid social channels for ecommerce, B2B, media, and app growth teams.


02 โ€” The case for integration

Why integrate Meta Ads with a headless CMS?

Paid social breaks down fast when campaign content lives in one place and ads are built by hand somewhere else. A product marketer updates a launch headline in your site content, but the Meta ad still uses last week's positioning. A regional team translates a landing page, but the ad copy stays in English. Someone changes a promo end date, but three active ad sets keep sending traffic to a stale offer.

Connecting Meta Ads to a headless CMS solves the copy drift problem by treating ad-ready fields as structured content. In Sanity, that content sits in the Content Lake as typed JSON, not as page blobs. You can model campaign names, primary text, headlines, descriptions, CTA types, landing page URLs, image assets, locales, UTM parameters, approval status, and publish windows in Sanity Studio, then send exactly those fields to Meta's Marketing API when content is published.

The manual alternative is screenshots, spreadsheets, and repeated entry in Ads Manager. That can work for five ads a month. It gets messy when you're running 40 localized variants, seasonal promotions, or product launches with legal approval steps. A real-time webhook or Sanity Function gives you a safer path: publish approved campaign content once, create or pause Meta ads automatically, and keep your site, mobile app, emails, AI agents, and paid social fed from the same structured source.


03 โ€” Architecture

Architecture overview

A typical flow starts with an editor publishing a campaign, promotion, or product launch document in Sanity Studio. A Sanity webhook fires on the publish event, or a Sanity Function runs directly on the mutation. The handler receives the document ID, uses @sanity/client and GROQ to fetch the exact fields Meta Ads needs, including joined references such as campaign image, locale, landing page slug, offer date, and CTA label. The server-side handler then calls the Meta Marketing API through the facebook-nodejs-business-sdk. It can create an AdCreative with object_story_spec for a Facebook Page post, attach a link, image URL, headline, primary text, description, and CTA, then create an Ad in an existing ad set with status set to PAUSED for review. If your workflow allows auto-activation, the handler can set ACTIVE, but many teams keep PAUSED to let media buyers review budgets, targeting, placements, and attribution settings in Ads Manager. The end user never sees the integration layer. They see the ad in Facebook or Instagram, click through to the landing page, and arrive at content powered by the same structured source. One trade-off: Meta ad creatives are commonly treated as versioned objects. If headline or image content changes after launch, your sync may need to create a new creative and update the ad, rather than editing every field in place.


04 โ€” Use cases

Common use cases

๐Ÿ“ฃ

Launch campaign creative sync

Publish an approved campaign in Sanity and create a matching Meta ad creative with primary text, headline, CTA, image, and landing page URL.

๐ŸŒŽ

Localized paid social variants

Generate ad creatives per locale, such as en-US, fr-FR, and de-DE, using translated campaign fields and region-specific landing pages.

๐Ÿ›๏ธ

Promotion and product ads

Send offer copy, product images, sale dates, and UTM-tagged URLs from structured product or promotion content into Meta campaigns.

๐Ÿงช

A/B creative testing

Model multiple headline and primary text variants in Sanity, then create separate Meta ad creatives for testing in the same ad set.


05 โ€” Implementation

Step-by-step integration

  1. 1

    Set up Meta Ads access

    Create or use a Meta Business Manager account, connect an ad account, and create a Meta app at developers.facebook.com. Add the Marketing API product, generate a system user access token, and grant permissions such as ads_management and ads_read. Install the Node SDK with npm install facebook-nodejs-business-sdk.

  2. 2

    Model ad-ready content in Sanity Studio

    Create a campaign schema with fields like name, primaryText, headline, description, ctaType, landingPage, image, locale, utmCampaign, metaAdSetId, and approvalStatus. Keep budget, targeting, and billing controls in Meta unless your team has a strong reason to model them in Sanity.

  3. 3

    Write a GROQ query for Meta fields

    Query only the content needed by Meta Ads. For example, fetch the campaign name, translated copy, image asset URL, landing page URL, CTA type, and referenced ad set ID. This avoids sending draft notes, internal comments, or fields that Meta doesn't accept.

  4. 4

    Create the sync mechanism

    Use a Sanity Function for server-side logic triggered by publish events, or use a Sanity webhook that posts to your own API route. Filter the trigger to campaign documents where approvalStatus == "approved" so drafts and review copies don't create ads.

  5. 5

    Call the Meta Marketing API

    Initialize facebook-nodejs-business-sdk with your access token, create an AdCreative on the ad account, then create an Ad in the selected ad set. Start with PAUSED status so your media team can review placements, budgets, and learning phase impact before activation.

  6. 6

    Test the full path

    Use a Meta test ad account or a paused campaign first. Publish a test document in Sanity, confirm the webhook payload, inspect the created creative in Ads Manager, click the destination URL, and verify UTM parameters in your analytics tool.


06 โ€” Code

Code example

typescriptmeta-ads-sync.ts
import {createClient} from '@sanity/client'
import bizSdk from 'facebook-nodejs-business-sdk'

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,
})

bizSdk.FacebookAdsApi.init(process.env.META_ACCESS_TOKEN!)
const AdAccount = bizSdk.AdAccount

export async function POST(req: Request) {
  const payload = await req.json()
  const id = payload.ids?.created?.[0] || payload.ids?.updated?.[0]
  if (!id) return Response.json({ok: true})

  const campaign = await sanity.fetch(`
    *[_id == $id && _type == "adCampaign"][0]{
      name,
      primaryText,
      headline,
      description,
      ctaType,
      "link": landingPage->url,
      "imageUrl": image.asset->url,
      metaAdSetId
    }
  `, {id})

  if (!campaign?.metaAdSetId) {
    return Response.json({ok: false, reason: 'Missing Meta ad set ID'}, {status: 400})
  }

  const account = new AdAccount(`act_${process.env.META_AD_ACCOUNT_ID}`)

  const creative = await account.createAdCreative([], {
    name: `${campaign.name} creative`,
    object_story_spec: {
      page_id: process.env.META_PAGE_ID,
      link_data: {
        message: campaign.primaryText,
        link: campaign.link,
        name: campaign.headline,
        description: campaign.description,
        picture: campaign.imageUrl,
        call_to_action: {
          type: campaign.ctaType || 'LEARN_MORE',
          value: {link: campaign.link},
        },
      },
    },
  })

  const ad = await account.createAd([], {
    name: campaign.name,
    adset_id: campaign.metaAdSetId,
    creative: {creative_id: creative.id},
    status: 'PAUSED',
  })

  return Response.json({ok: true, metaAdId: ad.id})
}

07 โ€” Why Sanity

How Sanity + Meta Ads works

Build your Meta Ads integration on Sanity

Sanity gives you the structured content foundation, real-time event system, and flexible APIs to connect approved campaign content with Meta Ads.

Start building free โ†’

08 โ€” Comparison

CMS approaches to Meta Ads

CapabilityTraditional CMSSanity
Ad-ready content structureAd copy often lives inside pages, modules, or rich text, so teams copy fields into Ads Manager by hand.Schema-as-code lets you model primary text, headline, CTA, image, locale, offer dates, and Meta ad set IDs as typed fields.
Sync timingSync usually depends on manual exports, scheduled jobs, or someone remembering to update ads after publishing.Webhooks and Functions can trigger on publish events, fetch the document with GROQ, and call the Meta Marketing API without polling.
Field-level control for Meta API payloadsTeams may need custom parsing to separate page content from ad-specific copy, URLs, and images.GROQ can filter, project, and join the exact payload Meta needs in one query, including referenced assets and landing pages.
Localization for paid socialLocalized ad variants are often tracked in spreadsheets, which increases the chance of wrong-region copy or URLs.Locale-specific schemas and GROQ filters can create one Meta creative per market, each with the right copy, image, and destination.
Editorial and media team handoffEditors publish site content, then media buyers manually rebuild the campaign in Ads Manager.Sanity Studio can include approval fields, comments, tasks, and release timing, while Meta ads can still be created as PAUSED for media buyer review.
Multi-channel campaign reuseContent is often tied to website pages, so paid social, email, and app teams duplicate campaign copy.One structured back end can feed web, mobile, Meta Ads, email tools, and AI agents through APIs, Functions, and Agent Context.

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