I submitted my first Forge app to the Marketplace this week. The app itself took
about two days. Getting it accepted took considerably longer, and almost none of
that was code.
I’m writing this down because the platform docs cover each piece in isolation
and are accurate about all of them, but nothing I found laid out the order
things happen in, or which steps silently depend on each other. I hit six dead
ends that were each obvious in hindsight and completely opaque at the time.
Disclosure so nobody has to wonder: I build Mermaid Studio for Confluence, and
this was its first submission. I’m not linking it. This post is about the
process, not the app.
The partner account rejected me with a 412
First attempt at creating a Marketplace Partner profile:
The underlying service call failed.
The underlying service marketplace_console_bff status code is: 412
That is a Precondition Failed with no indication of which precondition. I
verified my domain, set up a business mailbox, enabled 2FA, and tried again.
Same error.
What actually fixed it was shortening the organisation name. I never got
confirmation of why, and the error never changed to tell me. If you hit this,
try a simpler org name before you spend an evening on anything else.
Verifying a domain is not the same as changing your account email
Paid-via-Atlassian apps cannot use a personal email domain. I knew this, so I
bought a domain, set up a mailbox, and claimed the domain in Atlassian admin.
Then forge whoami still said:
Logged in as Rolf Eriksen (redacted for privacy)
Claiming a domain in the org admin proves you control it. It does not change
which address identifies your account. That lives in your personal account
settings at id.atlassian.com, and it is a completely separate screen. I spent an
hour assuming the first thing had done the second.
Worth knowing: changing the address on an existing account preserves the account
ID, so your apps and Developer Spaces come with you. Creating a second account
would have meant re-registering everything.
I ended up with two Developer Spaces and no way to bridge them
This one cost the most time.
My app was registered in the Developer Space I created when I first ran
forge register. When my partner account was approved, it came attached to a
different, empty Developer Space.
The Marketplace “Publish a new app” form reads from the partner-linked space, so
it showed:
No Forge apps available
with a hint about enabling sharing and publishing the Developer Space. Sharing
was already on. The space it wanted was the empty one.
forge developer-spaces list only ever showed one of the two. Attempting to
register into the other returned:
The Developer Space does not exist, or you don't have the correct permissions
to access it.
The console has a Transfer function for exactly this, which is the right
answer. But it told me:
No destination Developer Space available. You don't have another Developer
Space where you have the Admin role.
I was the org admin. I was not an Admin on that space, which is a different
grant. And the reason I could not grant it to myself was that the space hadn’t been set up yet. Setting up the space with just a couple clicks unlocked the Admin role, which unlocked
Transfer, which moved the app with its ID, versions and installs intact.
So the actual chain was: billing > space Admin role > Transfer > app appears in
the Marketplace form. Nothing surfaced that chain. Each error described only its
own link.
If you are starting from scratch, register your app after your partner account
exists and use the space it gives you. You will avoid all of this.
You cannot install your own licensed app from the CLI
Once you set licensing.enabled: true in the manifest, which you must do to
charge for the app:
forge install -e production --product confluence --site your-site.atlassian.net
Error: Installation error: Installing a licensed app is not permitted
This is correct behaviour, not a bug. Licensed apps install through the
Marketplace so entitlement can be attached. It means your production build gets
tested via a private listing install, not from your terminal. Plan for that,
because “test production before submitting” is good advice that quietly requires
the listing to exist first.
The first two rejections were automated and vague
Automatic Rejection - Not enough details on listing
then, after I expanded the listing:
Automatic Rejection - Invalid Links
Neither says which detail or which link. What eventually got me through:
- Filling every optional-looking field, not just the required ones
- Captions on every image, not just the images themselves
- In a field I was supposed to input a link to my website, I had instead written an explanation, because the field looked different than the other link input fields, so it confused me.
Paid apps need a support portal, and email does not count
The rejection that finally explained itself:
We are rejecting this app submission due to the absence of a support portal
link. For apps with Payment Model set as Paid via Atlassian, it is mandatory
to have a public support portal.
I had a support email address on my own domain and a support page. Not enough.
Customers must be able to raise and track a ticket.
Jira Service Management free tier covers this. Add JSM to your site, create a
service project, and set customer permissions so anyone can raise a request
without an invitation. Then check the portal URL in a private window. If it
demands a login, it is not public and it will fail the same check.
The default IT service management template gives you request types like “Report
broken hardware” and “Onboard new employees”. Delete all of them. For an app
vendor, four types cover it: report a problem, ask a question, suggest a
feature, billing or licensing.
The security questionnaires assume you have a security department
Two of them, and they ask about log retention policies, quarterly infrastructure
vulnerability scans, business continuity testing, SBOM maintenance, and periodic
access reviews.
My app is static resources running in the browser with no backend and no
permission scopes. There is no infrastructure to scan and no logs to retain.
The temptation is to answer Yes so the form looks respectable. Do not. These are
on record and several are verifiable. I answered No where the control genuinely
does not exist, and used the free-text field to explain that the controls are
absent because the attack surface is, rather than because I skipped them.
The second questionnaire has no free-text field. For that one I answered
honestly and posted the same explanation as a comment on the ticket.
Two things worth having ready before you start either form: a published security
policy and a written incident response plan. Both are short if you have no
infrastructure, and both turn a No into a Yes legitimately.
Four API details that cost me an hour each
Unrelated to submission, but they were the non-obvious ones during the build:
Macro config submits nested. view.submit({ source, theme }) fails with
Invalid “config” provided. Expected object. It wants
view.submit({ config: { source, theme } }).
view.resize() does not exist. I had guessed it did and wrapped it in a
feature check, so it silently never ran and my macro never sized correctly. The
actual mechanism is view.emitReadyEvent().
CQL cannot find Forge macros. macro = "your-macro-key" only matches
Connect macros. I built an admin page listing every page using my macro before
discovering this. Tracked as CONFCLOUD-81490.
CSS transforms do not resize a macro iframe. I implemented zoom with
transform: scale(). The diagram grew visually and the iframe did not, so
content overflowed a box that never changed height. Setting an explicit pixel
width reflows, and the auto-resizer then follows.
What I would do differently
Create the partner account first, then register the app into the space it gives
you. Set up the support portal before writing a line of listing copy. Publish
the privacy policy, terms, SLA and security policy as real pages early, since
four separate forms ask for those URLs.
The build was the easy part. I expected that to be the other way round.
Happy to answer questions if anyone is going through the same thing.