CRM & Sales8 min read

How to Integrate Salesforce with Your Headless CMS

Connect Salesforce to structured content so sales teams, campaigns, product pages, and customer-facing experiences stay in sync without copy-paste work.

Published April 29, 2026
01Overview

What is Salesforce?

Salesforce is a CRM platform used by sales, service, marketing, and operations teams to track accounts, contacts, leads, opportunities, campaigns, and customer activity. Its core strength is keeping customer and revenue data in one place, with APIs that let teams connect CRM records to websites, apps, sales workflows, and reporting tools. It’s one of the most widely used CRM platforms for companies that need configurable sales processes, automation, and integrations across large teams.


02The case for integration

Why integrate Salesforce with a headless CMS?

Salesforce often knows who the customer is, what stage they’re in, which products they care about, and which campaign touched them last. Your content system usually knows the approved product copy, campaign messaging, sales enablement assets, localized descriptions, and publish status. When those two systems don’t talk, sales reps paste outdated copy into opportunities, campaign teams re-enter launch details, and customer experiences drift away from what’s approved.


03Architecture

Architecture overview

A typical Salesforce integration starts when an editor publishes content in Sanity Studio. A webhook fires on the publish mutation, or a Sanity Function runs server-side on the same content event. The handler receives the document ID, uses @sanity/client to run a GROQ query against the Content Lake, and projects only the fields Salesforce needs, such as title, slug, campaign code, audience, product SKU, publish URL, and Salesforce external ID. The handler then authenticates to Salesforce with a Connected App and calls the Salesforce REST API through jsforce. For one record, you might upsert a Campaign, Product2, or custom object using an external ID field like Sanity_Id__c. For larger backfills, Bulk API 2.0 is a better fit. After Salesforce is updated, sales reps see current content metadata on the related CRM record, marketers can report on content-linked campaigns, and your frontend can still read the same approved content directly from Sanity for web, mobile, and AI agent experiences.


04Use cases

Common use cases

🎯

Campaign sync

Create or update Salesforce Campaign records when launch pages, gated assets, or nurture content are published in Sanity.

📦

Product content for Sales Cloud

Sync approved product names, SKUs, descriptions, and launch status from Sanity to Salesforce Product2 or custom product objects.

🧾

Sales enablement on opportunities

Attach current case studies, one-pagers, and pitch assets to Salesforce accounts or opportunities based on industry, region, or product interest.

📊

Content-attributed pipeline reporting

Send content IDs, campaign codes, and asset URLs into Salesforce so teams can report on which content is tied to leads, contacts, and opportunities.


05Implementation

Step-by-step integration

  1. 1

    Set up Salesforce API access

    Create a Salesforce Connected App in Setup, enable OAuth settings, and add scopes such as api and refresh_token, offline_access. Copy the Consumer Key and Consumer Secret. For server-side scripts, use OAuth refresh tokens, JWT bearer flow, or username plus security token depending on your company’s security policy.

  2. 2

    Create Salesforce fields for Sanity IDs

    Add an external ID field such as Sanity_Id__c to the target Salesforce object, for example Campaign, Product2, or a custom Content_Asset__c object. This lets your integration upsert records instead of creating duplicates on every publish.

  3. 3

    Model the content in Sanity Studio

    Define schema fields that match the CRM use case. For a campaign, include title, slug, campaignCode, audience, launchDate, status, heroAsset, relatedProducts, and salesforceCampaignId or Sanity_Id__c. Keep CRM-owned fields, such as pipeline stage or campaign member count, out of Sanity.

  4. 4

    Create the sync trigger

    Use a Sanity webhook filtered to published campaign, product, or asset documents, or use a Sanity Function to run server-side logic on content mutations. Configure the trigger to send the document ID and dataset so your handler can fetch the latest published document.

  5. 5

    Call the Salesforce API

    Install jsforce with npm install jsforce @sanity/client. In the handler, authenticate to Salesforce, fetch the Sanity document with GROQ, map fields to the Salesforce object, and call upsert with the external ID field. Use Composite API when one publish needs to update related records, and use Bulk API 2.0 for large migrations.

  6. 6

    Test the end-to-end experience

    Publish a draft in Sanity Studio, confirm the webhook or Function ran, inspect the Salesforce record, and verify that the frontend still renders from Sanity. Test updates, deletes, unpublished documents, required Salesforce fields, and permission failures before giving the workflow to editors.


06Code

Code example

Minimal TypeScript webhook handler that receives a Sanity publish event, fetches the document with GROQ, and upserts a Salesforce Campaign with jsforce.

typescript
import {createClient} from '@sanity/client'
import {Connection} from 'jsforce'

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

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

  const campaign = await sanity.fetch(`
    *[_id == $id][0]{
      _id,
      title,
      "url": "https://example.com/campaigns/" + slug.current,
      campaignCode,
      launchDate,
      audience
    }
  `, {id})

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

  const sf = new Connection({loginUrl: process.env.SF_LOGIN_URL})
  await sf.login(
    process.env.SF_USERNAME!,
    process.env.SF_PASSWORD! + process.env.SF_SECURITY_TOKEN!
  )

  const result = await sf.sobject('Campaign').upsert({
    Sanity_Id__c: campaign._id,
    Name: campaign.title,
    Campaign_Code__c: campaign.campaignCode,
    Landing_Page_URL__c: campaign.url,
    Audience__c: campaign.audience,
    StartDate: campaign.launchDate,
    Status: 'In Progress'
  }, 'Sanity_Id__c')

  return Response.json({ok: result.success, id: result.id})
}

07Why Sanity

How Sanity + Salesforce works

Build your Salesforce integration on Sanity

Sanity gives you the structured content foundation, real-time event system, and flexible APIs to connect approved content with Salesforce records and sales workflows.

Start building free →

08Comparison

CMS approaches to Salesforce

CapabilityTraditional CMSSanity
CRM-ready content structureContent is often page-shaped, so Salesforce syncs may need HTML parsing, manual field mapping, or extra cleanup.Content is typed JSON in the Content Lake, with schema-as-code for fields like campaignCode, SKU, audience, region, and Salesforce external ID.
Real-time Salesforce updatesTeams often rely on scheduled exports, plugins, or manual updates after publish.Webhooks trigger on content mutations, and Functions can run sync logic without separate infrastructure.
Field-level control for Salesforce payloadsIntegrations may receive full pages or plugin-defined payloads, which adds mapping work.GROQ can select exactly the fields Salesforce needs and join referenced products, industries, assets, and locales in one query.
Sales campaign operationsCampaign content and Salesforce Campaign records are often updated in separate workflows.Editors can publish campaign content in Sanity Studio, then a webhook or Function can upsert the matching Salesforce Campaign using Sanity_Id__c.
Multi-channel content reuseContent is commonly tied to web pages, which makes reuse in Salesforce, mobile apps, and AI agents harder.One structured back end can feed websites, apps, Salesforce, and AI agents through APIs, GROQ, and Agent Context.
Operational trade-offsLower setup effort if you only need a plugin and can accept limited data control.Best when you want schema control, real-time events, and custom workflows. You’ll still need to design ownership rules between Sanity and Salesforce.

09Next 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 Salesforce and 200+ other tools.