Developer
Get Support
Sign in
Get Support
Sign in
DOCUMENTATION
Cloud
Data Center
Resources
Sign in
Sign in
DOCUMENTATION
Cloud
Data Center
Resources
Sign in
Last updated Jan 13, 2025

Rolling releases

What are rolling releases?

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:

  • The new code runs on all installations immediately
  • Permissions remain at the old version until the admin approves
  • Your app checks for permissions at runtime and gracefully handles missing ones

Why rolling releases?

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:

  • Reduce version fragmentation: All customers get the latest code immediately, eliminating the need to maintain multiple versions
  • Ship faster: Deploy bug fixes, performance improvements, and new features continuously without waiting for approvals
  • Lower maintenance costs: No need to backport fixes to multiple versions, reducing development overhead and infrastructure costs

How it works

Decoupled state

When an installation is upgraded with a code-only upgrade:

  1. All installations get the new code immediately
  2. Permissions remain at the old version until the admin approves
  3. Your app must check for permissions at runtime and gracefully handle missing ones

This state is called "decoupled" because the code version is ahead of the permission version.

Exiting decoupled state

When an admin approves new permissions, the permissions will then match the manifest of that version, and is no longer considered "decoupled".

Getting started

Onboard an app to rolling releases

Update the permissions 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
2
app:
  id: ...
permissions:
  enforcement: app-managed
  scopes:
    - ...
  external:
    - ...

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/react) and backend (@forge/api) code.

Frontend SDK

Install the pre-release version of the @forge/react package:

1
2
npm install --save @forge/react@next

Check if app installation has required permissions

Import the usePermissions hook to check permissions:

1
2
import { 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 <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>;
};

Backend SDK

Install the next version of the @forge/api package:

1
2
npm install --save @forge/api@next

Check if app installation has a scope granted

1
2
import { permissions } from '@forge/api';

const isPermitted = permissions.hasScope('storage:app')

Check if backend permission is granted

1
2
import { permissions } from '@forge/api';

const isPermitted = permissions.canFetchFrom('backend', 'https://api.example.com')

Check if app has access to load resource

1
2
import { permissions } from '@forge/api';

const isPermitted = permissions.canLoadResource('images', 'https://api.example.com/image.png')

Check multiple types of permission grants

1
2
import { 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"],
    }
})

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 4

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 and Confluence are supported.
  • Forge Containers not supported.
  • Upgrading from version without license to a version with license enabled is 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.

Known issues

  • Frontend permission checks in the SDK (fetch client, images, fonts, scripts) may return true in cases where paths are in the manifest.
    • Client side permission checks (images, fonts, scripts, client fetch) all honour the path section of an address in the manifest, so may be blocked even if the SDK returned true.
    • Covers frontend & backend SDKs:
      • usePermissions
      • permissions.canFetchFrom('client', '...')
      • permissions.canLoadResource(..., ...)
    • For example:
      • Manifest: permissions.external.fetch.client: "https://api.example.com/api"
      • Code: permissions.canFetchFrom("client", "https://api.example.com/whatever")
      • Expected: false, Actual: true

Tutorials and guides

For a hands-on walkthrough of building an app with rolling releases:

Rate this page: