Translation & Localization8 min read

How to Integrate POEditor with Your Headless CMS

Send structured strings from your headless CMS to POEditor on publish, translate them by locale, and serve the right copy to every market without spreadsheet handoffs.

Published April 29, 2026
01Overview

What is POEditor?

POEditor is a translation management platform for software, websites, apps, and content teams that need to localize strings across multiple languages. It supports projects, terms, translations, contributors, translation memory, API access, Git integrations, and file formats like JSON, XLIFF, PO, CSV, and Android XML. Teams use it when translators, developers, and editors need one place to track source strings, translation status, and locale-specific copy.


02The case for integration

Why integrate POEditor with a headless CMS?

Localization gets messy when your source content lives in one system, translators work in another, and published pages need locale-ready copy at the same time. Without an integration, someone exports strings, uploads files to POEditor, waits for translations, downloads locale files, and asks a developer to merge them back into the site. That works for 20 strings. It breaks when you have 2,000 product descriptions, legal disclaimers, CTA labels, and help articles changing every week.


03Architecture

Architecture overview

A typical Sanity and POEditor integration starts when an editor publishes source-language content in Sanity Studio. A Sanity webhook fires on the publish mutation, or a Function runs directly on the content event. The handler receives the document ID, uses @sanity/client and GROQ to fetch the latest published fields from the Content Lake, then maps those fields into POEditor terms with stable keys such as article.introduction.title or product.sku123.description. The handler calls POEditor’s /v2/terms/add API with the project ID, API token, and JSON term payload. POEditor then exposes those terms to translators for each configured language. After translation, your return path can be a scheduled pull from POEditor’s export API, a CI job that downloads locale JSON files, or a second webhook-like process that writes approved translations back to Sanity. At runtime, the end user sees localized content from your frontend, which can read translated fields from Sanity or load locale files generated from POEditor.


04Use cases

Common use cases

🌍

Marketing site localization

Send page titles, hero copy, CTAs, and SEO descriptions from Sanity to POEditor whenever the English page is published.

🛍️

Product catalog translation

Create POEditor terms for product names, descriptions, feature bullets, and disclaimers while keeping SKU and category references intact.

📱

App and web string reuse

Use POEditor as the translation workspace for shared UI labels while Sanity structures longer editorial content for web, mobile, and in-product surfaces.

Locale release checks

Compare POEditor completion status with Sanity release plans so teams can hold a market launch until required languages are reviewed.


05Implementation

Step-by-step integration

  1. 1

    Create a POEditor project and API token

    In POEditor, create a project, add your source language, add target languages, and copy your API token from Account settings. Note the project ID from the project page or POEditor API response.

  2. 2

    Install the packages for your sync code

    Use @sanity/client to fetch content with GROQ. For a Node.js webhook handler, Node 18 or later includes fetch and FormData, so you can call POEditor’s REST API directly.

  3. 3

    Model localized content in Sanity Studio

    Add stable fields that can become translation keys, such as title, slug, excerpt, body, and seoTitle. For translated content, choose localized fields, locale-specific documents, or a separate translation document type before you start syncing production content.

  4. 4

    Create the publish trigger

    Add a Sanity webhook filtered to published documents, or use a Function for server-side sync on content mutations. Include the document ID and type in the payload so your handler can fetch the current published version.

  5. 5

    Map Sanity fields to POEditor terms

    Use GROQ to select only translatable fields, then convert each field into a POEditor term with a stable key, optional context, and reference. Call POEditor’s /v2/terms/add endpoint with token, project ID, and JSON data.

  6. 6

    Test the full localization loop

    Publish one test document, confirm the terms appear in POEditor, add a translation, export the target locale from POEditor, and render the translated content in your frontend. Test updates too, because changed source strings need a clear review policy.


06Code

Code example

typescriptapi/sync-poeditor.ts
import {createClient} from '@sanity/client'

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 {documentId} = await req.json()

  const doc = await sanity.fetch(`
    *[_id == $id][0]{
      _type,
      title,
      excerpt,
      "slug": slug.current,
      seoTitle
    }
  `, {id: documentId.replace('drafts.', '')})

  const terms = [
    ['title', doc.title],
    ['excerpt', doc.excerpt],
    ['seoTitle', doc.seoTitle]
  ].filter(([, value]) => typeof value === 'string' && value.length)
   .map(([field, value]) => ({
     term: `${doc._type}.${doc.slug}.${field}`,
     definition: value,
     context: `Sanity ${doc._type} field: ${field}`,
     reference: documentId
   }))

  const body = new FormData()
  body.set('api_token', process.env.POEDITOR_API_TOKEN!)
  body.set('id', process.env.POEDITOR_PROJECT_ID!)
  body.set('data', JSON.stringify(terms))

  const res = await fetch('https://api.poeditor.com/v2/terms/add', {
    method: 'POST',
    body
  })

  if (!res.ok) throw new Error(await res.text())
  return Response.json({synced: terms.length})
}

07Why Sanity

How Sanity + POEditor works

Build your POEditor integration on Sanity

Use Sanity’s structured content foundation, real-time event system, and flexible APIs to send clean source strings to POEditor and publish localized experiences faster.

Start building free →

08Comparison

CMS approaches to POEditor

CapabilityTraditional CMSSanity
Translation source extractionOften exports rendered pages or plugin-specific fields, so teams may clean up HTML before sending strings to POEditor.Content Lake structures typed JSON, and GROQ selects exact fields for POEditor, including referenced content.
Sync timingTranslation exports often happen manually or through scheduled plugin jobs.Webhooks or Functions can send source strings to POEditor on publish without polling.
Field-level controlTeams may translate full pages even when only one CTA or metadata field changed.GROQ can project only title, excerpt, Portable Text blocks, SEO fields, and referenced labels in one query.
Editorial preview by localePreview depends on localization plugins and may not match the final frontend.Sanity Studio, Presentation Tool, and structured locale fields can support click-to-edit previews for translated pages.
Translation return pathPOEditor translations usually return through imports that can overwrite fields if mappings drift.You can write translations into localized fields, locale documents, or release-specific drafts, but you’ll need to define the governance model.
Multi-channel deliveryLocalized content is usually shaped around website pages first.One structured back end can feed web, mobile, POEditor, AI agents, and other channels from the same content model.

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