How to create localized entries from code?

System Information
  • Strapi Version:
  • Operating System:
  • Database:
  • Node Version:
  • NPM Version:
  • Yarn Version:

Hello! Is there a way to create localized entries from code? I can’t find any documentation on the subject. All that is listed is the Localization API, which works well for creating localized versions of entries, but can they be created directly from code?

Something like:

  Title: "Test fi",
  locale: "fi",
  localizations: [
      Title: "Test en",
      locale: "en",


1 Like


I’m currently using this in my own take of the strapi-plugin-import-content plugin (GitHub - kiddicolour/strapi-plugin-import-content)

What I ended up with is a 2 step process:

  1. Creat the localized records, using the id from the record containing the default language values
    see line 54 of the import-content service

  2. Update all the records with the localizations array including the id
    see line 173 of the import-content service

This makes sure all relations are set and the admin displays the translations correctly linked.

Hope this helps!

Kind regards,

Keep in mind that the above plugin does more than just importing localizations and quite some abstractions are in place. The key takeaway is (as far as I reverse engineered all this): the localizations array can only contain the id, locale (and published_at column if draftAndPublish is enabled). I could be wrong though, but this approach works for me :wink:

Thanks for the explanation!

I checked your plugin, trying to do the same but it’s simply not working for me.

I created 2 separate entries, now trying to link together. ID 293367 is en locale, 316045 is fr . On the admin panel they appear fine when checking with different locales.

        id: 293367
        localizations: [{ id: 316045, locale: 'fr' }]

The result I get after the update:

  id: 293367,
  Word: 'car',
  created_at: 2021-09-22T09:28:00.000Z,
  updated_at: 2021-10-14T21:02:52.000Z,
  created_by: null,
  updated_by: null,
  locale: 'en',
  localizations: []

Any ideas?

@wintercounter For Strapi v4, let’s say we have three locales, so:

  1. We have to create two entries with different locale independently.
  2. Create (not update any or root!) the third entry with different locale by setting another two localized entries as ‘localizations’ - all three entries will be synchronized.
    Check repo …/packages/plugins/i18n/server/services/entity-service-decorator.js - decorator create vs. update methods.


As @wintercounter says, It’s not working on strapi v4.

I created an entity as a spanish translation of an existent english entity, and setted its localizations with the id of the existent one.

On the admin panel, the new translation has a relationship with the existent entity:

But the existent entity ignores the new translation:

The automatic syncing mentioned by @ShiS seems not working. Maybe I’m doing something wrong.

This is my code:

async function save(service, entityObj, entityId, availableLocales, locale) {
  entityObj['locale'] = locale;

  // gets the existent entity
  const relatedTranslation = await localizationRepository.getLocalization(
    service, availableLocales, entityId

  // set the existent entity as a translation of the new one
  if (relatedTranslation !== null) {
    entityObj['localizations'] =;

  // creates the new entity
  const translation = await service.create({ data: entityObj });

  // set the new entity as a existent entity translation
  await updateRelatedTranslationLocations(
    service, locale, translation, relatedTranslation

async function updateRelatedTranslationLocations(service, locale, translation, relatedTranslation) {
  const localization = {
    locale: locale

  const relatedTranslationLocalizations = relatedTranslation.localizations.concat([localization]);

  // try to sync the translations, but seems to do nothing
  await service.update(, {
    data: {
      localizations: relatedTranslationLocalizations

Any help will be appreciated, thanks!.

As a workaround, I could store and link a new translation with its original one with the following request, which is made from the admin panel when you create a new translation:

async function save(entityName, translation, related) {
    await axios({
        method: 'POST',
        url: `/content-manager/collection-types/api::${entityName}.${entityName}`,
        params: {
            'plugins[i18n][locale]': translation.locale, // new entity locale
            'plugins[i18n][relatedEntityId]': related // original related entity id to link with the new one
        data: translation, // new entity data
        headers: {
            Authorization: `Bearer ${auth.getToken()}`

Hope it could be useful for somebody.

I used the same controller that handles the normal localization request with URL (/api/:entity/:id/localizations) to create my localization from code. You can access it with the strapi.controller function like this:

        await strapi.controller(`api::${entity}.${entity}`).createLocalization(ctx)

This is how I added the data for new localization and :id from URL, which is the entry in main locale: =;
        ctx.request.body = newEntity;

newEntity is just an object with translated (or not) data, but make sure to add ‘locale’ entry to it before creating the localization.

I’ve just spent a very decent amount of time scratching my head over this for Strapi v4! The solution I came up with is as follows:

  1. Add the main entity to the db - store the id returned from this query
  2. Add each localized entity to the db - store the ids of each localized entity
  3. Create the relationship between the main entity and the localized entities
  4. Create the reverse relationships between each localized entity and the main entity
const uid = "api::entity.entity";
const { localizations, ...entityWithoutLocalizations } = entity;
        try {
          //  Create main entity without localizations
          const addedEntity = await strapi.entityService.create(uid, {
            data: entityWithoutLocalizations,

          //  Add the id of the inserted main entity to the entity without localizations - we need this later when we set up the reverse relationship

          for (let i = 0; i < localizations.length; i++) {
            //  Create the localization object in the db
            const addedLocalization = await strapi.entityService.create(uid, {
              data: localizations[i],
            //  Add the id of the localization that was just added to the db to the original data
            localizations[i].id =;
            //  Create the relationship between the localization and the main entity
            await strapi.query(uid).update({
              where: { id: localizations[i].id },
              data: { localizations: [entityWithoutLocalizations] },

          //  Finally, create the relationship between the main entity and all the localizations
          await strapi
            .update({ where: { id: }, data: { localizations } });
        } catch (e) {
1 Like
// const baseEntity  is api::translation.translation with default lng

const {createCoreController} = require('@strapi/strapi').factories;
              const contentType = strapi.getModel('api::translation.translation');
              const createLocalization = createCreateLocalizationHandler(contentType);
              await createLocalization({
                data: {
                  key: baseEntity.key,
                  namespace: ns,
                  locale: lng,
                  value: translateResultText,

Perhaps the best way to handle is below.

  1. Create entries for Default and Required locales.
  2. Once the locale entries are done update the entries with strapi query method as below.
                where: { id: },
                data: {
                  [{ id: 1, locale: 'ar-SA' }],

What did worked for me for strapi 4 :

    // Create the content in strapi 4
    const deItem = await strapi.entityService.create(`api::${k}.${k}`, {
      data: toAdd["de"],

    const enItem = await strapi.entityService.create(`api::${k}.${k}`, {
      data: { ...toAdd["en"], localizations: [] },
      populate: ['localizations'],

Strapi has a built in method to synchronize localizations.

strapi.plugin(‘i18n’).service(‘localizations’).syncLocalizations(entry, { model })

Here’s how to use it:

// Create your localized entries
const localizedEntities = await Promise.all(
  ['de', 'fr'].map((locale) => {
    strapi.entityService.create('', { data: {, locale } });

// Create the main entry and link all of the localized entries to it
const mainEntry = await strapi.entityService.create('', {
  data: {,
    locale: 'en',
    localizations: =>,
  populate: ['localizations'], // localizations needs to be populated for the upcoming `syncLocalizations()` call

await strapi
  .syncLocalizations(mainEntry, { model: strapi.getModel('') });

Ace, thanks @florianmrz !
your localizedEntities did end up being undefined, which I fixed by adding a return to the create statement in

['de', 'fr'].map((locale) => {
  return strapi.entityService.create('', { data: {, locale } });

Fetching the data from external api and I following the florianmrz . I try someting simillar to i can create and update the products by i can’t sincronize the localizations. Someone knows why?

const locals = await getLocales();     
            let localizations = [];
            let fail = false;
            let defaultLocale = await strapi.db.query(`api::product.product`).findOne({where: {apiId: id, locale: DEFAULT_LOCALE}});
            for(let l=0; l<locals.length; l++){
              let local = locals[l];
              localizations[l] = getParsedProductByLang(, id, local);
              if(local == DEFAULT_LOCALE){
                    await strapi.entityService.update('api::product.product',, { data: localizations[l],  populate: '*'});
                    defaultLocale = await strapi.entityService.create('api::product.product', { data: localizations[l],  populate: '*'  });
                    fail = true;
 localizations[l].id =;
                  let currentLocale = await strapi.db.query(`api::product.product`).findOne({where: {apiId: id, locale: local}});
                      localizations[l].id =;
                      currentLocale = await strapi.entityService.update('api::product.product',, { data: localizations[l],  populate: '*' });
                      currentLocale = await strapi.entityService.create('api::product.product', { data: localizations[l],  populate: '*'  });
                      fail = true;
                  localizations[l].id =;
                let mainLocale = await strapi.entityService.update('api::product.product',, {
                                    data: {
                                      localizations: =>,
                                    populate: ['localizations'], // localizations needs to be populated for the upcoming `syncLocalizations()` call
                await strapi.plugin('i18n')
                            .syncLocalizations(mainLocale, { model: strapi.getModel('api::product.product') }); 
1 Like

The code provided by @florianmrz works great, but I encountered an issue when I wanted to update an already existing entry and add a localization to it. Specifically, the localizations field would just stay empty.

I eventually figured out that using the Query Engine API instead resolved the problem for me. Here’s the modified code snippet using the Query Engine API to update an existing entry:

// (This exact logic does not work with the Entity Service API)
const updatedMainEntry = await strapi.query('api::item.item').update({ 
                where: { id },
                data: {
                    localizations: [],
                populate: ['localizations'],

I hope this helps anyone who might come across this post!


Thanks to both @florianmrz and @PhilippEngmann I got it working (Strapi 4.24.2).
You’re both lifesavers !

1 Like

I’m using also 4.24.2, but the solution is not really working for me - only the main language is showing all localised versions in the dashboard, but when switching to the other locales, they are shown like standalone entries - so no connection to the other locales… @Bishop can you confirm me, that this works fine for you?