Content Localization in Payload CMS
Payload supports multilingual content at the field level: each field marked as localized stores separate values for each locale. In PostgreSQL this is a column with JSONB object {ru: "...", en: "...", uk: "..."}.
Locale Configuration
// payload.config.ts
export default buildConfig({
localization: {
locales: [
{ label: 'Русский', code: 'ru' },
{ label: 'English', code: 'en' },
{ label: 'Українська', code: 'uk' },
],
defaultLocale: 'ru',
fallback: true, // if no translation exists — use default locale
},
})
Localized Fields
// collections/Posts.ts
fields: [
{
name: 'title',
type: 'text',
localized: true, // separate value per locale
required: true,
},
{
name: 'content',
type: 'richText',
localized: true,
},
{
name: 'slug',
type: 'text',
localized: true, // slug can be localized too
unique: true,
},
{
name: 'featuredImage',
type: 'upload',
relationTo: 'media',
// NOT localized — same image for all locales
},
]
Queries with Locale
// Get posts in Russian
const posts = await payload.find({
collection: 'posts',
locale: 'ru',
})
// Get post in all languages at once
const allLocales = await payload.findByID({
collection: 'posts',
id: postId,
locale: 'all',
})
// allLocales.title = { ru: '...', en: '...', uk: '...' }
// REST API:
// GET /api/posts?locale=en
// GET /api/posts/123?locale=all
Integration with Next.js i18n
// app/[locale]/posts/[slug]/page.tsx
import { getPayload } from 'payload'
import { notFound } from 'next/navigation'
type Locale = 'ru' | 'en' | 'uk'
export default async function PostPage({
params,
}: {
params: { locale: Locale; slug: string }
}) {
const payload = await getPayload({ config })
const result = await payload.find({
collection: 'posts',
locale: params.locale,
where: {
and: [
{ slug: { equals: params.slug } },
{ _status: { equals: 'published' } },
],
},
})
if (!result.docs[0]) notFound()
return <PostPage post={result.docs[0]} />
}
export async function generateStaticParams() {
const payload = await getPayload({ config })
const locales: Locale[] = ['ru', 'en', 'uk']
const params: { locale: Locale; slug: string }[] = []
for (const locale of locales) {
const posts = await payload.find({ collection: 'posts', locale, limit: 1000 })
posts.docs.forEach(post => {
if (post.slug) params.push({ locale, slug: post.slug as string })
})
}
return params
}
Timeline
Setting up localization for 3 languages with existing collections adaptation and Next.js routing — 1–2 days.







