Make @forge/react/jira resolve, and keep forge deploy working
Mihai Perdum
Author
9 min readSeptember 1, 2026
Key takeaways
END STATE: the subpath import type-checks, verified across TypeScript 5.9.2, 6.0.3 and 7.0.2, with a config that also survives forge deploy.
Use `bundler`. On TypeScript 6 and 7 it is a genuine one-line change; on 5 you must set `module` alongside it, because the two options are interlocked.
This is NOT editor-only. @forge/bundler runs ts-loader with no transpileOnly, reading your own tsconfig — so the error fails the build.
Three majors, three errors: 5.x TS2307 (module not found), 6.x TS5107 (node10 deprecated), 7.x TS5108 (node10 removed).
If your package.json has "type": "module", node16 and nodenext both break on Forge's CJS packages. Only bundler survives.
You add a Jira custom field to a Forge app, import the component the docs tell you to import, and TypeScript says the module does not exist:
text
1error TS2307: Cannot find module '@forge/react/jira' or its corresponding type declarations.
The natural reading is that something failed to install. It did not — the declaration file is on disk the whole time. What is wrong is the resolver.
Two things make this worth a full write-up rather than a one-line answer. On a Forge app it is not a cosmetic editor complaint: the bundler type-checks, so this fails your build. And the error you actually see depends on which TypeScript major you are running, across three that are all current.
Note
Prerequisites
Node 18+ and npm. Verified on Node v24.15.0.
@forge/react — checked against 12.1.3, published 31 August 2026.
Nothing needs deploying, and no Atlassian site is touched. You do need network access for the installs.
TypeScript is installed per step. Results below cover 5.9.2, 6.0.3 and 7.0.2.
1
Prove the types are installed before believing any error that says they are missing.
2
Reproduce the failure and identify which of three errors your TypeScript gives.
3
Inspect the package to see why a present file is unreachable.
4
Set moduleResolution to bundler, with the module change your version needs.
5
Verify the whole matrix, and confirm the runtime was never the problem.
6
Check it against how Forge actually builds, which is the step that matters.
Step 1 — Prove the types are installed
This step exists because I skipped it and burned a run. My first attempt reported that the package had no jira directory — true, and meaningless, because the install had silently failed and there was no package at all.
The relative path in that second command is not incidental. The obvious version — require('@forge/react/package.json') — throws, because the package's exports map does not publish ./package.json:
text
1Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './package.json' is not defined
2by "exports" in .../node_modules/@forge/react/package.json
That is the same mechanism this whole article is about, biting a diagnostic command. I had the bare-specifier version in an earlier draft of this piece, and it took a review to catch that I had run one command and written down another.
Ninety bytes, two lines, and it exports exactly the symbol you want. Anything that now says "cannot find module or its corresponding type declarations" is talking about resolution, not existence.
1// tsconfig.node.json — the classic resolver2{3"compilerOptions":{4"module":"commonjs",5"moduleResolution":"node",6"target":"es2022",7"jsx":"react-jsx",8"noEmit":true,9"skipLibCheck":true,10"strict":false11},12"include":["src"]13}
How you know it worked — it fails, and you should see one of exactly three errors, which identifies your version:
text
15.9.2 TS2307 Cannot find module '@forge/react/jira' or its corresponding type
2 declarations. There are types at '…/out/components/jira/index.d.ts',
3 but this result could not be resolved under your current
4 'moduleResolution' setting. Consider updating to 'node16',
5 'nodenext', or 'bundler'.
676.0.3 TS5107 Option 'moduleResolution=node10' is deprecated and will stop
8 functioning in TypeScript 7.0. Specify compilerOption
9 '"ignoreDeprecations": "6.0"' to silence this error.
10117.0.2 TS5108 Option 'moduleResolution=node10' has been removed. Please remove it
12 from your configuration.
Only the 5.x message describes the actual problem, and it names the fix in its second sentence — which is the part that gets cut when the error is pasted into a forum post. On 6 and 7, node is an alias for node10, which 6 deprecated and 7 removed, so the compiler complains about your config before it ever gets to the import. Silence the 6.x deprecation with ignoreDeprecations and you land back on TS2307.
How you know it worked: the first command should print No such file or directory, and the second should print a JSON object containing a "./jira" key. Seeing both together is the diagnosis — the subpath is declared and the folder is not there.
@forge/react/jira is a declared subpath mapping onto a file several directories away. The classic node resolver predates exports maps and ignores them, so it looks for node_modules/@forge/react/jira, finds nothing, and reports a missing module. Three subpaths are published this way, so ./global and ./router fail identically.
Step 4 — Set moduleResolution to bundler
Use bundler. It is what Forge's build actually is, it is the only value that stays safe in the case in step 6, and it is what Atlassian's own Custom UI frame ships.
On TypeScript 6 or 7 this is genuinely one line — module can stay as it is:
On TypeScript 5 it is not one line, and any article telling you otherwise has not run it. module and moduleResolution are interlocked, and changing only one gives you a different error:
text
1module stays commonjs, mr=node16 -> TS5110: Option 'module' must be set to 'Node16'…
2module stays commonjs, mr=nodenext -> TS5110: Option 'module' must be set to 'NodeNext'…
3module stays commonjs, mr=bundler -> TS5095: Option 'bundler' can only be used when
4 'module' is set to 'preserve' or to 'es2015' or later
So on 5.x, change both. Here is the complete file — not a fragment, because omitting jsx or noEmit gives you a fresh error and stray .js files in src/:
How you know it worked: you should see exactly one FAILS — the node row — and three COMPILES. If more than one fails, your tsconfigs are missing the matching module (see step 4); if none fails, you are not running the classic resolver at all.
One failure and three successes, on 5.9.2 and on 7.0.2 alike. That the failing case is the only failing case is what makes this a configuration diagnosis rather than a guess.
Node's runtime resolver has understood exports maps for years, so the JavaScript itself was always going to load. That explains the workaround you will find in forum threads — moving the import into a plain .js file makes the error vanish because you have stopped type-checking it, not because anything was fixed.
Step 6 — Check it against how Forge actually builds
This is the step that turns the advice from an editor preference into a requirement, and it is the one I nearly left out.
@forge/bundler compiles TypeScript with ts-loader, and it does not set transpileOnly:
How you know it worked: the first count should be non-zero and the second should be exactly 0. Run grep -rc "loader" node_modules/@forge/bundler as a control — if that is also 0 your grep is not reaching the package and the zero means nothing.
Fifteen references to ts-loader, and zero to transpileOnly — with loader itself appearing 103 times, so the search is finding things. ts-loader type-checks by default. Its options block overrides only three settings:
module and moduleResolution are not among them — they come from yourtsconfig.json, which getTSConfigPath() locates in your app directory. So an unresolved subpath import is not a red squiggle you can ignore. It is a type error inside your build.
And there is one combination to avoid. If your package.json declares "type": "module", node16 and nodenext model Node's native ESM-to-CommonJS interop — and @forge/react and @forge/resolver are transpiled CommonJS with a default export. Under those two settings you get errors like Property 'render' does not exist and This expression is not constructable, and esModuleInterop does not rescue you. bundler is immune, because it models bundler interop, which is what Forge actually runs. That is the real reason to lead with bundler rather than treat the three as equivalent.
What this tells you about the next one
"Cannot find module X or its corresponding type declarations" has two very different causes wearing one message: the thing is absent, or the thing is present and your resolver is not allowed to look there. Those need opposite responses, and the cheap discriminator is to ls the file the package claims to publish. If it is there, stop reinstalling.
[[takeaways]] @forge/react publishes ./jira, ./global and ./router only through its exports map, with no matching folders on disk, so TypeScript's classic resolver reports a missing module while the declaration sits installed nearby. Set moduleResolution to bundler — one line on TypeScript 6 and 7, and paired with module on 5, because the two options are interlocked. It is also the only value that survives an ESM Forge app.
Treat this as a build problem rather than an editor one: Forge bundles through ts-loader with type-checking on and reads your own tsconfig, so the error you are ignoring in your editor is the error that will fail your deploy.
And check that a file is really installed before believing an error that says it is missing. I burned a run on that, and then wrote a verification command into an early draft of this article that could not itself run — blocked by the very exports map the article is about.
I built a Forge major-version predictor on a guess, and it took a review to find the answer inside my own commit