JSM Portal Request Create Property Panel Submit: Types Won't Catch a Bad Payload
Gabriela Perdum
Author
12 min readSeptember 21, 2026
Key takeaways
END STATE: a jiraServiceManagement:portalRequestCreatePropertyPanel that saves form data correctly, verified against the payload shape Atlassian's own docs and a real developer's fix both confirm, plus a plain check that your own installed @forge/bridge won't catch you if you get it wrong.
view.submit()'s payload is typed as (payload?: any) => Promise<void> in the shipped @forge/bridge package. The real, required shape — { fields: [{ key, value }], isValid: true } — comes from Atlassian's own manifest-reference page and from a real developer thread where the wrong shape compiled, ran, and silently stored nothing.
The data isn't written the moment you call view.submit() — Atlassian's own docs state it's stored 'when the request form is submitted,' meaning the customer's final Send action on the portal, not each individual field update.
The stored data lands as a Jira issue property keyed by the UUID component of your app's own app.id, readable via the standard issue-properties REST endpoint and readable back inside jiraServiceManagement:portalRequestDetail as context.extension.request.property.
Turning that stored property into a real, visible custom field on the issue is a separate, harder problem this tutorial does not claim to solve. One real technique exists in the community (a 'lazy transmission' on portal redirect), with a documented gap for API- or automation-created requests, and the original asker who raised it never confirmed which approach they ended up using.
A second community thread shows Atlassian's own staff correcting a fact stated as settled two years earlier: the panel's serviceDeskId does not always equal the portal's portalId, per a linked, real bug ticket. Treat any single old forum answer as provisional, even a staff one.
A jiraServiceManagement:portalRequestCreatePropertyPanel module is supposed to be one of the simpler Forge surfaces: a small form on the customer portal's request-creation screen, a call to view.submit(), and the value lands as a Jira issue property you can read back later. On the Atlassian developer forum, one developer built exactly that, called view.submit(), and found the property came back undefined — no thrown error, no console warning, just nothing. Another developer hit the same wall three months later and asked how the first one had fixed it. The answer, when it finally came, was one wrong object shape.
The @forge/bridge package won't stop you from making the same mistake. Its own shipped types declare submit as (payload?: any) => Promise<void> — any object compiles, any object resolves. This tutorial is the shape that actually works, where Atlassian documents it, what "submitted" really means in terms of timing, how to read the value back, and an honest account of the one adjacent problem — turning that stored value into a real custom field — that nobody in the source threads has fully closed out.
Note
Prerequisites
A Forge app with a jiraServiceManagement:portalRequestCreatePropertyPanel module already scaffolded (forge create with the Jira Service Management template, or added to an existing manifest).
@forge/bridge installed in your Custom UI or UI Kit frontend — any recent version; the untyped submit signature shown below is current as of 7.0.0.
Basic familiarity with the Forge manifest and forge deploy/forge install.
A JSM project on a real or sandbox Jira Cloud site to test the portal flow against.
1
Confirm the payload shape before you write your handler
don't trust the types to tell you.
2
Reproduce why a wrong shape fails silently, and know what "silently" actually means here.
3
Submit the value correctly, taken from Atlassian's own example and a real developer's fix.
4
Check your own installed package to see the untyped signature yourself.
5
Know when the write actually happens
it isn't the moment view.submit() resolves.
6
Read the value back in jiraServiceManagement:portalRequestDetail.
7
Check the panel's context before you assume more than three fields.
8
Know that filling a real custom field from the submitted value is a separate, harder problem.
Confirm the payload shape before you write your handler
Atlassian's own manifest-reference page for the module documents the shape directly, in a "Form data schema" table: fields is a required list of field objects, isValid is a required boolean, and each field object needs a key (string) and a value (object). The page's own worked example makes the shape concrete:
That's the whole contract. Nothing about it is unusual once you've seen it written down — the problem, as the next section covers, is that nothing forces you to see it written down before you guess.
Reproduce why a wrong shape fails silently
On the developer forum, the first person to hit this wrote a module, called view.submit(), and reported: "As you see, property is undefined." He'd also tried reading the value directly via api.asApp().requestJira() in the backend and got a 404 for his trouble — the property simply didn't exist yet, because it had never been written. No error was thrown at submit time; the call resolved normally either way.
That account is the only first-hand evidence this tutorial has for the failure mode — this tutorial does not reproduce a fresh wrong-shape submit against a live app to show you a screenshot of nothing happening, and it would be dishonest to imply otherwise. What's independently confirmed is the mechanism that makes silent failure possible in the first place: the shape isn't enforced by the tools that would normally catch it. It's the same class of trap as another silent @forge/bridge failure mode — a call that resolves cleanly while doing something other than what you asked. The next two sections cover both halves of that — the correct shape, and the fact that your editor and your build will accept an incorrect one without complaint.
How to submit a value from the JSM module (the fix)
Three months after the first developer's post, a second developer, Vikram1, asked the same question on the same thread: "I am stuck at the same step trying to figure out how to invoke view.submit, will you be able to share how did you manage to submit value to a property?" The original poster answered with the actual fix:
"The issue was caused by an incorrect object structure in my submission," he wrote. Vikram1 confirmed it worked: "Fantastic, works like a charm! Thanks a lot." Two independent developers, three months apart, landed on the exact shape the manifest-reference page's own example already showed — a fields array of { key, value } objects, plus isValid. If your handler passes anything else — a bare value, a differently-shaped object, a nested structure that seems reasonable — it will compile, it will resolve, and it will store nothing.
Check your own installed package
You don't have to take the "the types won't catch you" claim on faith. Pull the package and read its own declaration file:
That's the entire type signature, as shipped in @forge/bridge@7.0.0: an optional parameter typed any, returning a Promise<void>. Nothing in TypeScript will flag a malformed fields array, a missing isValid, or a value in the wrong place — the function accepts anything and resolves regardless of whether it did what you meant. The same package's out/types.d.ts types the extension context object handed to sibling modules (including the request.property field covered two sections down) as { [k: string]: any } too, so the gap isn't limited to the write side. It's worth reading the package you've actually got installed rather than assuming — the same habit that caught a resolver-breaking import change in @forge/bridge before it shipped.
How you know it worked: run the npm pack/tar/cat sequence above yourself against whatever version you have installed. If your output differs from the any-typed signature shown here, a newer release has tightened it — worth knowing either way, and worth re-checking before you trust this tutorial's premise on a future version.
Know when the write actually happens
The manifest-reference page is specific about timing, and it's worth reading exactly: "The view.submit method can be invoked every time the fields in the Forge portal request create property panel form is updated. The field data would be stored in the Jira issue property when the request form is submitted." Those are two different moments. You can call view.submit() on every keystroke or field change to keep local state in sync, but the actual write to the Jira issue property happens when the customer submits the whole portal request — not independently, on each call. Build your handler assuming the last view.submit() call before the customer clicks Send is the one that counts, and don't rely on an individual call, by itself, to have already persisted anything.
Reading it back in jiraServiceManagement:portalRequestDetail
Once the request is submitted, the same manifest-reference page documents exactly where the value goes and how to get it back. The property is stored under a key matching the UUID component of your own app's app.id. Atlassian's own example: if your app.id is ari:cloud:ecosystem::app/d3adb33f-2ed0-4502-82f5-54ae21ea2f72, the issue property key is d3adb33f-2ed0-4502-82f5-54ae21ea2f72, and it's readable via the standard issue-properties REST endpoint:
That endpoint is the same general-purpose getIssueProperty operation Jira exposes for any issue property — Atlassian's own API reference documents it as accessible anonymously in principle, gated by the Browse projects permission (and issue-level security, if configured) rather than anything specific to Forge apps. Inside a sibling jiraServiceManagement:portalRequestDetail module, you don't need the REST call at all — the same manifest-reference page's example shows the stored value arriving directly in the extension context:
The reference page for portalRequestDetail confirms request.property as a real field on that context object, described as "The request properties (if any) stored during request creation through jiraServiceManagement:portalRequestCreatePropertyPanel module" — the two modules are meant to be read as a pair.
Context of the JSM portal request create property panel: don't assume more than three fields
Before you reach for anything beyond view.submit(), it's worth knowing what the panel's own context actually contains, because a separate community thread shows people assuming more than what's there. One developer, working from the module's Custom UI context, found it exposed only three parameters: moduleType, portalId, and requestTypeId. An Atlassian staff member confirmed this by running view.getContext().then(console.log) herself and reported that the returned value is a serviceDeskId, usable with the Jira Service Management API to fetch a project ID, available at the /extension/portal/id path, and stated at the time that "the service desk ID matches the portal ID."
That last claim didn't hold up. Nearly three years later, in the same thread, a different developer posted that "sometimes, the serviceDeskId is not equal to portalId," linking a real Atlassian bug, JSDCLOUD-18432 — open at the time he linked it, since resolved as Fixed. Nobody has since gone back and reconciled the two posts in the thread itself. If your app depends on serviceDeskId and portalId being interchangeable, that's an assumption worth testing on your own site rather than inheriting from an old forum answer, staff-confirmed or not — a fixed bug ticket doesn't tell you whether the underlying behavior it described is gone for good or just less common now.
Separately: if the panel needs to be visible to unauthenticated portal customers rather than only licensed Jira users, one developer in the same thread traced a missing-panel bug to unlicensedAccess not being set in the manifest — a property he described as documented on the sibling portalRequestDetail page but, at the time of his post, absent from the create-property-panel page itself. That gap is closed now: the panel's own manifest-reference page currently lists unlicensedAccess directly in its Form data schema table. If your panel isn't showing for portal customers, that's the property to check first.
Filling a custom field using a value submitted from the JSM portal request create property panel
Getting a value into an issue property is not the same as getting it onto a visible custom field on the issue itself, and that second step is not solved anywhere in the sources this tutorial draws from. One developer, in a different thread, described having view.submit() working and the value landing as an entity property exactly as this tutorial describes — but needing it to also populate a real custom field that shows up on the created issue.
A developer from a third-party Forge app responded with a real, named technique: a "lazy transmission" function triggered when the property panel is opened from the portal request, which moves values from the stored property map (keyed by field key, e.g. { customfield_123: "foo" }, with the whole property itself keyed by the app ID) onto the actual custom fields. He was explicit about its limit: "Our approach has problems when the request is created via API or automation, as there is no redirect to the portal view" — the technique depends on the customer actually loading the portal page. He also mentioned Forge triggers as a possible alternative, unconfirmed at the time he wrote it.
The original asker said he'd try triggers first and fall back to the lazy-transmission approach if that didn't work. No further post in that thread ever reports back which one he used, or whether either worked. If you need this — a real custom field, not just an issue property — budget real investigation time for it rather than assuming it's a small extension of the fix in this tutorial.
What this tutorial doesn't prove, and what's still open
This tutorial confirms the payload shape, the untyped signature that fails to catch a wrong one, the write-timing distinction, and the read-back path, all sourced to Atlassian's own manifest-reference pages, the shipped @forge/bridge types, and a real, verbatim developer fix. It does not reproduce the silent-failure behavior against a fresh live deploy — the evidence for "silent" is the original developer's own first-hand account, not a screenshot taken for this tutorial. It does not resolve whether serviceDeskId and portalId are ever safely interchangeable on your own site — the sources disagree with themselves on that, three years apart. And it does not solve turning a stored property into a visible custom field; that remains open in the community thread that raised it, with one real but limited technique on the table and no confirmed outcome.
Key takeaways
You now have the exact payload shape view.submit() needs — { fields: [{ key, value }], isValid: true } — confirmed against Atlassian's own manifest-reference example and a real developer's fix, plus a live check of your own installed @forge/bridge showing why nothing in the tooling will catch a wrong shape for you.
You now have the correct read-back path in both directions: the REST issue-properties endpoint keyed by the UUID component of your app's app.id, and the context.extension.request.property path inside a sibling jiraServiceManagement:portalRequestDetail module.
You now have the timing distinction that matters — view.submit() can be called on every field change, but the write to the Jira issue property happens when the customer submits the whole portal request, not before.
This does not cover turning a stored issue property into a real, visible custom field on the issue. That remains an open problem in the community thread that raised it, with one limited technique on the table and no confirmed outcome — do not assume it's a small extension of the fix here.
This does not cover a live reproduction of the silent-failure behavior against a fresh Forge deploy. The evidence for "silent" is a real developer's first-hand account, sourced and quoted, not a screenshot taken for this tutorial.
LeanZero at Atlassian Team '26 Europe Amsterdam: no booth, here to talk Forge migrations