Previewing unpublished content

In this article, you'll learn about a general approach to running your apps in two environments and using the preview environment to preview content from Kentico Kontent. This will enable your content contributors to preview unpublished content directly from your Kentico Kontent project.

To preview your content from your Kentico Kontent project, you will make use of the Delivery Preview API. The Delivery Preview API returns the latest versions of content items allowing your content producers to view content items before they publish them or preview changes made to already published content items.

Table of contents

    Content workflow

    When content contributors create new content items (such as articles) in the project, the content items begin their content workflow in the Draft step.

    A new content item at the beginning of its workflow in the content list.

    Right after being created, the new content items become available via the Delivery Preview API.

    Using the Delivery Preview API

    Every request to the Delivery Preview API must be authenticated with an API key. There are two concurrent keys, Primary and Secondary, unique for each project in Kentico Kontent.

    Note: For continuous use, we recommend using the Primary key to authenticate your API requests and only using the Secondary key when revoking the Primary key to prevent your preview environment from any downtime. Both API keys are generated per project and have no expiration date.

    Primary vs. Secondary key

    The following instructions work equally for both the Primary key and the Secondary key.

    To get the key:

    1. In Kentico Kontent, choose a project.
    2. From the app menu, choose Project settings .
    3. Under Development, choose API keys.
    4. In the Delivery Preview API box, click Copy to clipboard  to copy the API key.

    You can now use the Primary key to authenticate your requests to the API. The base URL of the Preview Delivery API is https://preview-deliver.kontent.ai/<YOUR_PROJECT_ID>/.

    The following example shows how you can get the latest version of the "On Roasts" article, as shown in the image above.

    • Java
    import com.kenticocloud.delivery_core.*; import com.kenticocloud.delivery_rx.*; import io.reactivex.Observer; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Function; // Prepares an array to hold strongly-typed models List<TypeResolver<?>> typeResolvers = new ArrayList<>(); // Registers the type resolver for articles typeResolvers.add(new TypeResolver<>(Article.TYPE, new Function<Void, Article>() { @Override public Article apply(Void input) { return new Article(); } })); // Prepares the DeliveryService configuration object String projectId = "<YOUR_PROJECT_ID>"; String previewApiKey = "<YOUR_PREVIEW_API_KEY>"; IDeliveryConfig config = DeliveryConfig.newConfig(projectId) .withTypeResolvers(typeResolvers) .withPreviewApiKey(previewApiKey); // Initializes a DeliveryService for Java projects IDeliveryService deliveryService = new DeliveryService(config); // Gets the latest version of a content item using a simple request Article item = deliveryService.<Article>item("on_roasts") .get() .getItem(); // Gets the latest version of a content item using RxJava2 deliveryService.<Article>item("on_roasts") .getObservable() .subscribe(new Observer<DeliveryItemResponse<Article>>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(DeliveryItemResponse<Article> response) { // Gets the article Article item = response.getItem(); } @Override public void onError(Throwable e) { } @Override public void onComplete() { } });
    import com.kenticocloud.delivery_core.*; import com.kenticocloud.delivery_rx.*; import io.reactivex.Observer; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Function; // Prepares an array to hold strongly-typed models List<TypeResolver<?>> typeResolvers = new ArrayList<>(); // Registers the type resolver for articles typeResolvers.add(new TypeResolver<>(Article.TYPE, new Function<Void, Article>() { @Override public Article apply(Void input) { return new Article(); } })); // Prepares the DeliveryService configuration object String projectId = "<YOUR_PROJECT_ID>"; String previewApiKey = "<YOUR_PREVIEW_API_KEY>"; IDeliveryConfig config = DeliveryConfig.newConfig(projectId) .withTypeResolvers(typeResolvers) .withPreviewApiKey(previewApiKey); // Initializes a DeliveryService for Java projects IDeliveryService deliveryService = new DeliveryService(config); // Gets the latest version of a content item using a simple request Article item = deliveryService.<Article>item("on_roasts") .get() .getItem(); // Gets the latest version of a content item using RxJava2 deliveryService.<Article>item("on_roasts") .getObservable() .subscribe(new Observer<DeliveryItemResponse<Article>>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(DeliveryItemResponse<Article> response) { // Gets the article Article item = response.getItem(); } @Override public void onError(Throwable e) { } @Override public void onComplete() { } });
    • Swift
    import KenticoCloud let client = DeliveryClient.init(projectId: "<YOUR_PROJECT_ID>", apiKey: "<YOUR_PREVIEW_API_KEY>") // More about strongly-typed models https://github.com/Kentico/cloud-sdk-swift#using-strongly-typed-models client.getItem(modelType: Article.self, itemName: "on_roasts") { (isSuccess, itemResponse, error) in if isSuccess { if let article = itemResponse.item { // Use your item here } } else { if let error = error { print(error) } } }
    import KenticoCloud let client = DeliveryClient.init(projectId: "<YOUR_PROJECT_ID>", apiKey: "<YOUR_PREVIEW_API_KEY>") // More about strongly-typed models https://github.com/Kentico/cloud-sdk-swift#using-strongly-typed-models client.getItem(modelType: Article.self, itemName: "on_roasts") { (isSuccess, itemResponse, error) in if isSuccess { if let article = itemResponse.item { // Use your item here } } else { if let error = error { print(error) } } }
    • Java
    import com.kenticocloud.delivery; DeliveryClient client = new DeliveryClient("<YOUR_PROJECT_ID>", "<YOUR_PREVIEW_API_KEY>"); ContentItem item = client.getItem("on_roasts").item;
    import com.kenticocloud.delivery; DeliveryClient client = new DeliveryClient("<YOUR_PROJECT_ID>", "<YOUR_PREVIEW_API_KEY>"); ContentItem item = client.getItem("on_roasts").item;
    • JavaScript
    const KontentDelivery = require("@kentico/kontent-delivery"); // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models class Article extends KontentDelivery.ContentItem { constructor() { super(); } } const deliveryClient = new KontentDelivery.DeliveryClient({ projectId: "<YOUR_PROJECT_ID>", previewApiKey: "<YOUR_PREVIEW_API_KEY>", globalQueryConfig: { usePreviewMode: true, // Queries the Delivery Preview API. }, typeResolvers: [ new KontentDelivery.TypeResolver("article", (rawData) => new Article) ] }); deliveryClient.item("on_roasts") .toObservable() .subscribe(response => console.log(response));
    const KontentDelivery = require("@kentico/kontent-delivery"); // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models class Article extends KontentDelivery.ContentItem { constructor() { super(); } } const deliveryClient = new KontentDelivery.DeliveryClient({ projectId: "<YOUR_PROJECT_ID>", previewApiKey: "<YOUR_PREVIEW_API_KEY>", globalQueryConfig: { usePreviewMode: true, // Queries the Delivery Preview API. }, typeResolvers: [ new KontentDelivery.TypeResolver("article", (rawData) => new Article) ] }); deliveryClient.item("on_roasts") .toObservable() .subscribe(response => console.log(response));
    • C#
    using Kentico.Kontent.Delivery; // Initializes a content delivery client for previewing content IDeliveryClient client = DeliveryClientBuilder .WithOptions(builder => builder .WithProjectId("975bf280-fd91-488c-994c-2f04416e5ee3") .UsePreviewApi("<YOUR_PREVIEW_API_KEY>") .Build()) .Build(); // Gets the latest version of a content item // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models DeliveryItemResponse<object> response = await client.GetItemAsync<object>("on_roasts"); var item = response.Item;
    using Kentico.Kontent.Delivery; // Initializes a content delivery client for previewing content IDeliveryClient client = DeliveryClientBuilder .WithOptions(builder => builder .WithProjectId("975bf280-fd91-488c-994c-2f04416e5ee3") .UsePreviewApi("<YOUR_PREVIEW_API_KEY>") .Build()) .Build(); // Gets the latest version of a content item // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models DeliveryItemResponse<object> response = await client.GetItemAsync<object>("on_roasts"); var item = response.Item;
    • PHP
    <?php // Defined by Composer to include required libraries require __DIR__ . "/vendor/autoload.php"; use Kentico\Kontent\Delivery\DeliveryClient; $client = new DeliveryClient("<YOUR_PROJECT_ID>", "<YOUR_PREVIEW_API_KEY>"); $item = $client->getItem("on_roasts");
    <?php // Defined by Composer to include required libraries require __DIR__ . "/vendor/autoload.php"; use Kentico\Kontent\Delivery\DeliveryClient; $client = new DeliveryClient("<YOUR_PROJECT_ID>", "<YOUR_PREVIEW_API_KEY>"); $item = $client->getItem("on_roasts");
    • cURL
    curl --request GET \ --url https://preview-deliver.kontent.ai/<YOUR_PROJECT_ID>/items/on_roasts \ --header "authorization: Bearer <YOUR_PREVIEW_API_KEY>"
    curl --request GET \ --url https://preview-deliver.kontent.ai/<YOUR_PROJECT_ID>/items/on_roasts \ --header "authorization: Bearer <YOUR_PREVIEW_API_KEY>"
    • Ruby
    require "delivery-sdk-ruby" delivery_client = Kentico::Kontent::Delivery::DeliveryClient.new project_id: "975bf280-fd91-488c-994c-2f04416e5ee3", preview_key: "<YOUR_PREVIEW_API_KEY>" delivery_client.item("on_roasts").execute do |response| item = response.item end
    require "delivery-sdk-ruby" delivery_client = Kentico::Kontent::Delivery::DeliveryClient.new project_id: "975bf280-fd91-488c-994c-2f04416e5ee3", preview_key: "<YOUR_PREVIEW_API_KEY>" delivery_client.item("on_roasts").execute do |response| item = response.item end
    • TypeScript
    import { ContentItem, DeliveryClient, Elements, TypeResolver } from "@kentico/kontent-delivery"; // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models export class Article extends ContentItem { public title: Elements.TextElement; public summary: Elements.TextElement; public post_date: Elements.DateTimeElement; public teaser_image: Elements.AssetsElement; public related_articles: Article[]; } const deliveryClient = new DeliveryClient({ projectId: "<YOUR_PROJECT_ID>", previewApiKey: "<YOUR_PREVIEW_API_KEY>", globalQueryConfig: { usePreviewMode: true, // Queries the Delivery Preview API. }, typeResolvers: [ new TypeResolver("article", (rawData) => new Article) ] }); deliveryClient.item<Article>("on_roasts") .toObservable() .subscribe(response => console.log(response));
    import { ContentItem, DeliveryClient, Elements, TypeResolver } from "@kentico/kontent-delivery"; // Create strongly typed models according to https://docs.kontent.ai/strongly-typed-models export class Article extends ContentItem { public title: Elements.TextElement; public summary: Elements.TextElement; public post_date: Elements.DateTimeElement; public teaser_image: Elements.AssetsElement; public related_articles: Article[]; } const deliveryClient = new DeliveryClient({ projectId: "<YOUR_PROJECT_ID>", previewApiKey: "<YOUR_PREVIEW_API_KEY>", globalQueryConfig: { usePreviewMode: true, // Queries the Delivery Preview API. }, typeResolvers: [ new TypeResolver("article", (rawData) => new Article) ] }); deliveryClient.item<Article>("on_roasts") .toObservable() .subscribe(response => console.log(response));

    As more people work on content, the content items progress through their workflow and, when all requirements are met, can be scheduled for publishing or published right away. Published items then become available via the Delivery API.

    Preview and live environments

    To preview unpublished content in your app, we recommend running the app in its own environment separated from the live version of the app.

    The main difference is that the app running in the live environment uses the Delivery API, while the app in the preview environment uses the Delivery Preview API. To differentiate between the environments in your app, you can, for instance, use environment variables. The way to check the environment variables will depend on the service you use to deploy your apps.

    For example, when using one of our Delivery SDKs, the API to use is often determined by providing or omitting the Preview API key. Note that server-side technology (such as ASP.NET MVC, Node.js with the Express.js framework, or others) is required to keep the Preview API key secret.

    In practice, your application can check whether it's run in a preview environment, set a boolean flag based on the check, and determine whether to use the Delivery Preview API.

    • Java
    import com.kenticocloud.delivery_core.*; import com.kenticocloud.delivery_rx.*; import io.reactivex.functions.Function; // TODO: Determine whether the app is running in a preview environment boolean isPreview = ...; // Prepares the DeliveryService configuration object String projectId = "<YOUR_PROJECT_ID>"; String previewApiKey = "<YOUR_PREVIEW_API_KEY>"; IDeliveryConfig config = DeliveryConfig.newConfig(projectId) .withTypeResolvers(typeResolvers) .withPreviewApiKey(isPreview ? previewApiKey : null); // Initializes a DeliveryService for Java projects IDeliveryService deliveryService = new DeliveryService(config);
    import com.kenticocloud.delivery_core.*; import com.kenticocloud.delivery_rx.*; import io.reactivex.functions.Function; // TODO: Determine whether the app is running in a preview environment boolean isPreview = ...; // Prepares the DeliveryService configuration object String projectId = "<YOUR_PROJECT_ID>"; String previewApiKey = "<YOUR_PREVIEW_API_KEY>"; IDeliveryConfig config = DeliveryConfig.newConfig(projectId) .withTypeResolvers(typeResolvers) .withPreviewApiKey(isPreview ? previewApiKey : null); // Initializes a DeliveryService for Java projects IDeliveryService deliveryService = new DeliveryService(config);
    • Swift
    import KenticoCloud // TODO: Determine whether the app is running in a preview environment let isPreview = ... let previewKey = isPreview ? "<YOUR_PREVIEW_API_KEY>" : nil let client = DeliveryClient.init(projectId: "<YOUR_PROJECT_ID>", apiKey: previewKey)
    import KenticoCloud // TODO: Determine whether the app is running in a preview environment let isPreview = ... let previewKey = isPreview ? "<YOUR_PREVIEW_API_KEY>" : nil let client = DeliveryClient.init(projectId: "<YOUR_PROJECT_ID>", apiKey: previewKey)
    • Java
    import com.kenticocloud.delivery; // TODO: Determine whether the app is running in a preview environment boolean isPreview = ...; DeliveryOptions deliveryOptions = new DeliveryOptions(); deliveryOptions.setProjectId("<YOUR_PROJECT_ID>"); deliveryOptions.setPreviewApiKey("<YOUR_PREVIEW_API_KEY>"); deliveryOptions.setUsePreviewApi(isPreview ? true : false); DeliveryClient client = new DeliveryClient(deliveryOptions);
    import com.kenticocloud.delivery; // TODO: Determine whether the app is running in a preview environment boolean isPreview = ...; DeliveryOptions deliveryOptions = new DeliveryOptions(); deliveryOptions.setProjectId("<YOUR_PROJECT_ID>"); deliveryOptions.setPreviewApiKey("<YOUR_PREVIEW_API_KEY>"); deliveryOptions.setUsePreviewApi(isPreview ? true : false); DeliveryClient client = new DeliveryClient(deliveryOptions);
    • JavaScript
    const KontentDelivery = require("@kentico/kontent-delivery"); // TODO: Determine whether the app is running in a preview environment const isPreview = ...; const deliveryClient = new KontentDelivery.DeliveryClient({ projectId: "<YOUR_PROJECT_ID", previewApiKey: "<YOUR_PREVIEW_API_KEY>", globalQueryConfig: { usePreviewMode: isPreview, // Queries the Delivery Preview API. }, typeResolvers: [] });
    const KontentDelivery = require("@kentico/kontent-delivery"); // TODO: Determine whether the app is running in a preview environment const isPreview = ...; const deliveryClient = new KontentDelivery.DeliveryClient({ projectId: "<YOUR_PROJECT_ID", previewApiKey: "<YOUR_PREVIEW_API_KEY>", globalQueryConfig: { usePreviewMode: isPreview, // Queries the Delivery Preview API. }, typeResolvers: [] });
    • C#
    using Kentico.Kontent.Delivery; // TODO: Determine whether the app is running in a preview environment bool isPreview = ...; // Prepares connection options for the content delivery client DeliveryOptions options = new DeliveryOptions() { ProjectId = "975bf280-fd91-488c-994c-2f04416e5ee3", PreviewApiKey = "<YOUR_PREVIEW_API_KEY>", // Determines whether to use the Delivery Preview API UsePreviewApi = isPreview }; // Ensures the specified options configuration is valid options.Validate(); // Initializes a content delivery client IDeliveryClient client = DeliveryClientBuilder .WithOptions(_ => options) .Build();
    using Kentico.Kontent.Delivery; // TODO: Determine whether the app is running in a preview environment bool isPreview = ...; // Prepares connection options for the content delivery client DeliveryOptions options = new DeliveryOptions() { ProjectId = "975bf280-fd91-488c-994c-2f04416e5ee3", PreviewApiKey = "<YOUR_PREVIEW_API_KEY>", // Determines whether to use the Delivery Preview API UsePreviewApi = isPreview }; // Ensures the specified options configuration is valid options.Validate(); // Initializes a content delivery client IDeliveryClient client = DeliveryClientBuilder .WithOptions(_ => options) .Build();
    • PHP
    <?php // Defined by Composer to include required libraries require __DIR__ . "/vendor/autoload.php"; use Kentico\Kontent\Delivery\DeliveryClient; // TODO: Determine whether the app is running in a preview environment $isPreview = ...; $client = new DeliveryClient("<YOUR_PROJECT_ID>", $isPreview ? "<YOUR_PREVIEW_API_KEY>" : null);
    <?php // Defined by Composer to include required libraries require __DIR__ . "/vendor/autoload.php"; use Kentico\Kontent\Delivery\DeliveryClient; // TODO: Determine whether the app is running in a preview environment $isPreview = ...; $client = new DeliveryClient("<YOUR_PROJECT_ID>", $isPreview ? "<YOUR_PREVIEW_API_KEY>" : null);
    • Ruby
    require "delivery-sdk-ruby" is_preview = false # Determine whether the app is running in a preview environment delivery_client = Kentico::Kontent::Delivery::DeliveryClient.new project_id: "975bf280-fd91-488c-994c-2f04416e5ee3", preview_key: "<YOUR_PREVIEW_API_KEY>" delivery_client.use_preview = is_preview delivery_client.items.execute do |response| items = response.items end
    require "delivery-sdk-ruby" is_preview = false # Determine whether the app is running in a preview environment delivery_client = Kentico::Kontent::Delivery::DeliveryClient.new project_id: "975bf280-fd91-488c-994c-2f04416e5ee3", preview_key: "<YOUR_PREVIEW_API_KEY>" delivery_client.use_preview = is_preview delivery_client.items.execute do |response| items = response.items end
    • TypeScript
    import { DeliveryClient } from "@kentico/kontent-delivery"; // TODO: Determine whether the app is running in a preview environment const isPreview = ...; const deliveryClient = new DeliveryClient({ projectId: "<YOUR_PROJECT_ID", previewApiKey: "<YOUR_PREVIEW_API_KEY>", globalQueryConfig: { usePreviewMode: isPreview, // Queries the Delivery Preview API. }, typeResolvers: [] });
    import { DeliveryClient } from "@kentico/kontent-delivery"; // TODO: Determine whether the app is running in a preview environment const isPreview = ...; const deliveryClient = new DeliveryClient({ projectId: "<YOUR_PROJECT_ID", previewApiKey: "<YOUR_PREVIEW_API_KEY>", globalQueryConfig: { usePreviewMode: isPreview, // Queries the Delivery Preview API. }, typeResolvers: [] });

    Content preview in Kentico Kontent

    With your application running in two separate environments, you can configure preview URLs for the content types in your project and preview content that is not yet published with one click from Kentico Kontent.

    Tip: See how to configure previews inside Kentico Kontent.

    Once you set up the URLs, your content contributors can use a preview button when editing new versions of content items. Note that the button is not shown for already published content items.

    Changing your project structure

    If you change any of the project models (this includes content types, content type snippets, and taxonomy groups), the changes are immediately reflected in the content items.

    This means that changes to the project structure cannot be separately tested in preview environment because they directly affect the live environment.

    Continuous development

    Deploying changes in the structure of a project to another project in order to, for example, test new content layouts, currently cannot be done.

    We're working on bringing the support for continuous development to Kentico Kontent in the near future. See our Roadmap to learn more.

    What's next?

    In this article, you've learned how to set up a preview environment using the Preview API. Once you've configured previews in your project, your content contributors can preview their new content in a separate preview environment before it's published.