Bringing Pega Constellation Forms into Native Mobile Apps — Our Experience on Pega 26

If you’re building native iOS or Android apps on top of Pega, you’ve probably faced the familiar trade-off: rebuild your Pega forms from scratch in Swift or Kotlin, or settle for a WebView wrapper that never quite feels native.

Demo : https://youtu.be/I5UOkCMvp1I

The Constellation Mobile SDK removes that trade-off. It lets you embed Constellation views directly into native Swift and Kotlin apps.

The full technical guide and implementation details are on the Pega Forum, written by Ilona Cisowska — well worth reading first: Bring Pega Constellation forms into your native mobile apps with the Constellation Mobile SDK

@MidhunKrish Thanks for your valuable insights and support along the way!

Why it’s a compelling approach

  • Centralized logic — rules, validation, and UI orchestration all stay inside Pega
  • Native UX — high-performance, smooth UI that feels natural on mobile devices
  • Lower maintenance — updates to business processes don’t require rewriting mobile app code

The architecture behind this is elegant. Built with Kotlin Multiplatform, the SDK runs Constellation’s CoreJS library inside a hidden WebView used purely as an execution engine — never shown to the user. Rather than rendering HTML, it produces a native component tree that your app renders with genuine SwiftUI or Jetpack Compose components.

So Pega continues to own the case logic, and your app owns the pixels. Change a flow or a validation rule in Pega, and the mobile app picks it up without a rebuild. That’s a genuinely powerful model.

The SDK is open source under Apache 2.0, and the team actively welcomes community feedback and contributions.

Why we tried it on Pega 26

The SDK documentation states:

“Currently Pega 24 and Pega 25 are supported.”

Support for Pega 26 is planned but hasn’t landed yet.

We were working on a Pega 26 trial instance, and downgrading it to 24 or 25 wasn’t an option for us — so we decided to try the SDK as-is and see how far we could get. The short answer: it works, with two small adjustments.

If you’re in the same position — wanting to evaluate the Constellation Mobile SDK but sitting on a Pega Infinity 26 environment — hopefully this saves you some time rather than getting stuck on the first screen.

We hit exactly two issues. Both are covered the same way below: what we saw on the device, what was actually happening inside the SDK, and the change we made to get past it.

You can confirm which Constellation version you’re on from the SDK’s own startup log:

[Bootstrap] Importing bootstrap shell:
https://release.constellation.pega.com/26.1.0-103357/react/prod/bootstrap-shell.js

Getting set up

These steps apply to any version — this is simply the path to a running app.

1. Install full Xcode, not just Command Line Tools. Kotlin/Native links against frameworks that ship inside Xcode.app.

sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

2. Use an LTS JDK. The Kotlin Gradle plugin targets established JDK versions, so if you’re on a very new JDK the build may not start. Installing an LTS release and pointing Gradle at it works cleanly:

brew install openjdk@21
# gradle.properties
org.gradle.java.home=/opt/homebrew/Cellar/openjdk@21/21.0.12/libexec/openjdk.jdk/Contents/Home

Setting it in gradle.properties rather than JAVA_HOME is handy, since Xcode runs Gradle from its own build phase with a different environment.

3. Build the XCFramework:

./gradlew :engine-webview:assembleConstellationSdkDebugXCFramework

4. Configure Info.plist with your Pega server URL, case type, and OAuth client details.

5. Set up OAuth correctly. The SDK uses Authorization Code with PKCE, which is the right flow for a native mobile app. Register a public client (no client secret — PKCE exists precisely so mobile apps don’t need one), enable PKCE, and make sure the redirect URI matches your Info.plist exactly.

At this point the app builds, launches, and authenticates.

Issue 1 — The form never renders: a throwing helper aborts FlowContainer.init()

What we saw: on first run, the blank case was created successfully in Pega — HTTP 201, all data intact — but the form itself never appeared on screen.

That combination is a useful signal: the backend is doing its job, and something in the rendering path stopped early.

Tracing it led to scripts/dxcomponents/components/containers/flow-container.component.js:

#getContainerName(oWorkData) {
    const actionName =
        this.flowContainerHelper.getActiveCaseActionName?.(this.pConn)
        ?? this.#getActiveCaseActionName(this.pConn);
    // ...
}

The optional chaining here handles the case where getActiveCaseActionNameisn’t available, falling back to the SDK’s own local implementation — which, judging by the code, is presumably the path taken on 24/25. We only had a 26 instance, so we couldn’t confirm that directly.

On Constellation 26 the helper is present, but for a newly created case — which has no active case action yet — it raises an error internally rather than returning empty:

TypeError: undefined is not an object (evaluating 't.find')

Because ?. guards against a method being absent rather than against it raising, the error propagates out of FlowContainer.init() before the Assignment component is created — so no form fields are built.

Adding a try/catch so the existing local fallback is used in both situations resolved it:

#getContainerName(oWorkData) {
    // On Pega 26 this helper is present but raises for a case with no active case action,
    // so fall back to the local implementation in that situation too.
    let actionName;
    try {
        actionName = this.flowContainerHelper.getActiveCaseActionName?.(this.pConn);
    } catch (e) {
        console.log(TAG, `getActiveCaseActionName unavailable (${e?.message}); using local fallback`);
    }
    if (actionName == null) {
        actionName = this.#getActiveCaseActionName(this.pConn);
    }
    return this.localizedVal(
        actionName || oWorkData.caseInfo.assignments?.[0]?.name,
        undefined,
        this.localeReference
    );
}

#getActiveCaseActionName(pConnect) {
    const caseActions = pConnect.getValue(...CASE_INFO_ACTIONS);
    const activeActionID = pConnect.getValue(...ACTIVE_ACTION_ID);
    const activeAction = caseActions?.find((a) => a.ID === activeActionID);
    return activeAction?.name || "";
}

With that in place, the form rendered — dropdowns, date fields and action buttons, all as native SwiftUI components.

Issue 2 — Cascading dropdowns close the form: an expected 400 ends the session

What we saw: the form rendered correctly, then dismissed itself a second or so later.

Our test case type used cascading dropdowns, where each dependent data page takes a required parameter from the field above it.

On first render those parent fields are naturally still empty, so Pega responds to the dependent lookups with:

400 — "Required parameters for data view are missing"

That’s expected behaviour: a dropdown simply waiting for its parent to be chosen.

The SDK’s error handling treats unhandled promise rejections as a signal to end the session, so these expected responses were closing the form shortly after it appeared. Treating data-view lookup failures as non-fatal handled it nicely:

if (isDataViewRequestFailure(reason)) {          // 4xx from /data_views/
    console.warn("[ErrorHandling]", "Data view lookup failed; leaving field unpopulated.");
    rejectionEvent.preventDefault();
    return;
}
bridge.onError("UnhandledRejectionError", ...);

After that, the cascade behaved exactly as designed: choose a value in the parent dropdown, and the dependent one reloads with its parameter and populates. Choose that, and the next level populates in turn.

A useful debugging tip

Worth knowing if you’re exploring the SDK’s internals: it logs a great deal of helpful detail, but through println, which writes to stdout. Since xcrun simctl log show reads os_log, the device log can look quiet even while the SDK is logging plenty.

Capture stdout directly instead:

xcrun simctl launch --console-pty "iPhone 17" com.your.bundle.id > sdk.log 2>&1

That gives you the full engine trace — bootstrap, every component created, every props update, and the WebView’s JavaScript console output bridged through to native. Extremely useful for understanding how the component tree is assembled.

One related detail: because Constellation’s bootstrap-shell.js is served from a different origin, browsers deliberately withhold detail from cross-origin script errors, which is why a generic "Script error." can appear in the global handler. Catching errors within the SDK’s own code surfaces the full message and stack.

The result

With those two adjustments, the SDK delivered exactly what it promises on Pega 26. We ran a complete multi-step case: dependent dropdowns, several sequential assignments, and final submission — then confirmed server-side that the values persisted, the stage completed, Pega ran its calculations, and the case routed onward for approval.

All of it rendered as native SwiftUI, with every rule and validation still living in Pega.

Both adjustments are small and self-contained. If you make them, commit them to your own repository so they survive a fresh clone.

Closing thoughts

The Constellation Mobile SDK solves a real problem in an elegant way. The hidden-WebView-as-engine approach means you genuinely get native performance and feel without duplicating a single business rule — and the “change it in Pega, see it in the app” workflow is exactly what teams maintaining both a Pega application and a mobile app have wanted for a long time.

Pega 26 support is already on the roadmap, so this is simply a bridge for anyone exploring the SDK on a 26 environment in the meantime. Since the SDK is Apache 2.0 and the team welcomes contributions, findings like these are best shared upstream on GitHub where they can help the wider community.

Detailed Documentation : https://stellarnexus.medium.com/bringing-pega-constellation-forms-into-native-mobile-apps-our-experience-on-pega-26-9a017c2ba448?sharedUserId=stellarnexus

If you’re building native mobile experiences on Pega, this SDK is well worth your time — and if you’re on a Pega 26 instance, hopefully this helps you get there a little faster.

2 Likes