{"id":21076795,"url":"https://github.com/atidatech/contentful-cli-migrations","last_synced_at":"2025-05-16T07:34:01.846Z","repository":{"id":173487088,"uuid":"648553912","full_name":"AtidaTech/contentful-cli-migrations","owner":"AtidaTech","description":"Contentful CLI Migrations Tool","archived":false,"fork":false,"pushed_at":"2024-08-27T09:55:19.000Z","size":324,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-27T11:43:50.864Z","etag":null,"topics":["cli","contentful","contentful-cli","contentful-migration","javascript","migration-tool","migrations","nodejs"],"latest_commit_sha":null,"homepage":"https://npmjs.com/package/contentful-cli-migrations","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AtidaTech.png","metadata":{"files":{"readme":"README-migrations.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-06-02T08:36:42.000Z","updated_at":"2024-08-27T09:54:44.000Z","dependencies_parsed_at":null,"dependency_job_id":"bf2776ac-5ccd-49bd-ba22-80b5a6d1d792","html_url":"https://github.com/AtidaTech/contentful-cli-migrations","commit_stats":null,"previous_names":["atidatech/contentful-cli-migrations"],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AtidaTech%2Fcontentful-cli-migrations","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AtidaTech%2Fcontentful-cli-migrations/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AtidaTech%2Fcontentful-cli-migrations/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AtidaTech%2Fcontentful-cli-migrations/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AtidaTech","download_url":"https://codeload.github.com/AtidaTech/contentful-cli-migrations/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254488829,"owners_count":22079505,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","contentful","contentful-cli","contentful-migration","javascript","migration-tool","migrations","nodejs"],"created_at":"2024-11-19T19:30:22.120Z","updated_at":"2025-05-16T07:33:57.861Z","avatar_url":"https://github.com/AtidaTech.png","language":"JavaScript","readme":"# 🐦 How to Write Contentful Migrations\n\n## For Starters\n\nContentful migrations are very similar to Database migrations. And the power of scripting migrations is that the Contentful\nContent-types (equivalent to Database tables) can have a unique structure that fits the current needs of the Editors and\nDevelopers. \n\nSome of the best resources to start writing migrations are:\n* [Scripting migrations with the Contentful CLI](https://www.contentful.com/developers/docs/tutorials/cli/scripting-migrations/)\n* [The right way to migrate your content...](https://www.contentful.com/blog/using-the-contentful-migration-cli/)\n* [Contentful migration official README](https://github.com/contentful/contentful-migration/blob/master/README.md)\n\nHowever, as the official Migration CLI (and NPM library) takes care of so many things, it is left to the user/developer\nto implement a suitable strategy to make migrations reliable and make the different environments work in a reliable way.\n\n## Use environments as branches\n\nOne of the first things to do is to think, for all intents and purposes, of Contentful Environments as Git Branches. \nThe idea behind is that we can create a new 'Environment' from an existing one (let's say 'dev'), create then a migration\nand test it properly. When everything is set, this migration can be committed alongside with the code, and be executed \non the 'dev' Environment assuring that the Content-types are correctly migrated and no data is corrupted during that process. \n\nSimilarly, we could use the scripted migration in a CI/CD pipeline, to automatically apply the same migrations to a 'staging' \nor 'master' Environment whenever the code is deployed from their respective branches.\n\n### Use a '-dev' and '-test' environment\n\nBut one of the first things to do is to actually create and/or modify existing Content-types to see if they fit whatever \nFE Development is intended for them. And meanwhile it is true that a simple migration can be scripted right away, it is\nalways better to start with a new Environment to make these modifications, till we are satisfied with them.\n\nA suggestion is to create a new Environment, using the migration number, or the ticket ID from JIRA, or anything else\nreally that can make the Environment name unique. Let's assume we arrived at migration '26' and we want to work on a new\nset of modifications, we could call the Environment (duplicated from 'dev' or 'master') `0027-dev`. In this branch we\ncould manually change, add, and remove Content-types till we are satisfied with the final result.\n\nLet's open the Contentful Web App (https://app.contentful.com/) and go to the tab 'Content model'. Once there click \non the blue button '+ Add content type'. We are going add a fictitious 'Blog Post' Content-type, just to show a relatable\nexample. We then proceed to add some fields:\n\n* `title` / short text - Select the field as 'Entry title' and 'Required Field'.\n* `slug` / short text - Configure it as 'Required' and 'Unique' - Towards the end select 'Appearance \u003e Slug'.\n* `post` / rich text - Just use default settings and click 'Confirm'.\n\nRemember to click on 'Save' in the top right corner. You should see in your editor something like this:\n![New Blog Post Content-type](./images/migrations-01-add-new-content-type.png)\n\nBut how do you transform a manual edit into a Contentful migration? This is mostly thanks to the 'JSON preview'. The \nresulting JSON structure can be easily ported to the Javascript syntax of the migration. Let's see how the JSON looks \nlike:\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003ccode\u003eJSON object of the new Content-type\u003c/code\u003e\u003c/summary\u003e\n\n```json\n{\n  \"name\": \"Blog Post\",\n  \"description\": \"New Amazing Blog articles\",\n  \"displayField\": \"title\",\n  \"fields\": [\n    {\n      \"id\": \"title\",\n      \"name\": \"title\",\n      \"type\": \"Symbol\",\n      \"localized\": false,\n      \"required\": true,\n      \"validations\": [],\n      \"disabled\": false,\n      \"omitted\": false\n    },\n    {\n      \"id\": \"slug\",\n      \"name\": \"slug\",\n      \"type\": \"Symbol\",\n      \"localized\": false,\n      \"required\": true,\n      \"validations\": [\n        {\n          \"unique\": true\n        }\n      ],\n      \"disabled\": false,\n      \"omitted\": false\n    },\n    {\n      \"id\": \"post\",\n      \"name\": \"post\",\n      \"type\": \"RichText\",\n      \"localized\": false,\n      \"required\": false,\n      \"validations\": [\n        {\n          \"enabledMarks\": [\n            \"bold\",\n            \"italic\",\n            \"underline\",\n            \"code\",\n            \"superscript\",\n            \"subscript\"\n          ],\n          \"message\": \"Only bold, italic, underline, code, superscript, and subscript marks are allowed\"\n        },\n        {\n          \"enabledNodeTypes\": [\n            \"heading-1\",\n            \"heading-2\",\n            \"heading-3\",\n            \"heading-4\",\n            \"heading-5\",\n            \"heading-6\",\n            \"ordered-list\",\n            \"unordered-list\",\n            \"hr\",\n            \"blockquote\",\n            \"embedded-entry-block\",\n            \"embedded-asset-block\",\n            \"table\",\n            \"hyperlink\",\n            \"entry-hyperlink\",\n            \"asset-hyperlink\",\n            \"embedded-entry-inline\"\n          ],\n          \"message\": \"Only heading 1, heading 2, heading 3, heading 4, heading 5, heading 6, ordered list, unordered list, horizontal rule, quote, block entry, asset, table, link to Url, link to entry, link to asset, and inline entry nodes are allowed\"\n        },\n        {\n          \"nodes\": {}\n        }\n      ],\n      \"disabled\": false,\n      \"omitted\": false\n    }\n  ],\n  \"sys\": {\n    \"space\": { ... },\n    \"id\": \"blogPost\",\n    \"type\": \"ContentType\",\n    \"createdAt\": \"2023-08-13T13:49:46.647Z\",\n    \"updatedAt\": \"2023-08-13T13:49:47.052Z\",\n    \"environment\": { ... },\n    \"publishedVersion\": 1,\n    \"publishedAt\": \"2023-08-13T13:49:47.052Z\",\n    \"firstPublishedAt\": \"2023-08-13T13:49:47.052Z\",\n    \"createdBy\": { ... },\n    \"updatedBy\": { ... },\n    \"publishedCounter\": 1,\n    \"version\": 2,\n    \"publishedBy\": { ... }\n  }\n}\n```\n\u003c/details\u003e\n\nAs we can see when we will start scripting the migration, the field names, values and structure is very similar to what\nneeds to be defined with the Javascript syntax. Let's first add the content-type data and the basic migration structure:\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003ccode\u003e0027-Add-BlogPost.cjs\u003c/code\u003e\u003c/summary\u003e\n\n```js\nconst {default: Migration, MigrationContext } = import(\"contentful-migration\")\n\n/**\n * @param {Migration} migration\n * @param {MigrationContext} context\n * @returns {Promise\u003cvoid\u003e}\n */\nmodule.exports = async function (migration, context) {\n    const blogPost = migration.createContentType('blogPost', {\n        name: 'Blog Post',\n        description: 'New Amazing Blog articles',\n        displayField: 'title'\n    })\n\n    blogPost.createField('title')\n        .name('title')\n        .type('Symbol')\n        .localized(false)\n        .required(true)\n        .validations([])\n        .disabled(false)\n        .omitted(false)\n\n    blogPost.createField('slug')\n        .name('slug')\n        .type('Symbol')\n        .localized(false)\n        .required(true)\n        .validations([\n            {\n                \"unique\": true\n            }\n        ])\n        .disabled(false)\n        .omitted(false)\n\n    blogPost.createField('post')\n        .name('post')\n        .type('RichText')\n        .localized(false)\n        .required(false)\n        .validations([\n            {\n                \"enabledMarks\": [\n                    \"bold\",\n                    \"italic\",\n                    \"underline\",\n                    \"code\",\n                    \"superscript\",\n                    \"subscript\"\n                ],\n                \"message\": \"Only bold, italic, underline, code, superscript, and subscript marks are allowed\"\n            },\n            {\n                \"enabledNodeTypes\": [\n                    \"heading-1\",\n                    \"heading-2\",\n                    \"heading-3\",\n                    \"heading-4\",\n                    \"heading-5\",\n                    \"heading-6\",\n                    \"ordered-list\",\n                    \"unordered-list\",\n                    \"hr\",\n                    \"blockquote\",\n                    \"embedded-entry-block\",\n                    \"embedded-asset-block\",\n                    \"table\",\n                    \"hyperlink\",\n                    \"entry-hyperlink\",\n                    \"asset-hyperlink\",\n                    \"embedded-entry-inline\"\n                ],\n                \"message\": \"Only heading 1, heading 2, heading 3, heading 4, heading 5, heading 6, ordered list, unordered list, horizontal rule, quote, block entry, asset, table, link to Url, link to entry, link to asset, and inline entry nodes are allowed\"\n            },\n            {\n                \"nodes\": {}\n            }\n        ])\n        .disabled(false)\n        .omitted(false)\n\n\n    blogPost.changeFieldControl('slug', 'builtin', 'slugEditor', {\n        trackingFieldId: 'title'\n    })\n}\n```\n\u003c/details\u003e\n\nNow we have our migration, but we don't know if it works correctly or will perform the expected modifications. Meanwhile\nlinters can help with the syntax and general validity of the Javascript code, it's always good to test the migration \nitself in a new 'clean' Environment, so that we can be sure the automation during deployment will go without problems.\nTo do that, let's duplicate a new Environment from the original source Environment. Just create a duplicate of `dev` and\ncall it `0027-test`. This also allows us to tune the Content-type in the other `0027-dev` Environment, and adjust the\nmigration till we are satisfied (we can always delete and recreate the `0027-test` Environment, since its sole purpose\nis to validate the migration runs smoothly).\n\nSave the file `0027-Add-Blog-Post.cjs` under the folder `./migrations/scripts` of your project and then run:\n\n```shell\n$ npx contentful-cli-migrations --to 0027-test\n```\n\nYou should see the following output:\n\n```shell\n##/INFO: Applying migrations to environment-id: 0027-test\n##/INFO: Latest migration successfully run # 26\nThe following migration has been planned\n\nEnvironment: 0027-test\n\nCreate Content Type blogPost\n  - name: \"Blog Post\"\n  - description: \"New Amazing Blog articles\"\n  - displayField: \"title\"\n\n  Create field title\n    - name: \"title\"\n    - type: \"Symbol\"\n    - localized: false\n    - required: true\n    - validations: []\n    - disabled: false\n    - omitted: false\n\n  Create field slug\n    - name: \"slug\"\n    - type: \"Symbol\"\n    - localized: false\n    - required: true\n    - validations: [{\"unique\":true}]\n    - disabled: false\n    - omitted: false\n\n  Create field post\n    - name: \"post\"\n    - type: \"RichText\"\n    - localized: false\n    - required: false\n    - validations: [{\"enabledMarks\":[\"bold\",\"italic\",\"underline\",\"code\",\"superscript\",\"subscript\"],\"message\":\"Only bold, italic, underline, code, superscript, and subscript marks are allowed\"},{\"enabledNodeTypes\":[\"heading-1\",\"heading-2\",\"heading-3\",\"heading-4\",\"heading-5\",\"heading-6\",\"ordered-list\",\"unordered-list\",\"hr\",\"blockquote\",\"embedded-entry-block\",\"embedded-asset-block\",\"table\",\"hyperlink\",\"entry-hyperlink\",\"asset-hyperlink\",\"embedded-entry-inline\"],\"message\":\"Only heading 1, heading 2, heading 3, heading 4, heading 5, heading 6, ordered list, unordered list, horizontal rule, quote, block entry, asset, table, link to Url, link to entry, link to asset, and inline entry nodes are allowed\"},{\"nodes\":{}}]\n    - disabled: false\n    - omitted: false\n\nPublish Content Type blogPost\nUpdate field controls for Content Type blogPost\n\n  Update field slug\n    - widgetId: \"slugEditor\"\n    - widgetNamespace: \"builtin\"\n    - trackingFieldId: \"title\"\n? Do you want to apply the migration (Y/n) \n```\n\nBy pressing the `Y` to confirm, we can see the migration being applied:\n\n```shell\n✔ Create Content Type blogPost\n✔ Update field controls for Content Type blogPost\n🎉  Migration successful\n##/INFO: Migration 0027-Add-Blog-Post.cjs Done!\n```\n\nThe migration is now safe to be committed alongside other code of your work. Once the commit is pushed, you can safely\nremove both the `0027-dev` and `0027-test` Environments from Contentful.\n\n## async/await\n\nA thing you might have noticed in the migration script example, is that the function is defined as `async`.\n\n```javascript\nconst {default: Migration, MigrationContext } = import(\"contentful-migration\")\n\n/**\n * @param {Migration} migration\n * @param {MigrationContext} context\n * @returns {Promise\u003cvoid\u003e}\n */\nmodule.exports = async function (migration, context) {\n    ...\n}\n```\n\nFor most of the use cases you might see migrations with or without async, however it's a good practice to define the \nfunction as `async` when using this tool, since the call to the scripts are all performed using `await`. In addition,\nusing `async` allows to include other functions, and be sure that everything is executed in the correct order.\n\nIf you are still not convinced, you can read this article on \n[5 Reasons Why Javascript async/await wins over promises](https://dev.to/deadwin19/5-reasons-why-javascript-async-await-over-promises-1if3).\n\nAnother best practice is to add annotations to your migrations. In this way you will most likely unlock your \neditor capabilities to help you autocomplete the different functions (like `createContentType`, `editContentType`, and \nso on). If by chance your editor still doesn't want to show you any suggestion for the functions, you could separately \nadd the `contentful-migration` npm package in your package.json (it worked on IntelliJ editor).\n\n## transformEntries\n\nEven though is documented, a 'function' of Contentful Migrations that is often overlooked is `transformEntries`. The \nreason to say such a thing, is because this function is pretty powerful and not only can help to transform a text field\nto a reference field (as many examples show with 'Blog Post Author' example), but it unlocks the possibility to \nmanipulate data, till a certain degree, in a migration that is usually/mostly supposed to change the structure.\n\nLet's take a real case: you want to change the validation for a certain 'id' field, like the slug of the previously \ncreated blog post (that has no validation on the input characters), or a product id. Changing the validation might be \nneeded to avoid that the Editors will input some weird characters that could create problems down the line. For example, \na slug, that is used to build a link, can result in a malformed URI.\n\nIn this case we should use a migration to first change the existing data, and then change the validation, so that we \nwon't have problem when applying that migration (it could fail if the old data is considered invalid). Changing \nthe existing data will also ensure that if we modify an older entry, we will not be blocked by the invalid id (or slug) \nto republish the updated entry.\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003ccode\u003e0043-Add-Validation-to-Slug-in-BlogPost.cjs\u003c/code\u003e\u003c/summary\u003e\n\n```js\nconst {default: Migration, MigrationContext } = import(\"contentful-migration\")\n\n/**\n * @param {Migration} migration\n * @param {MigrationContext} context\n * @returns {Promise\u003cvoid\u003e}\n */\nmodule.exports = async function (migration, context) {\n    migration.transformEntries({\n        contentType: 'blogPost',\n        from: ['slug'],\n        to: ['slug'],\n        // This loops in the field slug for each locale that might be present\n        transformEntryForLocale: function (fromFields, currentLocale) {\n            // We want to be sure a value is set up for the slug\n            if (\n                fromFields !== undefined \u0026\u0026\n                fromFields['slug'] !== undefined \u0026\u0026\n                fromFields['slug'][currentLocale] !== undefined\n            ) {\n                // Initial values\n                let originalSlug = fromFields['slug'][currentLocale]\n                let cleanedSlug = ''\n\n                // It removes anything that is not lowercase letter, numbers and dashes\n                cleanedSlug = originalSlug.toLowerCase().replace(/[^a-z0-9-]+/, '')\n                \n                // We assign the cleaned up slug back to the field\n                return {\n                    slug: cleanedSlug\n                }\n            }\n        }\n    })\n\n    const blogPost = migration.editContentType('blogPost')\n\n    blogPost.editField('slug').validations([\n        {\n            // We maintain the unique 'true'\n            unique: true\n        },\n        {\n            // We add the regular expression for valid characters\n            regexp: {\n                pattern: '^[a-z0-9\\\\-]*$',\n                flags: 'g'\n            },\n            message:\n                'The ID only allows: lowercase letters, numbers and dashes'\n        }\n    ])\n}\n```\n\u003c/details\u003e\n\nAs any powerful functionality, it surely should be used with responsibility, and it should be tested extensively, the \nmore important is the data for your business (that's why using also the \n[Contentful CLI Export](https://www.npmjs.com/package/contentful-cli-export) to make backups is always a good idea).\n\nIn addition, using the `transformEntries` with the two other 'tricks' we are going to explain (makeRequest and Locales), \nrepresents a somehow practical, repeatable and even cheaper alternative to writing your own script everytime some strong\ndata manipulation is needed.\n\n## makeRequest\n\nAnother feature that is documented but is also very powerful and obscure is the 'context' that is usually passed\nwhen creating a migration:\n\n```js\nmodule.exports = async function (migration, context) {\n    ...\n}\n```\n\n`context` contains 3 important object, and per documentation the module.exports can be written like this:\n\n```js\nmodule.exports = async function (migration, { makeRequest, spaceId, accessToken }) {\n    ...\n}\n```\n\nMeanwhile `spaceId` and `accessToken` are quite easy to understand, respectively the spaceId on which we are performing\nthe migration (useful if you want to specify a slightly different behaviour for that space), and accessToken is the \nmanagement accessToken that is executing the migration (We will see later how that can be used).\n\nBut it's the first `makeRequest` object that is the most interesting one. `makeRequest` allows you to perform \nContentful Management API Calls inside the migration. \n\n\u003e Remember: With great power comes great responsibility! 🕷️\n\nLet's see an example of a makeRequest inside a migration:\n\n```js\nconst {default: Migration, MigrationContext } = import(\"contentful-migration\")\n\n/**\n * @param {Migration} migration\n * @param {MigrationContext} context\n * @returns {Promise\u003cvoid\u003e}\n */\nmodule.exports = async function (migration, { makeRequest, spaceId, accessToken }) {\n    await migration.transformEntries({\n        contentType: 'blogPost',\n        from: ['authour'],\n        to: ['authorName'],\n        transformEntryForLocale: async function (fromFields, currentLocale) {\n\n            ...\n\n            const refEntryId = fromFields?.author['en-US']?.sys?.id\n\n            if (refEntryId !== undefined) {\n                const refEntry = await makeRequest({\n                    method: 'GET',\n                    url: `/entries/${refEntryId}`\n                })\n            }\n\n            ...\n\n        }\n    })\n}\n```\n\nIn this case, we would take a referenced Entry object, from its sys.id, and we would use that for some manipulation.\nIn this example, it would work well inside `transformEntries`, for example to take read the referenced entry and extract \nthe first and last name of the author. \n\nBut the `makeRequest` doesn't stop there. It allows you to perform also editing or deleting Entries, and that can be \nvery dangerous. And it is somehow different from what we have seen before, for two reasons:\n\n1. Differently from the `transformEntries`, the request is a raw API call, hence it's not structured inside a method that\n   would work inside the framework. It does not have built-in fail over, error management or even basic checks.\n2. The API calls are performed **before** the migration runs. Especially if editing some data, this means that if we \n   abort the migration (or it fails), the modified data will still remain modified. So when editing or adding values,\n   we should always use some sort of idempotent action (like adding the full name to a new field from first or last name).\n\nSee in details `{ makeRequest }` on the official documentation: \nhttps://github.com/contentful/contentful-migration/blob/master/README.md#context\n\nIf you want to see the makeRequest in action, try this:\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003ccode\u003e0044-Test-makeRequest.cjs\u003c/code\u003e\u003c/summary\u003e\n\n```js\nconst {default: Migration, MigrationContext } = import(\"contentful-migration\")\n\n/**\n * @param {Migration} migration\n * @param {MigrationContext} context\n * @returns {Promise\u003cvoid\u003e}\n */\nmodule.exports = async function (migration, { makeRequest, spaceId, accessToken }) {\n    const entryId = 'your-entry-id'\n    const result = await makeRequest({\n        method: 'GET',\n        url: `/entries/${entryId}`\n    })\n\n    console.log(result)\n}\n```\n\n\u003c/details\u003e\n\n## Locale-agnostic migrations\n\nLet's now kind of summarize all we explained so far, by addressing a problem that can happen when the scope of your \nbusiness expands. Writing Contentful migrations is all fun and cool, and writing them with a structured tool, makes you\nsleep good at night. However, as your business expands there might be the need to duplicate all the work you have\ndone into another space, to support an expansion: the classic example is when your website/e-commerce expands in new\ncountries.\n\nAs developers, we want to continue writing migrations that can accommodate as many scenarios as possible. Nobody \nis forbidding you to have one set of migrations for space-id: 1 and one for space-id: 2. But in a business, especially\nwhen the only difference is about language and locale, be able to write a migration that is locale-agnostic could be\nfor you a life (and money) saver.\n\nThe idea of locale-agnostic is that locale is only used in Contentful migrations when setting default values or when\ntransforming entries per locale. So with few simple functions you would be able to write migrations whose parameters \nare not baked in the script, hence be able to run the same migration on two different spaces. One tht has en-US as \ndefault locale, and 2 additional locales; but also one that has it-IT as default one, and maybe 5 more locales.\n\nThis doesn't come out of the box, but it can be implemented with all the tools we have seen so far, included the \n[Contentful Lib Helpers Library](https://www.npmjs.com/package/contentful-lib-helpers) on top of which many of these \nscripts are built.\n\nFor this you will need to include in your migration script:\n* The Contentful Management library (that is already imported)\n* The Contentful Migration library, for the annotations\n* The Contentful Lib Helpers\n\nThe Helpers provide you with some simple functions for locale:\n* `getAllLocalesCode` - Returns the array of locale codes. ie: ['en-US', 'it-IT', 'de-DE'], where usually the first one\n  is the default one;\n* `getDefaultLocaleCode` - The default locale code as a string. ie: 'en-US';\n* `getDefaultValuesForLocales` - It builds an object with all the locale codes as keys, and the passed default value as \n  the value for each of the objects.\n\nLet's see an example migration that uses the `getDefaultLocaleCode` function, and then using the returned value as the \nkey for the default value in a new field(in this case `en-US`). As you can see we use the 'context' object to extrapolate \nalso the environmentId (in addition to spaceId and accessToken), and all the imports and await calls are in a separate, more \nreadable and easy to use external function.\n\n\u003cdetails\u003e\n    \u003csummary\u003e\u003ccode\u003e0045-Add-isPromoted-to-BlogPost.cjs\u003c/code\u003e\u003c/summary\u003e\n\n```js\nconst {default: Migration, MigrationContext } = import(\"contentful-migration\")\n\n/**\n * @param {Migration} migration\n * @param {MigrationContext} context\n * @returns {Promise\u003cvoid\u003e}\n */\nmodule.exports = async function (migration, { spaceId, environmentId, accessToken }) {\n   const blogPost = migration.editContentType(\n           'blogPost'\n   )\n\n   const defaultLocale = await getDefaultLocale(spaceId, environmentId, accessToken)\n\n   blogPost\n           .createField('isPromoted')\n           .name('Is the post promoted?')\n           .type('Boolean')\n           .required(true)\n           .localized(false)\n           .disabled(false)\n           .omitted(false)\n           .validations([])\n           .defaultValue({\n              [defaultLocale]: false\n           })\n}\n\nasync function getDefaultLocale(spaceId, environmentId, accessToken) {\n   const ctManagement = await import('contentful-management')\n   const contentfulManagement = ctManagement?.default === undefined ? ctManagement : ctManagement?.default\n\n   const fnGetEnvironment =\n           await import('contentful-lib-helpers/lib/getEnvironment.js')\n   const environment = await fnGetEnvironment.getEnvironment(\n           contentfulManagement,\n           accessToken,\n           spaceId,\n           environmentId\n   )\n\n   const fnGetDefaultLocaleCode =\n           await import('contentful-lib-helpers/lib/locales/getDefaultLocaleCode.js')\n\n   return await fnGetDefaultLocaleCode.getDefaultLocaleCode(environment)\n}\n```\n\u003c/details\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatidatech%2Fcontentful-cli-migrations","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fatidatech%2Fcontentful-cli-migrations","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatidatech%2Fcontentful-cli-migrations/lists"}