Forge supports URL persistence for Connect page module URLs. This capability allows your app to use a platform-level bridge that keeps your Connect app's existing URLs working when migrating it to Forge. For Jira and Confluence apps that rely on permanent URLs, Forge will accept the original Connect URL (including its full path, query parameters, and fragments) and transparently redirect it to the corresponding Forge app module.
This page describes how to preserve a Connect app's page module URLs when migrating it to Forge, ensuring that customer bookmarks and shared links continue to work.
URL persistence is primarily intended for enabling backward compatibility with Connect apps. Avoid relying on it to provide new Forge features.
Forge doesn't support URL persistence for app modules created via Dynamic Modules.
In addition, URL persistence has the following known limitations:
Forge performs matching via module ID: {addonKey}__{moduleKey}. Jira/Confluence finds the Forge app whose app.connect.key in the manifest file matches the Connect app key, then finds the Forge module whose key matches the obsolete Connect module key.
Afterwards, Forge will build the resulting URL based on the corresponding module type. This URL uses some or all of the following variables:
| Variable | Description |
|---|---|
{baseUrl} | The app host URL |
{appKey} | The app key (equal to connect.app.key in the Forge manifest) |
{moduleKey} | The module key |
{projectKey} | The project or space key |
{projectType} | The type of project, such as core, software, servicedesk, etc. |
{appId} | The Forge app ID |
{envId} | The Forge environment ID |
{route} | The string declared in the Forge module properties |
For these supported modules, Jira and Confluence will extract the app key and module key from the source URL:
| Connect module types | URL format |
|---|---|
| General Page (for Jira or Confluence ) |
{baseUrl}/plugins/servlet/ac/{appKey}/{moduleKey}
|
| Jira Project page |
{baseUrl}/projects/{projectType}/{projectKey}?selectedItem=com.atlassian.plugins.atlassian-connect-plugin:{appKey}__{moduleKey}
|
For Forge module targets, use the following URL formats:
| Forge module types | URL format |
|---|---|
jira:globalPage | {baseUrl}/jira/apps/{appId}/{envId} |
jira:adminPage | {baseUrl}/jira/settings/apps/get-started/{appId}/{envId} |
jira:personalSettingsPage | {baseUrl}/jira/settings/personal/apps/{appId}/{envId} |
jira:projectPage | {baseUrl}/jira/{projectType}/projects/{projectKey}/apps/{appId}/{envId} |
jira:projectSettingsPage | {baseUrl}/jira/{projectType}/projects/{projectKey}/settings/apps/{appId}/{envId} |
jiraServiceManagement:queuePage | {baseUrl}/jira/servicedesk/projects/{projectKey}/queues/apps/{appId}/{envId} |
confluence:globalPage | {baseUrl}/apps/{appId}/{envId}/{route} |
confluence:globalSettings | {baseUrl}/admin/forge/apps/{appId}/{envId}/{moduleKey} |
Depending on the source URL formats, additional parameters may be required to determine the destination URL:
| Forge module types | From Connect General Page | From Connect Project Page |
|---|---|---|
jira:globalPage | Yes | Yes |
jira:adminPage | Yes | Yes |
jira:personalSettingsPage | Yes | Yes |
jira:projectPage | Yes, but only if the URL includes a valid project.id query parameter. | Yes |
jira:projectSettingsPage | Yes, but only if the URL includes a valid project.id query parameter. | Yes |
jiraServiceManagement:queuePage | Yes, but only if the URL includes a valid project.id query parameter. | Yes |
confluence:globalPage | Yes | N/A |
confluence:globalSettings | Yes | N/A |
For jiraServiceManagement:queuePage, the resulting page will not load as expected if the supplied project is not for Jira Service Management, even if the redirection process is completed.
To implement URL persistence from Connect to Forge:
Create matching Forge modules in the manifest. The module ID {addonKey}__{moduleKey} should be identical on both Forge and Connect apps so they can be matched. For each Connect URL you want to preserve (for example, generalPages), ensure that:
app.connect.key matches the source Connect app's key.Handle the x_atlassian_cf parameter in Forge. On the Forge app page, read and decode the x_atlassian_cf query parameter. For example:
1 2import { useEffect } from 'react'; import { view } from "@forge/bridge"; import ForgeReconciler from '@forge/react'; const extractConnectUrl = (location) => { const { search } = location || {}; if (!search) { return null; } try { const params = new URLSearchParams(search); const base64encodedUrl = params.get("x_atlassian_cf"); return base64encodedUrl ? atob(base64encodedUrl) : null; } catch (e) { console.warn("Failed to extract the Connect URL.", { location }, e); return null; } }; const handleConnectUrl = async () => { try { const { location } = await view.createHistory(); const connectUrl = extractConnectUrl(location); console.log("handleConnectUrl", { location, connectUrl, }); // Provide a backward-compatible experience using the original URL. if (connectUrl) { // ... } } catch (e) { console.error("Failed to retrieve the Connect URL", e); } }; const App = () => { useEffect(() => { handleConnectUrl(); return () => { // ... cleanup, etc. }; }, []); }; ForgeReconciler.render( <React.StrictMode> <App /> </React.StrictMode> );
This example uses the original Connect URL inside the query parameter x_atlassian_cf (including the path, query string, and fragment). This helps preserve consistent behaviour, such as routing to the proper view in your app.
The payload value of the query parameter x_atlassian_cf in the Forge URL is a Base64-encoded variant of the original Connect URL. While the URL persistence feature itself doesn’t have a length limit for the original Connect URL, the Atlassian platform does. The entire URL must contain no more than 8192 characters.
This example shows what changes to a Connect and Forge module should look like in a Connect-to-Forge app.
Remove the original Connect module declaration:
1 2connectModules: jira:generalPages: - key: hello-world-global-page location: system.top.navigation.bar name: value: Hello World! url: /hello-world
Then, add the new Forge module declaration:
1 2modules: jira:globalPage: - key: hello-world-global-page resource: main resolver: function: resolver render: native title: Hello World!
With URL persistence implemented, a customer could have a bookmark to a Connect module URL like the following:
https://example.atlassian.net/plugins/servlet/ac/com.example.app/connect-module-key-1?var1=&var2=%F0%9F%90%90#!welcome/asset?id=1001
Whenever they access this bookmark, Jira or Confluence will redirect it to the corresponding Forge page module's URL, for example:
https://example.atlassian.net/jira/apps/00000000-0000-0000-0000-000000000000/ffffffff-ffff-ffff-ffff-ffffffffffff?x_atlassian_cf=aHR0cHM6Ly9leGFtcGxlLmF0bGFzc2lhbi5uZXQvcGx1Z2luL3NlcnZsZXQvYWMvY29tLmV4YW1wbGUuYXBwL2Nvbm5lY3QtbW9kdWxlLWtleS0xP3ZhcjE9JnZhcjI9JUYwJTlGJTkwJTkwIyF3ZWxjb21lL2Fzc2V0P2lkPTEwMDE%3D
Where:
com.example.app is its Connect app keyconnect-module-key-1 is the Connect module key for the obsolete module00000000-0000-0000-0000-000000000000 is the Forge app IDffffffff-ffff-ffff-ffff-ffffffffffff is the Forge environment IDRate this page: