Forge rolling releases is available through Forge's Early Access Program (EAP). This feature is currently only available in Jira, Confluence, and Bitbucket.
EAP grants selected users early testing access for feedback; APIs and features in EAP are experimental, unsupported, subject to change without notice, and not recommended for production. To join the EAP, please complete the sign-up form.
For more details, see Forge EAP, Preview, and GA.
Rolling releases decouple permissions (scopes, egress, and remotes) from app code versions. This means you can deploy new code without waiting for admin approval of new permissions.
Currently, when permissions change, the app remains on the old version of the code for all existing installations, and admins are sometimes slow to update the apps, causing version fragmentation.
With rolling releases, when you deploy a new version with permission changes:
One of the drivers for this change is developers having many customers not getting the latest version of the code, and developers need to back-port fixes and security patches to old major versions, increasing the load on developers to support old major versions. Some developers have expressed that they do not have the capacity to maintain old versions.
Key benefits:
When an installation is upgraded with a code-only upgrade:
This state is called "decoupled" because the code version is ahead of the permission version.
When an admin approves new permissions, the permissions will then match the manifest of that version, and is no longer considered "decoupled".
This EAP focuses on adopting the SDK and testing the decoupled state, it does not support rolling out code versions to end-users, only test sites you control.
Update the permissions section in the app manifest to indicate that app is managing missing permissions. This is done by adding enforcement: app-managed config to app permissions.
1 2app: id: ... permissions: enforcement: app-managed scopes: - ... external: - ...When the enforcement is set to app-managed, the app will be eligible to enter a decoupled state, and will encounter a permission denied error, or have their external request blocked if the app attempts to perform some action that requires a new permission that is not granted yet. It is up to the app to use the SDK to prevent crashes.
Please see Permissions SDK to check for missing permissions at runtime.
Permissions SDK
The updated code may include changes that depend on new permissions but since only the app code was upgraded, some permissions might be missing. To handle such cases, use the permissions SDK to verify if the permission exists and gracefully handle if the needed permission does not exist.
The Permissions SDK is available for both frontend (
@forge/reactfor UIKit,@forge/bridgefor Custom UI) and backend (@forge/api) code.Frontend SDK
1 2npm install --save @forge/reactCheck if app installation has required permissions
Import the
usePermissionshook to check permissions:1 2import { usePermissions } from '@forge/react'; const MyComponent = () => { const { hasPermission, isLoading, missingPermissions, error } = usePermissions({ scopes: ['read:confluence-content', 'write:confluence-content'], external: { fetch: { backend: ['https://api.example.com'], client: ['https://cdn.example.com'] }, images: ['https://images.example.com'], fonts: ['https://fonts.googleapis.com'] } }); if (isLoading) return <Text>Loading...</Text>; if (error) return <Text>Error: {error.message}</Text>; if (!hasPermission) { return <Text>Missing: {JSON.stringify(missingPermissions)}</Text>; } return <Text>All permissions granted!</Text>; };The hook returns an
isLoadingboolean, but this should not betruefor any significant amount of time.Custom UI
1 2npm install --save @forge/bridgeCheck if app installation has required permissions
Import the
checkPermissionsfunction to check permissions:1 2import { checkPermissions } from '@forge/bridge'; const App = () => { const [hasPermission, setHasPermission] = useState(false); const [missingPermissions, setMissingPermissions] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { checkPermissions({ scopes: ['read:jira-work'], external: { fetch: { backend: ['https://example.com/something'], }, }, }).then(({granted, missing}) => { setHasPermission(granted); setMissingPermissions(missing); setIsLoading(false); }).catch(err => { setError(err); setIsLoading(false); }); }, []); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; if (!hasPermission) { return <div>Missing: {JSON.stringify(missingPermissions)}</div>; } return <div>All permissions granted!</div>; };Display conditions
You can disable or show an alternative module in the frontend if permissions are missing using display conditions. See Display conditions: Permissions for more information.
Backend SDK
Install the
nextversion of the@forge/apipackage:1 2npm install --save @forge/api@nextCheck if app installation has a scope granted
1 2import { permissions } from '@forge/api'; const isPermitted = permissions.hasScope('storage:app')Check if backend permission is granted
1 2import { permissions } from '@forge/api'; const isPermitted = permissions.canFetchFrom('backend', 'https://api.example.com')Check if app has access to load resource
1 2import { permissions } from '@forge/api'; const isPermitted = permissions.canLoadResource('images', 'https://api.example.com/image.png')Check multiple types of permission grants
1 2import { permissions } from '@forge/api'; const { granted, missing } = permissions.hasPermission({ scopes: ['storage:app'], external: { fetch: { backend: ["https://api.example.com", "https://blah.com", "https://www.google.com"] }, images: ["https://images.example.com", "https://cdn.example.com"], } })You must extract the
grantedproperty from the returned object.Atlassian app events filters
If your app doesn’t have the right permissions, running an Atlassian app event can result in error from the app code. You can use a filter to stop the function from running at the platform level when the required permissions are missing.
Permissions object is added to the event object so that we can filter out Atlassian app events if certain permissions are missing. Update the expression to filter events if certain permissions are missing. For example, to check the missing scopes use the following expression:
1 2modules: trigger: - key: jira-issue-trigger-filtering-by-expression-for-bug-issue-type function: main events: - avi:jira:created:issue filter: expression: "event.permissions.scopes.includes('write:jira-work') && event.issue.fields?.issueType.name == 'Bug'" function: - key: main handler: index.runExternal egress is also available in the
event.permissionsand can be used for filtering. The following expression can be used to filter out Atlassian app events based on external backend fetch permission:1 2modules: trigger: - key: jira-issue-trigger-filtering-by-expression-for-bug-issue-type function: main events: - avi:jira:created:issue filter: expression: "(event.permissions.external?.fetch?.backend || []).includes('*.example.com')" function: - key: main handler: index.run permissions: enforcement: app-managed external: fetch: backend: - "*.example.com"For more examples of expressions you can use, see Filter out Atlassian app events.
To try out your own event filtering expressions against a sample payload, use the Expressions playground.
Lifecycle events
App upgrade event
When an admin consents to the new permissions for your Forge app, the upgrade event is triggered. The event payload now includes the app’s updated
permissions, helping you track permission changes during major upgrades. For more information, see the Forge app upgrade event documentation.The upgrade event is not triggered for code-only upgrades (when only your app’s code changes, and there are no changes to the manifest or permissions).
Developer testing
For a developer to ensure their app will work when apps upgrade from previous versions, and they have checked the correct permissions in the correct places, they need to enter a decoupled state with various permission combinations from previous major versions.
Installing into a decoupled state
To test how your app behaves with different permission levels, you can install directly into a decoupled state:
1 2# Install with permission version 2, code version 2 (coupled) forge install --major-version 2 # Upgrade only code to version 4 (decoupled state: permissions v2, code v4) forge install --upgrade code --major-version 4 # Upgrade both code and permissions to version 4 (coupled again) forge install --upgrade --major-version 4License changes
You can now roll out a decoupled state when you enable licensing. This allows you to migrate users who are still on the free version of your app.
We recommend a deprecation window where you maintain the free function to enable a smooth transition for your users.
EAP limitations
The following features are under development, therefore are not offered as part of EAP:
- Code auto-upgrade on customer site not supported.
- Only Jira, Confluence, and Bitbucket are supported.
- Forge Containers not supported.
- Upgrading from version without storage to a version with storage (KVS and SQL) is not supported.
- Upgrading from a version without any dynamic webtriggers to a version with a dynamic webtrigger is not supported.
- Upgrading from a version without Forge LLMs to a version with Forge LLMs is not supported.
Known issues
- Forge Remote Compute endpoints cannot use the permissions encoded in the Forge Invocation Token (FIT).
- Workaround: Listen to the lifecycle event to monitor the current state of the permissions as they change.
Tutorials and guides
For a hands-on walkthrough of building an app with rolling releases:
Related resources
- Public RFC: RFC 106 - Future of Forge Versioning & Permissions
- Forge permissions
- Display conditions: permissions – control when modules are shown based on permission state
- Environments and versions
- Forge EAP, Preview, and GA
Rate this page: