Notes from the work

One email when a tutorial or migration write-up goes live. Nothing else, and an unsubscribe link in every email.

LeanZero

Two people in Romania doing Atlassian migrations, Forge apps and practical AI work for teams that would rather talk to the person doing the job. Most of what we learn ends up on this site.

Services

  • Atlassian Migrations
  • Atlassian FastShift
  • Atlassian Maintenance
  • Forge App Development
  • AI Development Consultation

Topics

  • Jira
  • Jira Service Management
  • Confluence
  • Bitbucket
  • Atlassian Forge
  • Cloud Migration
  • Local AI
  • AI Coding
  • Certifications
  • Atlassian Team
  • All topics

Company

  • Blog
  • Tutorials
  • Contact

Community

  • Join Discord
  • Support this site

© 2026 LeanZero. All rights reserved.

Privacy PolicyTerms of Service24/7 SupportTrust CenterSecurityDPALegal notice
  1. Home
  2. Tutorials
  3. Forge Jsm Portal Property Panel Submit
JSM Portal Request Create Property Panel Submit: Types Won't Catch a Bad Payload
Atlassian Forge

JSM Portal Request Create Property Panel Submit: Types Won't Catch a Bad Payload

Gabriela Perdum
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. 1

    Confirm the payload shape before you write your handler

    don't trust the types to tell you.

  2. 2

    Reproduce why a wrong shape fails silently, and know what "silently" actually means here.

  3. 3

    Submit the value correctly, taken from Atlassian's own example and a real developer's fix.

  4. 4

    Check your own installed package to see the untyped signature yourself.

  5. 5

    Know when the write actually happens

    it isn't the moment view.submit() resolves.

  6. 6

    Read the value back in jiraServiceManagement:portalRequestDetail.

  7. 7

    Check the panel's context before you assume more than three fields.

  8. 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:

javascript
1const handleEntityChange = async (data) => {
2  const property = { key: "some-key", value: data.target.value };
3  await view.submit({ fields: [property], isValid: true });
4};

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:

javascript
1const onSubmit = async (option) => {
2  console.log("option", option);
3  await view.submit({
4    fields: [{ key: "aip-forge-jsm-form", value: option }],
5    isValid: true,
6  });
7};

"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:

bash
1npm pack @forge/bridge
2tar -xzf forge-bridge-*.tgz
3cat package/out/view/submit.d.ts
text
1export declare const submit: (payload?: any) => Promise<void>;

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:

text
1GET /rest/api/3/issue/{issueIdOrKey}/properties/d3adb33f-2ed0-4502-82f5-54ae21ea2f72

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:

javascript
1const context = useProductContext();
2const requestProperty = context?.extension?.request?.property;

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.
ShareXLinkedIn
PreviousConvert a Fine-Tuned MLX Model to GGUF: Proving Quantization Parity with KL DivergenceNextConfluence Space Permissions After Migration: Audit Who Lost or Gained Access

Tags

forgejira-service-managementjsmportalview-submitcustom-ui

Comments

0

Add a comment

Confirm your address using the email we send. You can unsubscribe at any time. Your email stays private and this does not sign you up for the newsletter.

Have Questions?

Join our Discord community to ask questions and get help with implementing this tutorial.

Join Discord

On this page

  • Confirm the payload shape before you write your handler
  • Reproduce why a wrong shape fails silently
  • How to submit a value from the JSM module (the fix)
  • Check your own installed package
  • Know when the write actually happens
  • Reading it back in jiraServiceManagement:portalRequestDetail
  • Context of the JSM portal request create property panel: don't assume more than three fields
  • Filling a custom field using a value submitted from the JSM portal request create property panel
  • What this tutorial doesn't prove, and what's still open

Join the Community

Join the community to ask questions, share your work, and connect with other professionals.

Join Discord

Found My Advice Helpful?

If I helped you solve something for free, coffee money is always appreciated (but never expected).

Buy Me a Coffee

About the Author

Gabriela Perdum

Gabriela Perdum

Atlassian & AI Specialist

Related reading

DeepSeek V4.1 Flash coding benchmark: 0.33 on a Forge app upgrade, Opus 5.5 got 0.67

DeepSeek V4.1 Flash coding benchmark: 0.33 on a Forge app upgrade, Opus 5.5 got 0.67

Jira Gantt Chart Critical Path: Show the Work That Sets the Finish of a 1,992-Row Plan

Jira Gantt Chart Critical Path: Show the Work That Sets the Finish of a 1,992-Row Plan

Claude Haiku 5.5 vs GPT-6 Luna on two coding benchmarks: same price, 3.6x the agent bill

Claude Haiku 5.5 vs GPT-6 Luna on two coding benchmarks: same price, 3.6x the agent bill