Forge rolling releases is available through Forge's Early Access Program (EAP). This feature is currently only available in Jira and Confluence.
EAP grants selected users early testing access for feedback. APIs and features in EAP are experimental, unsupported, and subject to change without notice. To participate in the rolling releases EAP, sign up here.
For more details, see Forge EAP, Preview, and GA.
This tutorial walks you through onboarding your app to rolling releases, using the frontend and backend permissions SDK, and testing how your app behaves in a decoupled state.
By completing this guide, you will build an app for which you can upgrade only the code version in your test installation.
Before you begin, ensure you have the following:
A configured Forge development environment (Getting Started Guide)
Access to an Atlassian site for app installation (create one if needed)
You have been onboarded to the EAP
Install the next version of CLI and authenticate
1 2# Install next (pre-release) version of CLI npm i -g @forge/cli@next # Ensure you are authenticated by running forge whoami # If not authenticated then login to authenticate forge login
Use the Forge CLI to create your app. In this example, we'll name it rolling-release-confluence:
Authenticate with Forge if you haven't already:
1 2forge login
Create your app:
1 2forge create # ? Enter a name for your app: rolling-release-confluence # ? Select an Atlassian app or platform tool: Confluence # ? Select a category: UI Kit # ? Select a template: confluence-macro
Raise a request to enable rolling releases for your test app.
Navigate to your app directory:
1 2cd rolling-release-confluence
Update the permissions in the app manifest to add enforcement: app-managed.
manifest.yml file.permissions section with enforcement: app-managed:Your manifest.yml file should look like the following:
1 2modules: macro: - key: rolling-release-macro resource: main render: native resolver: function: resolver title: Hello World! function: - key: resolver handler: index.handler resources: - key: main path: src/frontend/index.jsx app: runtime: name: nodejs22.x memoryMB: 256 architecture: arm64 permissions: # Enable rolling releases for the app enforcement: app-managed
The enforcement: app-managed setting enables rolling releases for your app, allowing you to decouple code updates from permission approvals.
Update your app code to display the currently installed app version. This helps verify which version is running.
Open src/frontend/index.jsx.
Update the imports to include view from @forge/bridge:
1 2import { invoke, view } from '@forge/bridge';
Add state to store the context and a useEffect to fetch it:
1 2const [context, setContext] = useState({}); useEffect(() => { const fetchContext = async () => { setContext(await view.getContext()); }; fetchContext(); }, []);
Display the app version in the return statement:
Your complete src/frontend/index.jsx should look like this:
1 2import React, { useEffect, useState } from 'react'; import ForgeReconciler, { Text } from '@forge/react'; import { invoke, view } from '@forge/bridge'; const App = () => { const [data, setData] = useState(null); const [context, setContext] = useState({}); useEffect(() => { invoke('getText', { example: 'my-invoke-variable' }).then(setData); }, []); useEffect(() => { const fetchContext = async () => { setContext(await view.getContext()); }; fetchContext(); }, []); return ( <> <Text>Hello world!</Text> <Text>Installed app version: {context.appVersion}</Text> <Text>{data ? data : 'Loading...'}</Text> </> ); }; ForgeReconciler.render( <React.StrictMode> <App /> </React.StrictMode> );
Deploy the app by running:
1 2forge deploy
Install the app in your test instance:
1 2forge install
Follow the prompts to select your Confluence site.
View the app in Confluence by creating or editing a page and inserting your macro.
When viewing the app you should see the major version of the app installed (e.g., version 2.0.0).
Now we'll add a permission scope to demonstrate rolling releases behavior.
manifest.yml.scopes section under permissions with the read:space:confluence scope:1 2permissions: enforcement: app-managed scopes: # added a new scope - read:space:confluence
Update your app to fetch and display Confluence spaces.
Open src/frontend/index.jsx.
Update your imports to include CodeBlock from @forge/react and requestConfluence from @forge/bridge:
1 2import ForgeReconciler, { Text, CodeBlock } from '@forge/react'; import { invoke, view, requestConfluence } from '@forge/bridge';
Add a ShowSpaces component that fetches all spaces:
1 2// Added Component to fetch all spaces and render them const ShowSpaces = () => { const [isLoading, setIsLoading] = useState(true); const [spaces, setSpaces] = useState([]); useEffect(() => { const fetchSpaces = async () => { try { setIsLoading(true); const response = await requestConfluence(`/wiki/api/v2/spaces`, { headers: { Accept: "application/json", }, }); const data = await response.json(); setSpaces(data.results || []); } catch (error) { console.error('Failed to fetch spaces:', error); setSpaces([]); } finally { setIsLoading(false); } }; fetchSpaces(); }, []); if (isLoading) { return <Text>Loading spaces...</Text>; } return <CodeBlock text={JSON.stringify(spaces, undefined, 2)} language="json" />; };
App component to include ShowSpaces:1 2return ( <> <Text>Hello world!</Text> <Text>Installed app version: {context.appVersion}</Text> {/* Show all the spaces in the instance */} <ShowSpaces /> <Text>{data ? data : 'Loading...'}</Text> </> );
Deploy the app with the new scope:
1 2forge deploy
At this point, your app is in a state where the latest version requires new permissions. App installations will invoke the older version until admins manually update the app and grant permissions.
Let's see how rolling releases will help us handle such upgrades.
Use the usePermissions hook in the app frontend to gracefully handle missing permissions.
Open src/frontend/index.jsx.
Add usePermissions to your @forge/react imports:
1 2import ForgeReconciler, { Text, CodeBlock, usePermissions } from '@forge/react';
In the App component, add the usePermissions hook before the useEffect calls:
1 2// Check if read:space:confluence permission is granted const { hasPermission, isLoading: permissionsLoading } = usePermissions({ scopes: ["read:space:confluence"], });
Update the return statement to conditionally render ShowSpaces based on permission status:
Your complete src/frontend/index.jsx should now look like this:
1 2import React, { useEffect, useState } from 'react'; import ForgeReconciler, { Text, CodeBlock, usePermissions } from '@forge/react'; import { invoke, view, requestConfluence } from '@forge/bridge'; // Added Component to fetch all spaces and render them const ShowSpaces = () => { const [isLoading, setIsLoading] = useState(true); const [spaces, setSpaces] = useState([]); useEffect(() => { const fetchSpaces = async () => { try { setIsLoading(true); const response = await requestConfluence(`/wiki/api/v2/spaces`, { headers: { Accept: "application/json", }, }); const data = await response.json(); setSpaces(data.results || []); } catch (error) { console.error('Failed to fetch spaces:', error); setSpaces([]); } finally { setIsLoading(false); } }; fetchSpaces(); }, []); if (isLoading) { return <Text>Loading spaces...</Text>; } return <CodeBlock text={JSON.stringify(spaces, undefined, 2)} language="json" />; }; const App = () => { const [data, setData] = useState(null); const [context, setContext] = useState({}); // Check if read:space:confluence permission is granted const { hasPermission, isLoading: permissionsLoading } = usePermissions({ scopes: ["read:space:confluence"], }); useEffect(() => { invoke('getText', { example: 'my-invoke-variable' }).then(setData); }, []); useEffect(() => { const fetchContext = async () => { setContext(await view.getContext()); }; fetchContext(); }, []); return ( <> {/* Update the text to signify a backwards compatible change that you wished to ship in older installations */} <Text>Hello new world!</Text> <Text>Installed app version: {context.appVersion}</Text> {permissionsLoading ? ( <Text>Loading...</Text> ) : ( /* Skip calling show spaces if permission is not granted */ hasPermission && <ShowSpaces /> )} <Text>{data ? data : "Loading..."}</Text> </> ); }; ForgeReconciler.render( <React.StrictMode> <App /> </React.StrictMode> );
We renamed isLoading to permissionsLoading to avoid naming conflicts with the isLoading state in the ShowSpaces component.
Deploy the new version of the app:
1 2forge deploy
To upgrade only the code version of the app in your installations, run the install command with the code upgrade flag:
1 2forge install --upgrade code
This will upgrade only the code version of the app. The new read:space:confluence permission will not be approved.
Now when you view your app, you can see the new code version but fetching the spaces is skipped:
The app version received in context is the app code version (e.g., major version 3 in this case). To see the permission version, run:
1 2forge install list
The Major version in the table is the permission version for the installation. It should show as "Out-of-date" since the code version is newer.
This demonstrates rolling releases in action: your code updated without requiring admin approval for the new permission!
To upgrade the permission version of the app on your test instance, run the forge install command with the upgrade flag:
1 2forge install --upgrade
This will:
read:space:confluence scopeAfter approval, refresh your app in Confluence and you should see:
To help with testing, we've released a --major-version flag on the install command so that you can install an older version on your instance to test rolling releases.
You must uninstall the app before installing an older version.
Uninstall the app from the test instance:
1 2forge uninstall
Install the older version using the --major-version flag:
1 2forge install --major-version <major-version>
For example, to install major version 2:
1 2forge install --major-version 2
After installing the older version, the app will revert to the previous code and permissions state.
Now that you've learned about rolling releases, consider:
Rate this page: