Blog · 11 November 2026 · 6 min read

5 Forge traps the docs don't mention

We published eight Forge apps for Jira and Confluence this year. Most of the work went as the documentation describes. These five things did not, and each one cost us hours. None of them shows up in development, which is what makes them dangerous.

1. Every new customer sees "Authentication Required"

Most Forge resolvers are wrapped in a helper that catches errors and returns them to the UI as a friendly message. That is good practice, until a new user opens your app and it calls Jira with asUser(). Forge needs that user's consent, so the call throws NEEDS_AUTHENTICATION_ERR. If your helper catches it, the user gets an error message and no way to grant access.

You will never see this yourself: you granted access the first time you ran the app. Every customer will.

Fix: re-throw that one error so Forge can show its own "Allow access" button.

const handle = (fn) => async (req) => {
  try {
    return { ok: true, data: await fn(req) };
  } catch (e) {
    if (e?.name === 'NEEDS_AUTHENTICATION_ERR') throw e; // let Forge ask for consent
    console.error(e);
    return { ok: false, error: e.message };
  }
};

Then test with a second browser profile or a colleague's account before you submit.

2. A page that appears for a second and goes blank

UI Kit (render: native) can render your component between two state updates. Code like this looks safe:

setData(await invoke('getData'));
setSettings(await invoke('getSettings'));
// ...
return <Text>{settings.mode}</Text>;

But there is a render where data is set and settings is still null. settings.mode throws and the whole page disappears. There is nothing in forge logs, because the crash happened in the browser.

Fix: render nothing until every piece of data for the screen has arrived, and wrap each screen in an error boundary so a bug shows a message instead of an empty page.

if (!data || !settings) return <Spinner />;

3. One manifest change hides your custom field, and every field of that type

We added a resolver inside the contextConfig of a jira:customFieldType. forge lint passed and the deploy succeeded. Jira silently decided the module was invalid and hid the field type and every existing field created from it. No error anywhere: the fields were simply gone from issues and settings.

Removing the nested resolver and deploying again brought everything back.

Fix: after any manifest change to a custom field type, open an existing field of that type and check it is still there. Two related surprises: the field's context configuration does not reliably reach the edit view (keep your own copy in Forge storage), and custom field types only work in company-managed projects.

4. 401 "scope does not match" with the right scope

Listing Jira Software boards with read:board-scope:jira-software returned 401. The missing piece was read:project:jira, which the boards endpoint also needs and the error never mentions. Fetching sprint issues through the Agile API wanted further granular scopes; a JQL search was simpler:

await api.asApp().requestJira(route`/rest/api/3/search/jql`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ jql: `sprint = ${sprintId}`, fields: ['status'] }),
});

Remember that adding a scope is a major version: every existing install has to approve the upgrade. Settle your scopes before you go public.

Bonus: if a screen shows only aggregates (counts, progress, totals) that reveal no content, asApp() avoids the consent screen entirely.

5. "Automatic Rejection – Invalid Links"

All our links loaded fine, and the app was still rejected within minutes. The cause was an empty "Support ticketing system" field in the listing: the automatic check counts an empty support link as an invalid one. A second one that cost us a day: pricing entered through the API still shows "pricing has not yet been set" until you open the pricing page and click Save once.

Fix: fill every link field (support, documentation, terms), check that each returns HTTP 200, and save pricing in the UI before you submit.

Bonus: the blank page right after a deploy

Sometimes a page is blank right after forge deploy, with 404s in the browser console and no resolver calls in the logs. That is Forge still serving the new bundle, not your code. Deploy again or wait a few minutes before you start debugging.

Summary

  1. Re-throw NEEDS_AUTHENTICATION_ERR.
  2. Render only when all data has arrived, and use an error boundary.
  3. Check that custom field types still exist after manifest changes.
  4. Add read:project:jira for boards, use JQL for sprint issues, and settle scopes before launch.
  5. Fill every listing link, including the support field, and save pricing in the UI.

Skip the trial and error

The Forge Starter Kit has these fixes built in: Jira and Confluence templates, licensing, GDPR reporting, a Marketplace listing kit and a 12-page playbook. One payment, unlimited apps.