Work with apps in Release context
This guide explains how to ensure your apps work correctly when entries are being edited within a release context, and how to implement opt-out mechanisms when your app is not compatible with releases.
Table of contents
- Table of contents
- Overview
- Understanding Release Context
- Detecting Release Context
- Opt-out Patterns
- Affected App Locations
- Unsupported Features in Release Context
- Code Examples
Overview
Contentful's Timeline feature allows content editors to manage, preview, and schedule changes to a batch of entries. When an entry is being edited within a release context, some apps may encounter compatibility issues or need to adjust their behavior.
This guide provides practical solutions for handling release contexts in your apps, including opt-out mechanisms for apps that cannot function properly within releases.
Understanding Release Context
When a content editor works with entries in a release, they're editing a release version of the entry that exists separately from the main entry. This has several implications for apps:
- All entry data and references (including
sdk.entry
) are scoped to the release context, not the base entries - Some SDK methods like
publish()
andunpublish()
are not available because the release itself manages the publishing state of all included entries - The
sdk.release
attribute contains information about the current release
Detecting Release Context
The primary way to detect whether your app is running in a release context is by checking the sdk.release
attribute:
import { init } from '@contentful/app-sdk';
init((sdk) => {
if (sdk.release) {
// App is running in release context
console.log('Release title:', sdk.release.title);
console.log('Release start date:', sdk.release.start_date);
} else {
// App is running in normal (base entry) context
console.log('App is running in base entry context');
}
});
Release Object Structure
When in release context, sdk.release
contains the following information:
{
"sys": {
"type": "Release",
"id": "release-id",
"version": 1,
"space": {
"sys": { "type": "Link", "linkType": "Space", "id": "space-id" }
},
"environment": {
"sys": { "type": "Link", "linkType": "Environment", "id": "master" }
},
"createdAt": "2024-11-29T12:00:00.000Z",
"updatedAt": "2024-11-29T12:00:00.000Z",
"createdBy": { "sys": { "type": "Link", "linkType": "User", "id": "user-id" } },
"updatedBy": { "sys": { "type": "Link", "linkType": "User", "id": "user-id" } }
},
"title": "Black Friday Campaign",
"description": "Campaign for Black Friday promotion",
"entities": {
"sys": { "type": "Array" },
"items": [
{
"sys": {
"type": "Link",
"linkType": "Entry", // or "Asset"
"id": "entry-id"
}
}
]
}
}
When not in release context, sdk.release
will be undefined
.
Opt-out patterns
There are several approaches you can take when your app encounters a release context:
Complete opt-out
The simplest approach is to render nothing when in release context:
import React from 'react';
import { useSDK } from '@contentful/react-apps-toolkit';
const Sidebar = () => {
const sdk = useSDK();
// Complete opt-out: render nothing in release context
if (sdk.release) {
return null;
}
// Normal app functionality for base entries
return (
<div>
<h3>My App</h3>
<p>This app works with base entries only.</p>
</div>
);
};
export default Sidebar;
Partial opt-out with fallback UI
Show a user-friendly message explaining why the app is not available:
import React from 'react';
import { Paragraph, Note } from '@contentful/f36-components';
import { useSDK } from '@contentful/react-apps-toolkit';
const FieldEditor = () => {
const sdk = useSDK();
// Partial opt-out: show explanatory message
if (sdk.release) {
return (
<Note variant="warning" title="Not available in releases">
<Paragraph>
This app is not compatible with release entries. Please edit this
field in the base entry or contact your administrator.
</Paragraph>
</Note>
);
}
// Normal field editor functionality
return <div>{/* Your field editor implementation */}</div>;
};
export default FieldEditor;
Conditional feature disabling
Disable specific features while keeping the app partially functional:
import React, { useState } from 'react';
import { Button, Stack, Paragraph } from '@contentful/f36-components';
import { useSDK } from '@contentful/react-apps-toolkit';
const EntryEditor = () => {
const sdk = useSDK();
const [isPublishing, setIsPublishing] = useState(false);
const isInReleaseContext = !!sdk.release;
const handlePublish = async () => {
if (isInReleaseContext) {
sdk.notifier.error('Publishing is not available in release context');
return;
}
setIsPublishing(true);
try {
await sdk.entry.publish();
sdk.notifier.success('Entry published successfully');
} catch (error) {
sdk.notifier.error('Failed to publish entry');
} finally {
setIsPublishing(false);
}
};
return (
<Stack flexDirection="column" spacing="spacingM">
{isInReleaseContext && (
<Paragraph>
You are editing this entry in release context: "{sdk.release.title}"
</Paragraph>
)}
<Button
variant="primary"
isDisabled={isInReleaseContext || isPublishing}
isLoading={isPublishing}
onClick={handlePublish}
>
{isInReleaseContext
? 'Publish (Not available in releases)'
: 'Publish Entry'}
</Button>
{/* Other app functionality that works in both contexts */}
</Stack>
);
};
export default EntryEditor;
Affected App Locations
Apps in the following locations are affected by release contexts:
Location | Constant | Impact |
---|---|---|
Entry Field | LOCATION_ENTRY_FIELD |
Field values are release-scoped |
Entry Sidebar | LOCATION_ENTRY_SIDEBAR |
Entry data is release-scoped |
Entry Editor | LOCATION_ENTRY_EDITOR |
Entire entry is release-scoped |
Dialog | LOCATION_DIALOG |
Inherits release context when opened from entry-related locations above |
Apps in these locations are not affected by releases:
- Page (
LOCATION_PAGE
) - Home (
LOCATION_HOME
) - App Configuration (
LOCATION_APP_CONFIG
)
Dialog Context Inheritance
When a dialog is opened from a release-aware location (Entry Field, Entry Sidebar, or Entry Editor), it inherits the parent's release context. This means:
sdk.release
will be defined in the dialog app- Entity selector dialogs (
selectSingleEntry
,selectMultipleEntries
, etc.) will show release-scoped entities - The dialog app should implement the same release-aware patterns as other entry-related apps
However, if the same dialog is opened from a non-release location (Page, Home, App Configuration), it will not have release context.
Unsupported Features in Release Context
The following SDK methods and features are not available when working with release-scoped entries:
Entry methods
sdk.entry.publish()
- Throws error: "Publish entry method is not supported in release context"sdk.entry.unpublish()
- Throws error: "Unpublish entry method is not supported in release context"
Code examples
Entry field app with release awareness
import React, { useState, useEffect } from 'react';
import { TextInput, FormControl, HelpText } from '@contentful/f36-components';
import { useSDK } from '@contentful/react-apps-toolkit';
const CustomFieldEditor = () => {
const sdk = useSDK();
const [value, setValue] = useState(sdk.field.getValue() || '');
// Handle release context
if (sdk.release) {
return (
<FormControl>
<FormControl.Label>Custom Field (Release Mode)</FormControl.Label>
<TextInput
value={value}
onChange={(e) => {
setValue(e.target.value);
sdk.field.setValue(e.target.value);
}}
/>
<HelpText>Editing in release: {sdk.release.title}</HelpText>
</FormControl>
);
}
// Normal field editor
return (
<FormControl>
<FormControl.Label>Custom Field</FormControl.Label>
<TextInput
value={value}
onChange={(e) => {
setValue(e.target.value);
sdk.field.setValue(e.target.value);
}}
/>
</FormControl>
);
};
export default CustomFieldEditor;
Entry sidebar app with conditional features
import React from 'react';
import { Card, Paragraph, Button, Stack } from '@contentful/f36-components';
import { useSDK } from '@contentful/react-apps-toolkit';
const EntrySidebar = () => {
const sdk = useSDK();
const handleExternalSync = () => {
if (sdk.release) {
sdk.notifier.warning(
'External sync is not available for release entries'
);
return;
}
// Perform external sync
};
return (
<Card>
<Stack flexDirection="column" spacing="spacingM">
<Paragraph>
{sdk.release ? `Release: ${sdk.release.title}` : 'Base Entry'}
</Paragraph>
<Button
size="small"
isDisabled={!!sdk.release}
onClick={handleExternalSync}
>
Sync to External System
</Button>
{sdk.release && (
<Paragraph marginTop="spacingS" fontColor="gray600">
Some features are disabled in release context
</Paragraph>
)}
</Stack>
</Card>
);
};
export default EntrySidebar;