Multi-Language Templates
This feature is only available in paid plans. Learn More
Multi-Language lets a single template hold content in multiple languages. Users switch languages in the editor and translate text, images, and other elements without duplicating the template.

Multi-Language controls the languages of your template content. To translate the editor interface itself (panels, buttons, tool names), see Localization.
Plan limits
Each plan includes a number of languages per editor instance, meaning the list you configure with features.multiLanguage.languages or setLanguages. The default language counts toward the limit:
| Plan | Languages per editor |
|---|---|
| Free | Not available |
| Launch | Not available |
| Scale | 2 |
| Optimize | 5 |
| Enterprise | Unlimited |
The limit is enforced by Unlayer's servers from your project's plan, so the value you pass to unlayer.init cannot raise it. When you configure more languages than the plan includes:
- The default language is always kept, then the remaining slots go to the other languages in the order you configured them.
- Languages beyond the limit stay visible in the language selector but are greyed out, with a tooltip naming the limit. They cannot be selected in the editor, through
setCurrentLanguage, or as thelanguageof a client-side export. - Content already stored for those languages is preserved in the design JSON — nothing is deleted — so it becomes editable again if the plan is upgraded.
- The editor's export methods (
exportHtml,exportImage,exportPdf,exportZip) fall back to the current language when asked for one beyond the limit, and log a console warning. A direct Cloud API export of such a language is rejected with a403error that names the limit.

Enable
Disabled by default. Enable it with features.multiLanguage — pass true, or an object with enabled: true plus the available languages (the object form does not enable the feature on its own):
unlayer.init({
features: {
multiLanguage: {
enabled: true,
languages: [
{ label: 'English', value: 'en_US', default: true },
{ label: 'Spanish', value: 'es_ES' },
{ label: 'Arabic', value: 'ar_AE', rtl: true },
],
},
},
});
Each language object supports:
| Property | Type | Required | Description |
|---|---|---|---|
value | string | Yes | Unique identifier for the language, e.g. en_US, pt_BR |
label | string | No | Display name shown in the language selector. Can be omitted if using translations. |
default | boolean | No | Marks the default language. Set it on exactly one language. |
rtl | boolean | No | Applies direction: rtl to content when this language is selected. |
If no language has default: true, the first language in the array is only the initially selected one — no language becomes the default. Edits in every language are then stored as per-language overrides, so untranslated content falls back to each tool's built-in default value instead of the first language's content.
Switching languages
When enabled, a language selector appears in three places: the editor toolbar, the Body settings tab, and the preview modal header. Switching re-renders the canvas with that language's content:

You can also drive it programmatically after init:
// Replace the available languages
unlayer.setLanguages([
{ label: 'English', value: 'en_US', default: true },
{ label: 'Portuguese', value: 'pt_BR' },
]);
// Switch the active language
unlayer.setCurrentLanguage('pt_BR');
Translating language labels
To make the language dropdown follow the user's locale, define keys via the translations API instead of hardcoding label. For each language the editor checks, in order:
languages.{value}— thevaluelowercased (languages.en_usforen_US)- The
labelas an exact key, orlabels.{label}in snake_case (labels.english_usforEnglish (US)) — both skipped when the language has nolabel - Falls back to
labelif set, then tovalue
unlayer.init({
features: {
multiLanguage: true,
},
translations: {
en: { 'languages.en_us': 'English', 'languages.pt_br': 'Portuguese' },
'pt-BR': { 'languages.en_us': 'Inglês', 'languages.pt_br': 'Português' },
},
});
unlayer.setLanguages([{ value: 'en_US' }, { value: 'pt_BR' }]);
Translation keys are always lowercase, even when the language value contains uppercase characters: for en_US, the key is languages.en_us.
Data structure
Content edited in the default language is stored at the root of the element's values. Other languages store only their overrides in a _languages object; anything not translated falls back to the default language:
{
"text": "Hello World",
"_languages": {
"es_ES": { "text": "Hola Mundo" },
"fr_FR": { "text": "Bonjour le Monde" }
}
}
Multi-Language composes with device overrides: the root _languages translates the base (desktop) values, and each device override carries its own _languages inside _override — so e.g. a background image can vary by device and language:
{
"backgroundImage": { "url": "https://example.com/pt_desktop.jpg" },
"_languages": {
"en_US": {
"backgroundImage": { "url": "https://example.com/en_desktop.jpg" }
}
},
"_override": {
"mobile": {
"backgroundImage": { "url": "https://example.com/pt_mobile.jpg" },
"_languages": {
"en_US": {
"backgroundImage": { "url": "https://example.com/en_mobile.jpg" }
}
}
}
}
}
Supported content types
| Content Type | Translatable Properties |
|---|---|
| Text | Text content, font family |
| Paragraph | Text content, font family |
| Heading | Heading text |
| Button | Button text |
| Image | Source image, alt text |
| Menu | Menu items (text and links) |
| Table | Cell content; header, content, and footer font families |
| Form | Field, label, and button font families |
| Timer | Countdown settings (labels, locale, fonts), alt text |
| Row Background | Background images (for localized visuals) |
Font families being per-language lets you pick a script-appropriate font for each language (e.g. Arabic, Hebrew, or CJK content).
Exporting in a specific language
Pass language to any export method — exportHtml, exportPlainText, exportImage, exportPdf, exportZip (see Export HTML for all options). Element dimensions (e.g. button width) are recalculated for the specified language's content:
unlayer.exportHtml(
function (data) {
var html = data.html;
},
{ language: 'es_ES' },
);
If omitted, the export uses the language currently selected in the editor (initially the default language).
Server-side exports also accept languages — the list of language codes configured for the template. The editor sends it automatically. If you call the Cloud API directly with a language and no languages list, the languages the template JSON carries content in (every _languages code — the editor stores default-language content edited with multi-language enabled there too) plus the requested one must fit within your plan's limit, otherwise the request is rejected with a 403 asking you to send languages. Send languages whenever you build the JSON yourself: it is the only way to tell the API which languages, default included, the export should count. Only the first N entries of languages (where N is your plan limit) can be rendered.