jira:fullPage is deprecated: what global:fullPage actually changes for cross-product Forge apps
Mihai Perdum
Author
14 min readAugust 11, 2026
Key takeaways
jira:fullPage and confluence:fullPage are deprecated on September 30, 2026, announced August 6 — both were only ever Preview modules.
You cannot put both fullPage modules in one cross-product app today. forge lint rejects the one that does not match the required product, and forge lint --fix cannot repair it.
global:fullPage is product-agnostic in name but still needs a required Atlassian app, so migrating removes the per-product module, not the product context.
The frontend context of a global:fullPage module carries no product field at all — it reports experienceMode standalone.
The backend resolver's installContext names the REQUIRED app, not where the user came from. I proved it by flipping the required product and watching the ARI change.
Changing the required app once installations exist is refused server-side: 'Cross-product app cannot be modified due to existing installations'.
In November 2025 a Marketplace partner asked Atlassian a very direct question on the developer community. Steffen (sopel) had just read two Preview announcements — apps compatible with multiple Atlassian apps, and the Confluence full page module — and wanted to know why the full page module was Confluence-specific at all:
I'm wondering why confluence:fullPage is a Confluence specific module in the first place that is now getting a sibling module for Jira (I'll assume jira:fullPage), rather than this being a say core:fullPage module right away
The answer from Atlassian, on topic 96901, was a polite no:
Our current architecture requires modules to be defined within a product context. Our long term vision and engineering roadmap enables installation contexts outside of our core products, so you can expect this to evolve to be independent of product. [...] but we cannot commit to timelines at this stage.
That was 21 November 2025. On 3 August 2026 the Forge changelog announced global:fullPage in Preview. Three days later, on 6 August 2026, it announced this:
We are deprecating the confluence:fullPage(Preview) and jira:fullPage(Preview) modules on Sep 30, 2026.
So the module Steffen asked for shipped in about nine months, and the ones it replaces get 55 days. I am writing this on 11 August 2026, which leaves 50 days.
I did not want to write that up from the release notes, so I built the app that breaks, reproduced the failure, migrated it, and deployed the result into both Jira and Confluence on a real site. Everything below was run on @forge/cli 13.3.0 against wolfaenpak.atlassian.net, our test tenant, on 11 August 2026. Where I am quoting someone else rather than my own terminal, I say so.
The thing that does not work
Start with what a lot of people will try first, because it is the obvious thing: one Forge app, compatible with Jira and Confluence, serving a full page in each.
Forge's multiple-app compatibility — still labelled Preview on the app compatibility docs when I read them on 11 August 2026 — lets one app declare several Atlassian apps, exactly one of which is required:
1/Users/workhorse/Projects/lz-xca-fullpage-lab/manifest.yml
29:2 error Using the module `confluence:fullPage` is invalid when the required
3product is Jira. Full page modules must match the required product. Use `jira:fullPage`
4as the module type, or change the required product to Confluence in your manifest.yml
5compatibility settings. valid-document-required
67Error: Command failed due to validation error.
That is the message verbatim, wrapped here to fit. The rule name is valid-document-required and the process exits 1, so this fails a CI step rather than printing a warning nobody reads.
Two details worth having. First, this is local validation: I hit it with a placeholder app ID before the app was even registered, so you do not need a deployed app to reproduce it. Second, it is symmetric — flip the required product to Confluence and the same rule fires at the other module, naming jira:fullPage instead.
Setting both to required does not escape it
The natural next move is to stop choosing and mark both required. That produces three errors, and two of them contradict each other:
text
12:2 error Using the module `jira:fullPage` is invalid when the required product is
2Confluence. [...] valid-document-required
349:2 error Using the module `confluence:fullPage` is invalid when the required
5product is Jira. [...] valid-document-required
6725:2 error One context (Jira, Confluence, or Compass) must have `required:true` in
8app.compatibility. valid-document-required
Read the first two together. The validator tells you the required product is Confluence and that it is Jira, in the same run, on the same file — while the third error says no context is required at all. Each fullPage module is evidently checked against the other one's product, and the "exactly one required" rule is evaluated separately. It is not wrong about the outcome, but if you are debugging from the messages alone you will chase your tail. Atlassian's own bug ticket records the same three errors, so this is the documented behaviour rather than something specific to my setup — I am reporting that it still reproduces exactly on CLI 13.3.0 today.
Warning
forge lint --fix does not help here. I ran it against the failing manifest: it printed the same error and left the file byte-for-byte unchanged. This is not in the class of problems the fixer handles.
Currently, there is no known workaround for this behavior. A workaround will be added here when available
There is one thing that works today, and Atlassian's own ticket points straight at it. From the description:
This contrasts with globalPage modules, which work correctly in dual Jira/Confluence installations.
I checked rather than assumed. A single app with jira:globalPage and confluence:globalPage, Jira required and Confluence optional, lints clean:
text
1No issues found.
Watch the module shapes if you try it — they are not identical twins. confluence:globalPage requires a route and rejects a module-level resolver, so a manifest copy-pasted from the Jira side fails with confluence:globalPage should NOT have additional property 'resolver'. That cost me a lint round.
The other cross-product fullPage bug, which is fixed
"message": "Failed to validate FCT: 'contextIds' claim mismatch"
That one has been fixed. Timothy Van Dyke (Atlassian Staff) posted on 5 July 2026 that the rollout was complete, and a second affected developer confirmed the next day that it was working. So if you are searching for FCT_VALIDATION_CLAIM_MISMATCH today, the answer is that it was a platform bug and it is gone — the ticket text that still describes it has simply not been trimmed. The lint rule is the part that is still open.
Migrating to global:fullPage
Here is the whole app, and it is smaller than the broken one because there is only one module now.
The new property is routePrefix, and it is mandatory. It becomes part of the URL, which is where the product-agnostic part becomes literal — there is no /jira/ or /wiki/ in it:
The backend is an ordinary resolver. Note what is not imported here — @forge/bridge must not appear in a resolver bundle, because the backend bundler does not tree-shake an unused import of it out and the resolver throws at runtime. We measured that one separately, in the @forge/bridge import that kills your resolver:
npx forge lint returns No issues found on the global:fullPage manifest
2
Deploy
npx forge deploy -e development
3
Install into the required app first
npx forge install --site your-site.atlassian.net --product jira -e development
4
Install into the optional app
the same command with --product confluence
5
Read back what you got
npx forge install list
That last step is the one that shows the feature working:
text
1┌──────────────────────────────────────┬─────────────┬──────────────────────────┬─────────────────────────────┐
2│ Installation ID │ Environment │ Site │ Atlassian apps │
3├──────────────────────────────────────┼─────────────┼──────────────────────────┼─────────────────────────────┤
4│ 4cacf369-e6a9-4bbe-9360-46e762328924 │ development │ wolfaenpak.atlassian.net │ Jira (Required), Confluence │
5└──────────────────────────────────────┴─────────────┴──────────────────────────┴─────────────────────────────┘
One installation ID, both products. Not two installs that happen to share a name — one row. I loaded the resulting URL in a logged-in browser and the page rendered.
The part the docs do not tell you
Now the question every developer actually has once this works: if one module serves two products, how does my code know which product it is in?
Here is the frontend context, read off the running page:
There is no product in it. Not a product key, not a Jira or Confluence hint anywhere in extension. What it says instead is experienceMode: "standalone", and that word is doing a lot of work. A global:fullPage module is not rendering inside Jira or inside Confluence. It is a standalone surface hung off the site, reachable by a URL that mentions neither product. Migrating off confluence:fullPage does not move your page from one product to two — it moves it out of product chrome altogether.
The backend looks more promising at first, because there is an ARI in it:
ari:cloud:jira::site/.... If you were going to branch on anything, it would be that. So I tested whether it means what it appears to mean. I uninstalled the app, flipped the required product from Jira to Confluence, redeployed, and reinstalled into both products again — same site, same user, same module, same code. The resolver now returns:
installContext names the required Atlassian app, not the product the user came from. It changed only because I changed which app was required. If Jira is your required app, a global:fullPage resolver reports ari:cloud:jira::site/... to every user forever, including the ones who only use Confluence. Branch on it and you have written a constant.
Notice too how thin that backend context is here: cloudId, moduleKey and localId all came back null in the resolver on this module, while the frontend has all three. If your backend needs the cloud ID, pass it down from the frontend rather than expecting to find it on req.context.
Two more things that will cost you an afternoon
The required app is a one-way door, enforced server-side. The docs mention it in an FAQ line — "You can only change or remove the required Atlassian app if the app has no existing installations" — but they do not show you what it looks like. This is what you get if you try, and it is not a lint rule, it comes back from the deploy API:
text
10:0 error Upsert Environment Request body is invalid. Error message: Cross-product
2app cannot be modified due to existing installations MANIFEST_INVALID_RULE
To get out of it I had to uninstall first — and uninstalling from the required app removed the app from Confluence too, in one command. That is documented behaviour, and it is worth internalising before you demo anything: there is no "detach Confluence and keep Jira running" move.
There is a mild contradiction sitting next to that rule which is worth knowing about. The compatibility FAQ says changing the required app is only a minor version bump:
Any changes to the required Atlassian app is a minor version change unless elevated permissions or egress are added.
That is true, and I watched it happen — flipping the required product from Jira to Confluence took my app from 2.4.0 to 2.5.0, and flipping it back took it to 2.6.0. Both minor, so neither would demand an admin re-consent. But the rule you will actually hit first is the other one: you cannot make that change at all while anyone has the app installed. The "it's only a minor version" reassurance therefore only ever applies to an app nobody has installed yet. In practice, treat your choice of required app as permanent from your first customer install onward, and pick it before you ship rather than after.
UI Kit still wants React 18, and nothing warns you.@forge/react 12.1.1 declares react: ^18.2.0 and react-reconciler: ^0.29.0. I had installed react@latest (19.2.8) out of habit. The app linted clean, deployed clean, installed clean, and rendered a blank page with a spinner that never resolved. The only evidence was in the browser console:
Downgrading to React 18.3.1 fixed it with no code change. If you get a blank full page and a green terminal, check your React major before you check anything else.
Note
If what you actually wanted was product chrome rather than a standalone surface, the answer today is still globalPage, not fullPage of any flavour. It runs in both products, it lints clean cross-product, and it keeps your app inside the product's navigation instead of on a bare URL. Our own planning app for Jira runs on jira:globalPage for exactly that reason.
What I did not verify
Three things, stated plainly so nobody quotes me on them.
I did not test whether a global:fullPage module gets a navigation entry in each product's Apps menu. I reached it by URL. If discovery matters to your app, check that yourself before you plan around it.
I did not test Compass, which the compatibility docs allow only as an optional app, and I did not test the Isolated Cloud or Atlassian Government Cloud environments — the global:fullPage reference says those "are not yet supported but will be added in the near future".
I also cannot tell you what happens to a jira:fullPage module that is still deployed on 1 October 2026. "Deprecated" and "removed" are not the same date in Forge, the changelog gives one date, and I am not going to guess at the other.
The short version
global:fullPage is a real improvement and the migration itself is small — one module instead of two, one routePrefix, one URL. But the name oversells it in one specific way, and that is the thing to take away. It is product-agnostic in the sense that the module and the URL no longer name a product. It is not product-agnostic in the sense of knowing which product your user is in, because there is no longer a product to be in. The compatibility block still gates who can install you, installContext still reports the required app and nothing else, and any code you have that branches on product context needs to be deleted rather than ported.
Fifty days. The lint error is the easy part.
Locking in Forge KVS: FAIL_IF_EXISTS works, TTL leases do not