Importing linked content

Your content will often contain references to other pieces of imported content. A content item can use assets or point to other content items using linked items elements or rich text elements. This tutorial will walk through how to import these references to Kentico Kontent. For the basics of using the Content Management API (CM API), first see Importing to Kentico Kontent.

Premium feature 

The Content Management API requires a Professional plan or higher.

Note: The CM API currently has two versions. While CM API v2 is in BETA, it is not expected that its behavior will substantially change just that more features will be added (all changes will be noted in the API changelog). For this reason, the cURL and JavaScript examples here use v2. For v1, you can only manage content items, their language variants, and assets. Until the .Net SDK is updated to v2, the examples here for .Net use v1.

Table of contents

    To avoid having to import objects in a specific order, you can use external IDs to reference non-existent (not imported yet) content. This also solves problems with circular dependencies.

    Here is how it works:

    1. Define external IDs for all content items and assets you want to import in advance.
    2. When referencing another content item or asset, use its external ID.
    3. Import your content using the upsert methods with external ID. The system will resolve all references.

    This way, you can import your content in any order and run the import process repeatedly to keep your project up to date. In the example below, you will import two content items that reference each other in their Linked items elements.

    Example scenario

    Say you want to import two related content items: Donate with us and On Roasts articles. Each content item references the other in the Related articles Linked items element. The final result will have the following structure:

    1. Defining external IDs

    External IDs are string-based identifiers of items and assets defined by you. You can define new IDs or reuse IDs from the original storage system you are importing content from.

    It's up to you to ensure no two objects have the same external ID. See referencing by external ID for more details. For large projects, consider using GUIDs.

    To keep things simple here, use 123 and 456 for your two articles.

    2. Using external IDs to reference items

    When defining the Linked items elements, use external IDs to reference the other content item:

    • JSON
    "related_articles": [ { "external_id": "456" } ]
    "related_articles": [ { "external_id": "456" } ]

    Rich text links 

    See the CM API reference to learn how to link to content items from Rich text elements.

    3. Importing content

    To create the On Roasts content item, send a PUT request to the /items/external-id/<YOUR_ITEM_EXTERNAL_ID> endpoint.

    In the body of the request, specify the item's name and content type.

    See our API Reference for more details on upserting content items.

    Best practice: Upsert by external ID 

    You can use a simple POST to /items request to add the content item. But using an UPSERT operation and defining an external ID for your item has advantages and makes the import process much smoother:

    • You can run the same request repeatedly. If the item doesn't exist, it will be created. If it does, it will be updated.
    • You can reference or link to your item, even if it hasn't been imported yet (and has no internal ID or codename). You might have other content items that reference this one in Rich text or Linked items elements. But if you are using external IDs you don't need to worry about the order in which the content items are imported.
    • JavaScript
    // Using ES6 syntax import { ManagementClient } from "@kentico/kontent-management"; const client = new ManagementClient({ projectId: "<YOUR_PROJECT_ID>", apiKey: "<YOUR_API_KEY>" }); client.upsertContentItem() .byItemExternalId("123") .withData( { name: "On Roasts", type: "article" } ) .toObservable() .subscribe((response) => { console.log(response); }, (error) => { console.log(error); });
    // Using ES6 syntax import { ManagementClient } from "@kentico/kontent-management"; const client = new ManagementClient({ projectId: "<YOUR_PROJECT_ID>", apiKey: "<YOUR_API_KEY>" }); client.upsertContentItem() .byItemExternalId("123") .withData( { name: "On Roasts", type: "article" } ) .toObservable() .subscribe((response) => { console.log(response); }, (error) => { console.log(error); });
    • C#
    // Using CM API v1 using KenticoCloud.ContentManagement; ContentManagementOptions options = new ContentManagementOptions { ApiKey = "<YOUR_API_KEY>", ProjectId = "<YOUR_PROJECT_ID>" }; ContentManagementClient client = new ContentManagementClient(options); string externalId = "123"; ContentItemUpsertModel item = new ContentItemUpsertModel() { Name = "On Roasts", Type = ContentTypeIdentifier.ByCodename("article"), SitemapLocations = new[] { SitemapNodeIdentifier.ByCodename("articles") } }; ContentItemModel contentItemResponse = await client.UpsertContentItemByExternalIdAsync(externalId, item);
    // Using CM API v1 using KenticoCloud.ContentManagement; ContentManagementOptions options = new ContentManagementOptions { ApiKey = "<YOUR_API_KEY>", ProjectId = "<YOUR_PROJECT_ID>" }; ContentManagementClient client = new ContentManagementClient(options); string externalId = "123"; ContentItemUpsertModel item = new ContentItemUpsertModel() { Name = "On Roasts", Type = ContentTypeIdentifier.ByCodename("article"), SitemapLocations = new[] { SitemapNodeIdentifier.ByCodename("articles") } }; ContentItemModel contentItemResponse = await client.UpsertContentItemByExternalIdAsync(externalId, item);
    • cURL
    curl --request PUT \ --url https://manage.kontent.ai/v2/projects/<YOUR_PROJECT_ID>/items/external-id/123 \ --header "Authorization: Bearer <YOUR_API_KEY>" \ --header "Content-type: application/json" \ --data " { "name":"On Roasts", "type":{ "codename":"article" } }"
    curl --request PUT \ --url https://manage.kontent.ai/v2/projects/<YOUR_PROJECT_ID>/items/external-id/123 \ --header "Authorization: Bearer <YOUR_API_KEY>" \ --header "Content-type: application/json" \ --data " { "name":"On Roasts", "type":{ "codename":"article" } }"

    Import content by upserting a language variant.

    Send a PUT request to the endpoint specifying the language variant you want to insert or update. In the body of the request, specify the values of individual content elements.

    • JavaScript
    // Using ES6 syntax import { ManagementClient } from "@kentico/kontent-management"; const client = new ManagementClient({ projectId: "<YOUR_PROJECT_ID>", apiKey: "<YOUR_API_KEY>" }); client.upsertLanguageVariant() .byItemExternalId("123") .byLanguageCodename("en-US") .withElements([ { element: { codename: "title" }, value: "On Roasts" }, { element: { codename: "related_articles" }, value: [ { "external_id":"456" } ] } ]) .toObservable() .subscribe((response) => { console.log(response); }, (error) => { console.log(error); });
    // Using ES6 syntax import { ManagementClient } from "@kentico/kontent-management"; const client = new ManagementClient({ projectId: "<YOUR_PROJECT_ID>", apiKey: "<YOUR_API_KEY>" }); client.upsertLanguageVariant() .byItemExternalId("123") .byLanguageCodename("en-US") .withElements([ { element: { codename: "title" }, value: "On Roasts" }, { element: { codename: "related_articles" }, value: [ { "external_id":"456" } ] } ]) .toObservable() .subscribe((response) => { console.log(response); }, (error) => { console.log(error); });
    • C#
    // Using CM API v1 using KenticoCloud.ContentManagement; ContentManagementOptions options = new ContentManagementOptions { ApiKey = "<YOUR_API_KEY>", ProjectId = "<YOUR_PROJECT_ID>" }; ContentManagementClient client = new ContentManagementClient(options); ArticleModel stronglyTypedElements = new ArticleModel { Title = "On Roasts", RelatedArticles = new [] { ContentItemIdentifier.ByExternalId("456") } }; ContentItemIdentifier itemIdentifier = ContentItemIdentifier.ByExternalId("123"); LanguageIdentifier languageIdentifier = LanguageIdentifier.ByCodename("en-US"); ContentItemVariantIdentifier identifier = new ContentItemVariantIdentifier(itemIdentifier, languageIdentifier); ContentItemVariantModel<ArticleModel> responseVariant = await client.UpsertContentItemVariantAsync<ArticleModel>(identifier, stronglyTypedElements);
    // Using CM API v1 using KenticoCloud.ContentManagement; ContentManagementOptions options = new ContentManagementOptions { ApiKey = "<YOUR_API_KEY>", ProjectId = "<YOUR_PROJECT_ID>" }; ContentManagementClient client = new ContentManagementClient(options); ArticleModel stronglyTypedElements = new ArticleModel { Title = "On Roasts", RelatedArticles = new [] { ContentItemIdentifier.ByExternalId("456") } }; ContentItemIdentifier itemIdentifier = ContentItemIdentifier.ByExternalId("123"); LanguageIdentifier languageIdentifier = LanguageIdentifier.ByCodename("en-US"); ContentItemVariantIdentifier identifier = new ContentItemVariantIdentifier(itemIdentifier, languageIdentifier); ContentItemVariantModel<ArticleModel> responseVariant = await client.UpsertContentItemVariantAsync<ArticleModel>(identifier, stronglyTypedElements);
    • cURL
    curl --request PUT \ --url https://manage.kontent.ai/v2/projects/<YOUR_PROJECT_ID>/items/external-id/123/variants/codename/en-US \ --header "authorization: Bearer <YOUR_API_KEY>" \ --header "content-type: application/json" \ --data " { "elements":{ "title":"On Roasts", "related_articles":[ { "external_id":"456" } ] } }"
    curl --request PUT \ --url https://manage.kontent.ai/v2/projects/<YOUR_PROJECT_ID>/items/external-id/123/variants/codename/en-US \ --header "authorization: Bearer <YOUR_API_KEY>" \ --header "content-type: application/json" \ --data " { "elements":{ "title":"On Roasts", "related_articles":[ { "external_id":"456" } ] } }"

    Notice that you are referencing the Donate with us item even though you haven't imported it yet.

    A screenshot of an empty related articles element.

    The reference is not visible inside the Kentico Kontent UI, but it still exists. It will resolve itself once you import the second content item.

    Second content item

    Repeat the same process with the Donate with us article. Start by creating the content item:

    • JavaScript
    // Using ES6 syntax import { ManagementClient } from "@kentico/kontent-management"; const client = new ManagementClient({ projectId: "<YOUR_PROJECT_ID>", apiKey: "<YOUR_API_KEY>" }); client.upsertContentItem() .byItemExternalId("456") .withData( { name: "Donate with us", type: "article" } ) .toObservable() .subscribe((response) => { console.log(response); }, (error) => { console.log(error); });
    // Using ES6 syntax import { ManagementClient } from "@kentico/kontent-management"; const client = new ManagementClient({ projectId: "<YOUR_PROJECT_ID>", apiKey: "<YOUR_API_KEY>" }); client.upsertContentItem() .byItemExternalId("456") .withData( { name: "Donate with us", type: "article" } ) .toObservable() .subscribe((response) => { console.log(response); }, (error) => { console.log(error); });
    • C#
    // Using CM API v1 using KenticoCloud.ContentManagement; ContentManagementOptions options = new ContentManagementOptions { ApiKey = "<YOUR_API_KEY>", ProjectId = "<YOUR_PROJECT_ID>" }; ContentManagementClient client = new ContentManagementClient(options); string externalId = "456"; ContentItemUpsertModel item = new ContentItemUpsertModel() { Name = "Donate with us", Type = ContentTypeIdentifier.ByCodename("article"), SitemapLocations = new[] { SitemapNodeIdentifier.ByCodename("articles") } }; ContentItemModel contentItemResponse = await client.UpsertContentItemByExternalIdAsync(externalId, item);
    // Using CM API v1 using KenticoCloud.ContentManagement; ContentManagementOptions options = new ContentManagementOptions { ApiKey = "<YOUR_API_KEY>", ProjectId = "<YOUR_PROJECT_ID>" }; ContentManagementClient client = new ContentManagementClient(options); string externalId = "456"; ContentItemUpsertModel item = new ContentItemUpsertModel() { Name = "Donate with us", Type = ContentTypeIdentifier.ByCodename("article"), SitemapLocations = new[] { SitemapNodeIdentifier.ByCodename("articles") } }; ContentItemModel contentItemResponse = await client.UpsertContentItemByExternalIdAsync(externalId, item);
    • cURL
    curl --request PUT \ --url https://manage.kontent.ai/v2/projects/<YOUR_PROJECT_ID>/items/external-id/456 \ --header "Authorization: Bearer <YOUR_API_KEY>" \ --header "Content-type: application/json" \ --data " { "name":"Donate with us", "type":{ "codename":"article" } }"
    curl --request PUT \ --url https://manage.kontent.ai/v2/projects/<YOUR_PROJECT_ID>/items/external-id/456 \ --header "Authorization: Bearer <YOUR_API_KEY>" \ --header "Content-type: application/json" \ --data " { "name":"Donate with us", "type":{ "codename":"article" } }"

    This resolves the reference in the On Roasts item.

    Lastly, import content of the Donate with us item by upserting its language variant:

    • JavaScript
    // Using ES6 syntax import { ManagementClient } from "@kentico/kontent-management"; const client = new ManagementClient({ projectId: "<YOUR_PROJECT_ID>", apiKey: "<YOUR_API_KEY>" }); client.upsertLanguageVariant() .byItemExternalId("456") .byLanguageCodename("en-US") .withElements([ { element: { codename: "title" }, value: "Donate with us" }, { element: { codename: "related_articles" }, value: [ { "external_id":"123" } ] } ]) .toObservable() .subscribe((response) => { console.log(response); }, (error) => { console.log(error); });
    // Using ES6 syntax import { ManagementClient } from "@kentico/kontent-management"; const client = new ManagementClient({ projectId: "<YOUR_PROJECT_ID>", apiKey: "<YOUR_API_KEY>" }); client.upsertLanguageVariant() .byItemExternalId("456") .byLanguageCodename("en-US") .withElements([ { element: { codename: "title" }, value: "Donate with us" }, { element: { codename: "related_articles" }, value: [ { "external_id":"123" } ] } ]) .toObservable() .subscribe((response) => { console.log(response); }, (error) => { console.log(error); });
    • C#
    // Using CM API v1 using KenticoCloud.ContentManagement; ContentManagementOptions options = new ContentManagementOptions { ApiKey = "<YOUR_API_KEY>", ProjectId = "<YOUR_PROJECT_ID>" }; ContentManagementClient client = new ContentManagementClient(options); ArticleModel stronglyTypedElements = new ArticleModel { Title = "Donate with us", RelatedArticles = new [] { ContentItemIdentifier.ByExternalId("123") } }; ContentItemIdentifier itemIdentifier = ContentItemIdentifier.ByExternalId("456"); LanguageIdentifier languageIdentifier = LanguageIdentifier.ByCodename("en-US"); ContentItemVariantIdentifier identifier = new ContentItemVariantIdentifier(itemIdentifier, languageIdentifier); ContentItemVariantModel<ArticleModel> responseVariant = await client.UpsertContentItemVariantAsync<ArticleModel>(identifier, stronglyTypedElements);
    // Using CM API v1 using KenticoCloud.ContentManagement; ContentManagementOptions options = new ContentManagementOptions { ApiKey = "<YOUR_API_KEY>", ProjectId = "<YOUR_PROJECT_ID>" }; ContentManagementClient client = new ContentManagementClient(options); ArticleModel stronglyTypedElements = new ArticleModel { Title = "Donate with us", RelatedArticles = new [] { ContentItemIdentifier.ByExternalId("123") } }; ContentItemIdentifier itemIdentifier = ContentItemIdentifier.ByExternalId("456"); LanguageIdentifier languageIdentifier = LanguageIdentifier.ByCodename("en-US"); ContentItemVariantIdentifier identifier = new ContentItemVariantIdentifier(itemIdentifier, languageIdentifier); ContentItemVariantModel<ArticleModel> responseVariant = await client.UpsertContentItemVariantAsync<ArticleModel>(identifier, stronglyTypedElements);
    • cURL
    curl --request PUT \ --url https://manage.kontent.ai/v2/projects/<YOUR_PROJECT_ID>/items/external-id/456/variants/codename/en-US \ --header "authorization: Bearer <YOUR_API_KEY>" \ --header "content-type: application/json" \ --data " { "elements":{ "title":"Donate with us", "related_articles":[ { "external_id":"123" } ] } }"
    curl --request PUT \ --url https://manage.kontent.ai/v2/projects/<YOUR_PROJECT_ID>/items/external-id/456/variants/codename/en-US \ --header "authorization: Bearer <YOUR_API_KEY>" \ --header "content-type: application/json" \ --data " { "elements":{ "title":"Donate with us", "related_articles":[ { "external_id":"123" } ] } }"

    Both references are now resolved. To verify, you can view the imported content items inside Kentico Kontent:

    A visual comparison of two items with linked related articles.

    In the app menu, choose Content & Assets to view the imported content items.

    Validating content

    After your import process is finished you can validate the content of your project using the /validate endpoint. The system checks your project for possible issues, such as references to missing content items or empty required elements. It returns a detailed report.

    • JavaScript
    // Using ES6 syntax import { ManagementClient } from "@kentico/kontent-management"; const client = new ManagementClient({ projectId: "<YOUR_PROJECT_ID>", apiKey: "<YOUR_API_KEY>" }); client.validateProjectContent() .forProjectId("<YOUR_PROJECT_ID>") .toObservable() .subscribe((response) => { console.log(response); }, (error) => { console.log(error); });
    // Using ES6 syntax import { ManagementClient } from "@kentico/kontent-management"; const client = new ManagementClient({ projectId: "<YOUR_PROJECT_ID>", apiKey: "<YOUR_API_KEY>" }); client.validateProjectContent() .forProjectId("<YOUR_PROJECT_ID>") .toObservable() .subscribe((response) => { console.log(response); }, (error) => { console.log(error); });
    • C#
    // Using CM API v1 using KenticoCloud.ContentManagement; using KenticoCloud.ContentManagement.Models.ProjectReport; ContentManagementOptions options = new ContentManagementOptions { ApiKey = "<YOUR_API_KEY>", ProjectId = "<YOUR_PROJECT_ID>" }; ContentManagementClient client = new ContentManagementClient(options); ProjectReportModel response = await client.ValidateProjectAsync();
    // Using CM API v1 using KenticoCloud.ContentManagement; using KenticoCloud.ContentManagement.Models.ProjectReport; ContentManagementOptions options = new ContentManagementOptions { ApiKey = "<YOUR_API_KEY>", ProjectId = "<YOUR_PROJECT_ID>" }; ContentManagementClient client = new ContentManagementClient(options); ProjectReportModel response = await client.ValidateProjectAsync();
    • cURL
    curl --request POST \ --url https://manage.kontent.ai/v2/projects/<YOUR_PROJECT_ID>/validate \ --header "Authorization: Bearer <YOUR_API_KEY>" \ --header "Content-Length: 0" \ --header "Content-Type: application/json" \
    curl --request POST \ --url https://manage.kontent.ai/v2/projects/<YOUR_PROJECT_ID>/validate \ --header "Authorization: Bearer <YOUR_API_KEY>" \ --header "Content-Length: 0" \ --header "Content-Type: application/json" \

    Imagine you never added the second content item. The report would alert you to the fact that you are referencing a non-existent object:

    • JSON
    { "project":{ "id":"a7dfe559-b503-44bf-8164-a495fd14c70e", "name":"Importing linked content tutorial" }, "variant_issues":[ { "item":{ "id":"fc93acd9-3d85-50d9-ab80-c7226c553686", "name":"On Roasts", "codename":"on_roasts" }, "language":{ "id":"00000000-0000-0000-0000-000000000000", "name":"English (United States)", "codename":"en-US" }, "issues":[ { "element":{ "id":"5cc64d60-2045-fa85-10b7-cc6989c39a83", "name":"Related articles", "codename":"related_articles" }, "messages":[ "Element "Related articles" references a non-existent content item e8337cb1-faa3-524a-9bc1-3363c1e283a1." ] } ] } ] }
    { "project":{ "id":"a7dfe559-b503-44bf-8164-a495fd14c70e", "name":"Importing linked content tutorial" }, "variant_issues":[ { "item":{ "id":"fc93acd9-3d85-50d9-ab80-c7226c553686", "name":"On Roasts", "codename":"on_roasts" }, "language":{ "id":"00000000-0000-0000-0000-000000000000", "name":"English (United States)", "codename":"en-US" }, "issues":[ { "element":{ "id":"5cc64d60-2045-fa85-10b7-cc6989c39a83", "name":"Related articles", "codename":"related_articles" }, "messages":[ "Element "Related articles" references a non-existent content item e8337cb1-faa3-524a-9bc1-3363c1e283a1." ] } ] } ] }

    What's next?

    In this tutorial, you have imported two mutually referencing content items using their external IDs.