Forums

Articles
Create
cancel
Showing results for 
Search instead for 
Did you mean: 

Redirection form Confluence DC to Cloud post Migration using Atlassian Forge

Meghdut Mandal
I'm New Here
I'm New Here
Those new to the Atlassian Community have posted less than three times. Give them a warm welcome!
April 16, 2026

We migrated our organization from Confluence Data Center to Confluence Cloud. After the migration, users often ended up on the wrong instance, either following an old DC link to content that had moved to Cloud, or needing to cross-check a Cloud page against its DC version.

To fix this, we built a Forge app that automatically redirects users in both directions. Sharing it here in case it helps others doing similar migrations. Open to questions in the comments.

Happy to discuss trade-offs in the comments.


What the app does

Two redirect directions, handled by two different frontend modules but the same backend:

  • Cloud → DC: A user on a Cloud page clicks a button and gets taken to the equivalent DC page (if one exists).
  • DC → Cloud: A user follows an old DC link and the app resolves it to the corresponding Cloud page and redirects them there.

The core of the app is a lookup table mapping DC page IDs to Cloud page IDs, plus a handful of fallback strategies for when a direct mapping isn't available.


Why Forge

The whole thing runs on Atlassian Forge. Nothing is self-hosted; there are no external servers. The relevant Forge pieces we used:

  • Hosted compute (Node.js functions) for backend resolvers
  • Forge SQL for storing the mappings
  • Forge UI Kit for the admin dashboard and the redirect page
  • External fetch permissions declared in the manifest so the app can open the DC Page without any issues

One thing worth calling out: because everything lives inside Forge, there's no infrastructure to manage, no separate auth layer to build, and no secrets to store. This makes it easy to divest the DC infrastructure eventually

High-level architecture

┌─────────────────────────────────────────────────────────────────────────┐
│                         Confluence Cloud (Forge)                        │
│                                                                         │
│  ┌──────────────────────────┐     ┌──────────────────────────────────┐  │
│  │    Frontend Modules      │     │      Backend Resolvers           │  │
│  │                          │     │                                  │  │
│  │  • Cloud→DC Module       │────▶│  • Mapping Lookups               │  │
│  │  • DC→Cloud Module       │────▶│  • Tracking Writer               │  │
│  │  • Admin Settings UI     │────▶│  • Statistics & Export           │  │
│  └──────────────────────────┘     │  • Schema Migrations             │  │
│                                   └──────────────┬───────────────────┘  │
│                                                  │                      │
│                                   ┌──────────────▼───────────────────┐  │
│                                   │         Forge SQL                │  │
│                                   │                                  │  │
│                                   │  • page_mappings                 │  │
│                                   │  • redirect_tracking             │  │
│                                   │  • migration_log                 │  │
│                                   └──────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────┘
          │                                           │
          ▼                                           ▼
  Confluence Cloud API                      Confluence DC instance
  (page metadata, CQL search)               (destination for Cloud→DC)

A few design notes:

  • The countdown is there partly for UX (users see what's happening and where they're going) and partly so the resolver has time to complete without making navigation feel instantaneous-but-broken when something fails.
  • Every attempt is tracked, including failures. This is what lets the admin team spot pages that have broken links pointing at them and add missing mappings.
  • The three input shapes mean the app can handle links that were copy-pasted out of wikis in various styles over the years.

How the mappings get loaded

The mapping data doesn't generate itself. Before (or during) migration, a separate process exports a CSV of DC → Cloud page ID pairs with all the URL variants and metadata. Then:

  1. A batch script reads the CSV and sends records to a backend resolver via GraphQL
  2. The resolver performs an upsert (INSERT ... ON DUPLICATE KEY UPDATE), so re-runs are idempotent — safe to re-run to patch or update rows
  3. Admins can also add, correct, or delete individual mappings through the dashboard UI

For the batch import itself, we chunk records (around 1,000 per call), dispatch several chunks concurrently, and cap a single run at around 10,000 records. Those numbers were tuned to stay well inside Forge's per-invocation limits; your mileage will vary depending on payload size and Forge SQL latency in your region.


Admin dashboard

Forge global settings page, admin-only (enforced by Forge). Five sections:

TabPurpose
DashboardDB health, total mapping count, breakdown by space
Test LookupQuery the mappings table by DC page ID or (space + title)
Add MappingManually insert or update a single row
TrackingDaily redirect volume, success/failure rates, paginated log, CSV export
Danger ZoneBulk delete

The tracking tab turned out to be more valuable than we expected. It's how admins find the "long tail" of broken inbound links that nobody filed a ticket about.


Things that worked well

  • Keeping everything in Forge. Zero infrastructure.
  • Tracking every DC → Cloud attempt. Track every redirect attempt.
  • Idempotent batch import. Being able to re-run the import without fear made mapping corrections painless.
  • Fallback chain for URL resolution. Direct page ID → display URL → error. Users almost never hit the error tier in practice.

Things I'd think about if starting over

  • Forge SQL limits. Batch import sizing needs careful tuning. Worth prototyping early with realistic data volumes.
  • Cutoff date handling. Ours is a single date. If your migration happens in waves per space, you'd want a per-space cutoff instead.
  • Admin UX for bulk corrections. Our dashboard handles one row at a time, which is fine for occasional fixes but painful if you discover a systematic mapping issue.

Happy to answer questions on any of this, especially around Forge SQL, the migration system, or the tracking design.



5 comments

Comment

Log in or Sign up to comment
Darryl Lee
Community Champion
April 16, 2026

This sounds amazing. I don't suppose you will be planning to open source the code?

Wondering how the DC redirection works. Do you have an additional server to redirect to your Forge app's endpoints on your Cloud instance?

Meghdut Mandal
I'm New Here
I'm New Here
Those new to the Atlassian Community have posted less than three times. Give them a warm welcome!
April 17, 2026

We have added a small js script in the Banner on DC, which takes the dcPage id from the current page, and redirects the user to the cloud URL. Once the cloud page opens, the Forge app takes over the redirection proces as mentioned above.

Stavros_Rougas_EasyApps
Atlassian Partner
April 17, 2026

@Meghdut Mandal wow, I would love to learn more. We have the only cloud redirection app on the Atlassian Marketplace - Redirection for Confluence and we know that migrations are a use case.

Tomislav Tobijas
Community Champion
April 21, 2026

Oh nice! This sounds really neat...

Thanks for sharing! 🙌
Definitely going to bookmark the article to potentially get inspired next time we do a migration :))

Darryl Lee
Community Champion
April 21, 2026

OH that's a pretty ingenious approach! Thanks!

 

C_ Faysal_CFcon_
Community Champion
April 29, 2026

Great write-up, and a use case I ran into myself recently.

We had a similar but slightly different scenario: Confluence had been migrated to Cloud, but Jira was still sitting on Data Center, holding 300k+ issue links pointing at the old Confluence DC URLs. No redirection layer was going to fix that at scale.

I ended up porting one of my Forge apps into a Data Center plugin to tackle this directly inside Jira. It lets you:

- Upload the CMA (Confluence Migration Assistant) export to map old URLs to new ones
- Connect to the Cloud tenant for live lookups when the CMA does not cover a URL
- Scan projects via JQL to scope what gets processed
- Run a search and replace across issue content, replacing DC links with the correct Cloud URLs

Handles the 300k+ links without timeouts using a background job. If anyone is dealing with the Jira side of a phased migration, happy to share more details.

Darryl Lee
Community Champion
April 30, 2026

Wow. Pretty cool.

I do wonder why you thought "No redirection layer was going to fix that at scale."

Are you thinking that a redirection service would not hold up under the load?

I would posit that even though there are 300k+ issue links to Confluence, it seems unlikely that all 300k links are being clicked on at the same time.

Because that's the only time the a redirect service would actually have to actually do anything.

My mapping file contains over 300k links. I don't have the usage stats handy, but we have a 6000 user license, and we've never had issues with the redirection service (I'm using Apache) going down.

Like C_ Faysal_CFcon_ likes this
C_ Faysal_CFcon_
Community Champion
April 30, 2026

Fair point on scaling, you're right.

Throughput wasn't the blocker, "at scale" was sloppy framing on my part.
The actual reason redirect wasn't an option: the source DC Confluence host had been decommissioned by the time we got involved.

No DNS, no Apache, no server.

The links in the Jira issues had become pure dead URLs. With nothing alive at the old hostname, there's nothing to put a redirector on.

That made the choice less about "is redirect or rewrite better" and more about "redirect requires the source host to still exist, and in our case it didn't."
On the Forge SQL angle from Meghdut's setup: storage grows linearly with the mapping list.

Meghdut himself flagged "Forge SQL limits, batch import sizing needs careful tuning" in the original post.

At 300k+ rows per installation it becomes a real operational consideration, especially when the same app gets installed across many customers each with their own large CMA export.
Even when the source host is alive, one more thing nudges toward rewrite: the URL in the Jira issue body is what users see in search results, copy-link, board cards, CSV exports. Even with a working redirector, the displayed URL still reads as the old DC hostname.

Looks broken to end users until they actually click.
I'll also admit a stylistic bias here: I come from the "rather fix what's broken than slap a patch on it" camp.

Rewriting the source feels cleaner to me than carrying a permanent translation layer forward.
So my real point isn't "redirect doesn't scale", it's "redirect requires the source URL to resolve somewhere, and in our scenario it couldn't." Apache RewriteMap is genuinely the right answer when the source host is still up. We just couldn't run it.

PS: And thanks for bringing this up, honestly. I really enjoy these kinds of topics. With everything moving to Cloud nowadays, there's less and less ground for low-level migration conversations like this one.

TAGS
AUG Leaders

Atlassian Community Events