Disable forge tunnel

Hi,

I am trying to move my connect app to forge. For the time being I have moved my modules to connect-on-forge and trying to move them completely to forge one at a time. My pages from connect-on-forge and forge-custom-UI were both loading till morning. Today, I tried to use forge tunnel, but couldn’t proceed with it as the static resources are not on the root folder of my local dev server but divided among different modules. Hence, I stopped my tunnel and am trying to use it the old way “forge deploy -e development && forge install –upgrade -e development”. The issue now is that my forge module is loading but my connect-on-forge modules are not loading now and are throwing the following error.

Unable to establish a connection with the Custom UI bridge.
If you are trying to run your app locally, Forge apps only work in the context of Atlassian products. Refer to ``https://go.atlassian.com/forge-tunneling-with-custom-ui`` for how to tunnel when using a local development server.

How can I disable forge tunnel on my forge app for all modules?
I have already done uninstall and re-install the forge app completely, even restarted my local machine.

Hey Kaustubh,

Did you launch a server to run your CustomUI project and then run forge tunnel? (Both need to be running to work).

Also did you follow the instructions outlined in the error message? https://developer.atlassian.com/platform/forge/tunneling/#tunneling-with-custom-ui

Hi Benny.

Yes. I launched a npm server and then forge tunnel. I was able to see the server root folder contents on the page, but since the actual page is in a sub-folder, requiring multiple js files to load. couldn’t find a way to render it. Hence, went back to rendering it the old deploy and re-install way.

Now, as mentioned, I am able to load my forge pages, but not my connect-on-forge pages. As I understand, tunneling is for forge pages, not connect-on-forge pages.

1 Like

Hi Benny,

My team mates are also facing the same issue who have never tried forge tunnel on their machine. Hence, I am guessing the issue is not with the tunnel itself, but with how forge is deploying to the development environment.

Thanks for the updates.
It is hard to debug without a lot more information on your on how you setup your App/ folder structures etc.

Best to raise an Help ticket via: Jira Service Management

Please provide your App ID’s, steps to reproduce and info on your folder structure. This will get routed to the right Forge team to investigate further.

Not completely sure if this helps, but I’ve noticed something similar when stopping the tunnel.

If you interrupt it twice with Ctrl + C, it doesn’t actually shut down cleanly - Forge still seems to think the tunnel is running, and the Custom UI keeps trying to connect to the local server.

What worked for me was:
1. Run the tunnel again
2. Then stop it once with Ctrl + C and wait until you see

After doing this, my app started using the deployed Custom UI again instead of looking for the local tunnel.

Summary: Don’t interrupt the tunnel shutdown — let it finish stopping completely.

@vzakharchenko

Thanks, but did this, with the same error. Also, as mentioned, forge pages are loading, it’s the connect-on-forge pages which give the error. Connect-on-forge pages shouldn’t be looking for the tunnel in the first place.

I just checked the code behind this error, and it looks like the issue isn’t actually with the tunnel itself.

Your connect-on-forge pages are importing @forge/bridge, which only works in the Forge runtime context.

If you look at the @forge/bridge source, the error

is thrown whenever the bridge can’t find the Forge environment (it expects window.__bridge to be available).

Since Connect pages don’t run inside the Forge context, you should remove any imports or references to @forge/bridge from your Connect frontend. Once that’s done, the error should disappear.

yes. Thanks @vzakharchenko

removing the imports allows the pages to load. Though, is there any way to add the import.

While moving the pages to forge, the app code for a particular page remains the same, and I am just doing a bit of pre-processing to allow for the same code to be used for both connect-on-forge and forge versions of the page. Hence, it would help a lot if I am able to import @forge/bridge on the page.

I see two possible ways to handle this.

In our case, we not only moved the app to Forge but also switched from Webpack to Vite, and decided to split the frontend - one for Connect and one for Forge - since that approach was simpler for us.

Another option is to keep a single shared codebase and switch only the platform layer using a bundler alias (webpack, vite):

// webpack.config.js
const path = require('path');
const isForge = process.env.BUILD_TARGET === 'forge';

module.exports = {
  // entry/output...
  resolve: {
    alias: {
      '@platform': path.resolve(
        __dirname,
        isForge ? 'src/platform/forge.ts' : 'src/platform/connect.ts'
      ),
    },
    extensions: ['.ts', '.tsx', '.js'],
  },
};

Package.json Scripts:

{
  "scripts": {
    "build:forge": "cross-env BUILD_TARGET=forge webpack --mode production",
    "build:connect": "cross-env BUILD_TARGET=connect webpack --mode production"
  }
}

src/platform/forge.ts:

import { invoke as bridgeInvoke } from '@forge/bridge';

export async function invoke(key: string, payload?: unknown) {
  return bridgeInvoke(key, payload);
}

src/platform/connect.ts:

export async function invoke(key: string, payload?: unknown) {
 // ... connect call
  const res = await fetch(`/connect-api/${key}`, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(payload ?? {}),
  });
  if (!res.ok) throw new Error(`Connect call failed: ${res.status}`);
  return res.json();
}

Then in your app:

import { invoke } from "@platform";

And in tsconfig.json for IDE support:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@platform": [
        "src/platform/forge.ts",
        "src/platform/connect.ts"
      ]
    },
    // ...
  },
  "include": ["src"]
}

This way:

  • The bundler alias (webpack or vite) decides which file to include in the final build.
  • The tsconfig path mapping ensures your IDE and TypeScript compiler don’t complain about missing @platform.
1 Like

thanks @vzakharchenko
for the time being for development I ended up adding a conditional import. Though, I will most probably be adding an alias in webpack as you suggested when we are ready to deploy, though that is still some time away.