A plugin for Sanity Studio v3 that integrates with the TranslationOS API to translate Sanity documents.
This plugin requires either the Sanity document internationalization plugin or the Sanity field-level internationalization plugin.
The TranslationOS plugin for Sanity offers two independent translation modes:
- Single document translation: Sends a single document to the TranslationOS API for translation. Supports both document-level and field-level localization.
- Bulk document translation: Sends multiple documents to the TranslationOS API for translation. Supports document-level localization only.
Single document translation is available within the Structure tool. Bulk document translation is available as a top-level tool.
Add the plugin to your Sanity Studio project as a dependency by running:
npm install sanity-plugin-tosAdd the plugin to your sanity.config.ts (or .js).
Below is an example configuration using both modes, sharing languages and document types with the internationalization plugins, and defining supported field types.
We recommended using TranslationOS-supported language codes, but we can unofficially support other language codes by configuring a custom mapping.
import {defineConfig} from 'sanity'
import {StructureBuilder} from "sanity/structure";
import {tosPlugin} from 'sanity-plugin-tos'
import {internationalizedArray} from 'sanity-plugin-internationalized-array'
// Languages shared across plugins
const languages = [
{id: 'en-US', title: 'English (US)'},
{id: 'fr-FR', title: 'French (France)'},
{id: 'de-DE', title: 'German (Germany)'},
{id: 'es-ES', title: 'Spanish (Spain)'},
];
// Document types handled by each mode
const documentLevelTypes = ['post', 'author']
const fieldLevelTypes = ['blog']
// Common TranslationOS configuration
const tosCommonConfig = {
supportedLanguages: languages,
customBlockTypes: ['myblock'],
}
export default defineConfig({
// [...]
plugins: [
// [...]
documentInternationalization({
supportedLanguages: languages,
schemaTypes: documentLevelTypes,
weakReferences: true,
}),
structureTool({
defaultDocumentNode: (S: StructureBuilder, {schemaType}: DefaultDocumentNodeContext) => {
if ([...documentLevelTypes, ...fieldLevelTypes].includes(schemaType)) {
return S.document().views([
S.view.form(),
// Structure tool plugin view for single document translation
tosPlugin(S, {
...tosCommonConfig,
// Document types supported in document-level translation mode
documentLocalizationSchemaTypes: documentLevelTypes,
// Document types supported in field-level translation mode
fieldLocalizationSchemaTypes: fieldLevelTypes,
})
])
}
}
}),
internationalizedArray({
languages,
fieldTypes: ['string', 'text', 'block'],
}),
],
// Top-level tool for bulk document translation in document-level translation mode
tools: [
translationOS({
...tosCommonConfig,
schemaTypes: documentLevelTypes,
})
],
})Returns a ComponentViewBuilder to be added to the editor views for specific document types.
Recommended: only enable for schema types configured in your internationalization plugin.
Options:
supportedLanguages– array of{id, title}language objectsdocumentLocalizationSchemaTypes– document types for document-level translationfieldLocalizationSchemaTypes– document types for field-level translationcustomBlockTypes– additional block type names to treat like Sanity'sblock
Note: A schema type can't be included in both the
documentLocalizationSchemaTypesand thefieldLocalizationSchemaTypesoptions, otherwise the plugin will display a configuration error and won't work properly.
Returns a tool to be added to the top-level tools array in your Sanity configuration.
Recommended: only enable for schema types configured in the document internationalization plugin.
Options:
env– TranslationOS API environment (staging,sandboxorproduction)apiKey– TranslationOS API key for the above environmentsupportedLanguages– array of{id, title}language objectscustomBlockTypes– additional block type names to treat like Sanity'sblockschemaTypes– document types to manage with bulk translation
The plugin requires a TranslationOS API key to function. This API key will be stored securely in your Sanity dataset.
When you first use the plugin, by clicking on the TranslationOS editor tab, you will be prompted to enter your API key and select the environment (sandbox or production).
NOTE: the authentication setup can be performed only by a Studio user with admin privileges.
Non-admin users will see a warning message requesting them to contact their Sanity administrator.
If you need to change your API key or environment, you can reset the plugin configuration by clicking on the Reset credentials menu item in the settings menu. The settings menu is accessible by clicking on the cog button near the version badge in the top-right corner of the TranslationOS editor tab.
NOTE: after resetting the authentication configuration, the plugin will display the setup screen again for admin users and the warning message for non-admin users.
For field translation mode:
- Supports common types:
string,text,block - Supports nested
objectfields - Does not traverse
arrayfields to locate nested translatable fields
You can add tosProperties to any field to control translation behavior.
defineField({
name: 'fullname',
type: 'string',
title: 'Full Name',
options: {
tosProperties: {
exclude: true, // exclude from translation
}
}
})Adding tosProperties.exclude: true to an object field excludes all nested fields.
By default, the plugin translates:
- All fields of type
string,text,slug,block - Nested fields within
arrayorobjectvalues (recursively) - Excludes Sanity metadata (
_id,_rev,_type,createdAt, etc.) - Excludes fields with
tosProperties.exclude: true
Sanity's block type represents rich text as an array of objects. The plugin converts these to HTML before sending to TranslationOS, preserving structure and context. Supported features include:
- Bold, italic, underline, strikethrough, code spans
- Headings, blockquotes
- Bullet and numbered lists
- Image references
- Links with metadata (metadata preserved, not translated)
If you define custom block names in your schema, list them in customBlockTypes so the plugin can process them correctly.
Let's say you have the following rich text type definition:
export default defineType({
name: 'myrichtext',
title: 'My Rich Text Content',
type: 'array',
of: [
defineArrayMember({
name: 'myblock', // <--- custom block type name
title: 'My Block',
type: 'block',
styles: [
{title: 'Normal', value: 'normal'},
{title: 'H1', value: 'h1'},
],
marks: {
decorators: [
{title: 'Code', value: 'code'}
],
},
}),
],
})Fields of type myrichtext would have the following JSON structure:
{
"_createdAt": "2025-01-16T13:48:35Z",
"_id": "drafts.cc28b1ec-8340-41ae-a820-3cd3e4f6038c",
"_rev": "b464c3da-548a-4402-a2a0-be29fb61120e",
"_type": "post",
"_updatedAt": "2025-01-17T13:44:05Z",
"body": [
{
"_key": "aba42a503204",
"_type": "myblock", // <--- custom block type name
"children": [
{
"_key": "e8799f8c0f9e",
"_type": "span",
"marks": [],
"text": "The Post Body"
}
],
"markDefs": [],
"style": "h2"
}
]
}You must configure the plugin to correctly handle fields of type myrichtext with myblock elements as follows:
tosPlugin(S, {
// [...]
customBlockTypes: ['myblock'],
})The plugin supports links with arbitrary metadata inside rich text. Metadata is preserved but not translated.
Example:
export default defineType({
name: 'blockContent',
title: 'Block Content',
type: 'array',
of: [
defineArrayMember({
title: 'Block',
type: 'block',
// [...]
marks: {
decorators: [
// [...]
],
annotations: [
{
name: 'link',
title: 'Complex Link',
type: 'object',
fields: [
{
name: 'title',
title: 'Title',
type: 'string',
},
{
name: 'external',
title: 'External Link',
type: 'url',
},
{
name: 'author',
title: 'Author',
type: 'reference',
to: [{type: 'author'}]
},
{
name: 'newTab',
title: 'Open in new tab',
type: 'boolean',
initialValue: false,
description: 'Set to true to open the link in a new tab.',
},
],
},
],
},
}),
],
})If you are migrating from version 4.x or below to version 5.x of the sanity-plugin-tos, please note the following.
In version 5.x, the plugin changes how it manages the TOS API key. Once you update to v5, the process mentioned in the
Authentication section above will have to be followed.
The plugin also requires you to slightly change your previous configuration.
Please follow the steps below:
- Remove the
apiKeyandenvoptions from your TOS plugin configuration insanity.config.tsorsanity.config.js, both from thetosPluginor thetranslationOStool. - Start your Sanity Studio, the TOS configuration page should pop up automatically.
- Fill in the required fields (TOS API key, environment) and save the configuration.
The authentication information will be saved in your Sanity Studio dataset as a document of type translationOSSettings, and the
plugin will use this configuration from then on.


