Appearance
Are you an LLM? You can read better optimized documentation at /features/deep_linking.md for this page in Markdown format
Deep linking
This document provides a technical overview of the deep linking feature, which enables linking directly from external systems, such as the Contentful web app, into the application. This mechanism allows for dynamic configuration of the Contentful context (space, API tokens, and preview mode) on a per-session basis, primarily to support editorial workflows and content previews.
Overview
The deep linking feature allows the application to interpret specific query string parameters in an incoming URL. These parameters can temporarily override the application's default Contentful configuration for the duration of a user's session. This is essential for features like Contentful's "Open in website" preview, where an editor needs to view draft or unpublished content rendered within the live application's layout.
The core functionality is handled by a custom ASP.NET Core middleware, which reads the parameters, validates them, and stores the resulting configuration in the user's session state. Subsequent requests within that session will then use this session-specific configuration to fetch content from Contentful.
Deeplinker Middleware
The heart of this feature is the Deeplinker middleware. It is registered in the application's request pipeline in Startup.cs.
csharp
// File: TheExampleApp/Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// ... other middleware registrations
app.UseSession();
// ...
app.UseDeeplinks();
app.UseMvc(/* ... */);
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
The middleware intercepts every incoming request to inspect its query string.
Implementation Details
The Deeplinker.cs file contains the middleware logic. Its Invoke method performs the following actions:
Parameter Detection: It checks for the presence of
space_id,preview_token, anddelivery_tokento detect a full configuration override. It also checks forapi(to toggle preview mode) andeditorial_features(to enable UI indicators).Configuration Override: If a full set of credentials is provided, it constructs a new
ContentfulOptionsobject. This object is then validated. Upon successful validation, it is serialized to a JSON string and stored in the user's session.Session-Based Configuration: The
ContentfulOptionsManageris a custom service that retrievesContentfulOptions. It prioritizes loading options from the session, falling back to the application'sappsettings.jsonconfiguration only if no session data is found. This design is what allows the deep link to dynamically reconfigure the application. For more details on service configuration, see the Services Configuration documentation.csharp// File: TheExampleApp/Configuration/ContentfulOptionsManager.cs /// <summary> /// Gets the currently configured <see cref="ContentfulOptions"/> either from session, if present, or from the application configuration. /// </summary> public ContentfulOptions Options { get { var sessionString = _accessor.HttpContext.Session.GetString(nameof(ContentfulOptions)); if (!string.IsNullOrEmpty(sessionString)) { return JsonConvert.DeserializeObject<ContentfulOptions>(sessionString); } return _options; } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16Editorial Features Flag: The
editorial_featuresparameter is handled separately and its state ("Enabled" or "Disabled") is stored in the session. This flag is used by UI components to decide whether to render editorial information, such as the entry state indicators.
csharp
// File: TheExampleApp/Configuration/Deeplinker.cs
public async Task Invoke(HttpContext context)
{
var query = context.Request.Query;
// Check for a full credential override from the query string
if (query.ContainsKey("space_id") && query.ContainsKey("preview_token") && query.ContainsKey("delivery_token"))
{
var currentOptions = new ContentfulOptions();
currentOptions.DeliveryApiKey = query["delivery_token"];
currentOptions.SpaceId = query["space_id"];
currentOptions.PreviewApiKey = query["preview_token"];
currentOptions.UsePreviewApi = query.ContainsKey("api") && query["api"] == "cpa";
// ... validation logic ...
// Store the validated options in the session
context.Session.SetString(nameof(ContentfulOptions), JsonConvert.SerializeObject(currentOptions));
}
// ... logic for handling 'api' parameter alone ...
// Check for the editorial features flag
if (query.ContainsKey("editorial_features") && string.Equals(query["editorial_features"], "enabled", StringComparison.InvariantCultureIgnoreCase))
{
context.Session.SetString("EditorialFeatures", "Enabled");
}
else if(query.ContainsKey("editorial_features"))
{
context.Session.SetString("EditorialFeatures", "Disabled");
}
await _next(context);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Link Generation
To activate the deep linking feature, a URL must be constructed with the correct query string parameters. These links are typically configured in the Contentful web app's content type settings under "Previews".
The following parameters are supported:
| Parameter | Description | Example Value |
|---|---|---|
space_id | The ID of the Contentful space to connect to. | cfexampleapi |
delivery_token | The Content Delivery API (CDA) token for the space. | b4c0n73n7fu1 |
preview_token | The Content Preview API (CPA) token for the space. | e5e8d4c5c122cf92301ad578 |
api | Specifies which API to use. Set to cpa to use the Preview API. If omitted, defaults to the Delivery API. | cpa |
editorial_features | Set to enabled to display editorial UI elements like entry status indicators. | enabled |
Example URL:
https://www.the-example-app.com/courses/hello-contentful/lessons/apis?space_id=cfexampleapi&delivery_token=b4c0n73n7fu1&preview_token=e5e8d4c5c122cf92301ad578&api=cpa&editorial_features=enabled1
This URL will navigate to a specific lesson page and configure the user's session to:
- Use the Contentful space
cfexampleapi. - Fetch content using the Content Preview API.
- Enable the rendering of editorial UI components.
Entry State Indicator
When editorial_features are enabled via a deep link, the application renders visual indicators ("pills") that show the publication status of the content being viewed. This provides immediate feedback to editors about whether the content is a draft or has unpublished changes.
The indicators are rendered by the EntryStateViewComponent and can show two states:
- Draft: This indicates that the entry exists in the Contentful Preview API but has not been published to the Delivery API.
- Pending Changes: This indicates that the entry is published, but there is a newer version saved in Contentful that has not yet been published.
The visual representation is defined in the component's view:
razor
// File: TheExampleApp/Views/Shared/Components/EntryState/Default.cshtml
@model TheExampleApp.ViewComponents.EntryStateModel
@if (Model.Draft)
{
<div class="editorial-features__item">
<div class="pill pill--draft">
@Localizer["draftLabel"]
</div>
</div>
}
@if (Model.PendingChanges)
{
<div class="editorial-features__item">
<div class="pill pill--pending-changes">
@Localizer["pendingChangesLabel"]
</div>
</div>
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
EntryStateViewComponent
This component contains the logic for determining the publication status of one or more Contentful entries. It is a self-contained, reusable component as described in the View Components documentation.
Logic and Implementation
The InvokeAsync method of the EntryStateViewComponent is responsible for comparing the state of an entry from the Preview API against the Delivery API.
Input: The component receives the
SystemPropertiesof the entry (or entries) currently being displayed on the page. These properties are typically from a preview context if the deep link usedapi=cpa.Internal Client: It instantiates its own
ContentfulClientthat is explicitly configured to use the Delivery API (usePreviewApi: false). This is a critical design choice, as it ensures the component always compares against the live, published state, regardless of the session's configuration.State Determination:
- Draft Check: It attempts to fetch the entry from the Delivery API using its ID. If the API call throws an exception or returns a null/empty result, the component concludes the entry is not published and therefore is a draft.
- Pending Changes Check: If the entry is found in the Delivery API, the component compares the
UpdatedAttimestamp of the preview version (passed into the component) with theUpdatedAttimestamp of the published version. A helper function,TrimMilliseconds, is used to prevent precision-related comparison errors. If the timestamps do not match, the entry has pending changes.
csharp
// File: TheExampleApp/ViewComponents/EntryStateViewComponent.cs
public async Task<IViewComponentResult> InvokeAsync(IEnumerable<SystemProperties> sys)
{
// Create a new client with preview set to false to always get the entry from the delivery API.
var client = new ContentfulClient(_httpClient, _options.DeliveryApiKey, _options.PreviewApiKey, _options.SpaceId, false);
IEnumerable<EntryStateModel> entries = null;
var model = new EntryStateModel();
try
{
// Try getting the entry by the specified Id. If it throws or returns null the entry is not published.
entries = await client.GetEntries<EntryStateModel>($"?sys.id[in]={string.Join(",", sys.Select(c => c.Id))}");
}
catch { }
if(entries == null || (entries.Any() == false || sys.Select(c => c.Id).Except(entries.Select(e => e.Sys.Id)).Any()))
{
// One of the entries are not published, thus it is in draft mode.
model.Draft = true;
}
if (entries != null && AnySystemPropertiesNotMatching(entries.Select(c => c.Sys),sys))
{
// The entry is published but the UpdatedAt dates do not match, thus it must have pending changes.
model.PendingChanges = true;
}
return View(model);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Use Cases
The deep linking functionality serves several key purposes for both editorial and development teams:
- Content Previews: This is the primary use case. Content editors can use the "Open in website" feature in the Contentful web app to see exactly how their draft or modified content will look on the live site before publishing.
- Cross-Environment Testing: Developers or QA testers can easily point the application to different Contentful environments (e.g., a development or staging space) by simply crafting the correct URL. This avoids the need to change local configuration files or redeploy the application.
- Stakeholder Reviews: A deep link can be shared with stakeholders to allow them to review content that is not yet live, providing a seamless in-context review experience.
- Debugging: Developers can quickly switch between the Preview and Delivery APIs to debug content-related issues by appending
?api=cpaor?api=cdato a URL.
Contentful Space/Environment
The deep linking architecture makes the application highly adaptable to different Contentful configurations. By leveraging session state to store ContentfulOptions, the application is not tied to a single, static Contentful space defined at build time.
This dynamic reconfiguration is fundamental to the application's integration with Contentful, allowing a single deployed instance to serve content from multiple sources based on the context provided in a URL. For a broader understanding of how the application interacts with Contentful, refer to the Contentful Integration architecture document.
The Deeplinker middleware includes validation to ensure that the provided tokens and space ID are well-formed, preventing runtime errors and redirecting the user to a settings page if the credentials fail validation. This provides a robust mechanism for safely and dynamically altering the application's content source.