Guide · read once, then keep it open
Configure an onboarding journey for a country
Follow these eight steps and you will finish with a complete, validated onboarding configuration for a new country — journey, forms, account products and wording — rendering correctly on web and mobile, downloaded as a change bundle you raise as a pull request. No JSON by hand, and no engineer needed to author it.
◆ How config fits together#
Three layers. Understanding them makes every screen in the studio obvious, and prevents the one mistake that causes real damage: editing something shared when you meant to change one country.
One shared definition per form — basic_info, address,
ekyc… It defines which fields exist and how they behave everywhere.
A small patch holding only what this country changes — optionally narrowed to one channel (web / android / ios / ussd).
The order of screens for this country, plus the conditional branches (FATCA, CRS, extra documents) and where the customer rejoins the main flow.
What follows from this
- You never edit a base form to serve one country. Open the form for your country and change what differs; the studio records just the difference. Kenya renaming “State” to “County” is a two-line overlay, not a copy of the form.
- A journey references forms, it doesn't contain them. Reordering steps never touches field definitions.
- Precedence is base → all-channel overlay → channel overlay. A web-only overlay wins over an all-channel one, for web only.
▸ Set up the studio#
npm install
npm start # then open the URL it prints
Requires Node 24.15 or newer. If npm start complains about your
Node version, switch it (nvm use 26) and try again.
Three things to know before your first session:
| Your draft is local | A new-country workspace lives in this browser, on this machine. It is not shared with teammates and does not sync. Don't clear site data mid-task, and don't expect a colleague to pick up your draft — download the bundle, raise the pull request, and collaborate there instead. |
|---|---|
| One draft at a time | The studio holds a single new-country workspace. Discard workspace deletes it permanently. |
| The studio hands you files | It never writes to the repository and never deploys. You finish by downloading a
change bundle — a zip of the exact files, at the exact paths the
service expects — then add them to the DigitalOnboardingSvc repo and
raise the pull request yourself. A human reviews and merges; only then does the
lower environment pick it up. |
Offline mode — the safe sandbox. Out of the box the studio reads seed data bundled into the app rather than reading it from the service. Nothing you do can affect the repository either way, so it's the way to learn the tool without consequences.
The catch is staleness: a bundled snapshot only knows the countries it was built with, so a country published to the service since then won't appear. Every bundle you download records which source it came from and when. Check that stamp before handing a bundle to anyone — configuration built on a stale base is the one mistake this mode makes easy.
1 Create the workspace#
Catalogue → New country · about 2 minutes
Always start by cloning a country that already works. You inherit a journey that is known to be valid and adapt what differs — far faster and safer than assembling one from nothing.
| Field | What to enter |
|---|---|
| Country code | The 2-letter ISO code, e.g. TZ. Rejected if that country already exists. |
| Display name | e.g. Tanzania. |
| Environment | Testing (lower env) while you build. |
| Channels | The channels this country will actually launch on. For web and mobile, tick web, android and ios. |
| Clone from | The country closest to your target. Pick Start from scratch only if nothing resembles it — you would then build every step by hand. |
| Also copy overlays | Leave on. You inherit the source country's form tweaks and can remove what doesn't apply. |
| Deep-copy country data | Leave on to copy the source account products and privacy policy instead of starting with an empty account catalogue. |
| Local currency | Confirm the new country's three-letter currency code. Products in the source country's local currency are remapped to it; foreign-currency products such as USD, EUR and GBP keep their original currency. |
Press Create workspace →. The studio deep-copies the source journey — steps,
branches, workflow settings, exit rules — and rewrites its wording keys from the source
country to yours (retail_onboarding.ke.step… → …tz.step…), carrying
the source's text over as your starting point. When country-data copying is selected, the
account products and privacy policy are independently deep-copied and the local product
currency is remapped to the code you confirmed.
You land on the workspace hub: three numbered sections that double as your to-do list. You can leave and return at any time; the draft persists in this browser.
- The hub header shows your country, its environment badge and the channels you chose.
- Section 1 lists a journey with a step count (e.g. “7 main steps · 2 branches”).
2 Shape the journey#
Hub section 1 → Edit journey · every change auto-saves to your draft
This is where you decide what happens in what order. The Flow map at the top is a read-only picture of the current path — check it before and after editing.
Main steps
- Order is the customer's experience. Reorder with the ↑ / ↓ arrows.
- Step type matters — see the types table. Use
FORMfor data entry,REVIEW_FORM/CONFIRM_FORMfor “check your details” screens,CONSENT_FORMfor accept/decline,STATUS_PAGEfor a wait-while-we-check screen, andINFOfor a screen with no form attached. - Form is the base form the step renders — choose from the list. A typo becomes a publish-blocking error rather than a production surprise.
- Title must be a wording key, not literal text (e.g.
retail_onboarding.tz.step.basic_info.title). Literal text still publishes but raises a warning, and customers see that same string in every language. - Remove steps this country doesn't need. That never deletes the form — other countries keep using it.
Workflow settings
Three values the backend needs. Both step references must name main steps (not steps inside a branch), or publishing is blocked.
initiateAfterStepId
The step after which account opening kicks off — usually the first data-capture step.
completeOnStepId
The step that finishes the journey.
otpEnabled
Whether the workflow sends a one-time password.
Branches — the conditional detours
| Setting | Meaning |
|---|---|
| Branch id | Unique name, e.g. fatca. |
| Trigger after step | The main step after which the condition is evaluated. |
| Resume before step | Where the customer rejoins the main flow once the detour ends. |
| Condition | all (every part must pass) or any (one is enough). Each part reads a field captured in an earlier step, using either an operator (EQUALS, IN, IS_NOT_EMPTY…) or a named predicate (isFatcaCountry, isCrsCountry). |
| Branch steps | The screens inside the detour, in order. |
The rule people trip over. A condition may only read a step at or before its trigger — you cannot branch on an answer the customer hasn't given yet. The linter says so plainly if you get it wrong.
Exit rules (on branch steps) end a journey early — for example when a
customer declines a mandatory waiver. Each needs a condition, the action
terminate_journey, and a reason.
Read the Lint panel before you leave
It sits at the bottom of the builder. Errors block publishing; warnings are advice worth taking. Aim for zero errors now rather than at handoff time — see what blocks a handoff.
- The flow map reads like the journey you intend, in order.
- The lint panel says references resolve and anchors are valid — zero errors.
- You've pressed Done — back to workspace →.
3 Adapt the forms#
Hub section 2 → Edit · the longest step, and the one that decides how screens look
The table lists every form your journey touches, whether it already carries overlays, and an Edit → link. Open only the forms that genuinely differ for your country; inherited overlays are often already correct.
First, set the scope
Scope decides where your change lands, and it is the most common thing to get wrong.
All channels
The change applies everywhere in this country — web and mobile alike. Start here for wording, validation and new fields.
Only {channel}
A channel-specific tweak, e.g. a different layout on web. Switch the Channel selector first, then choose “Only …”.
Making one form work on web and mobile. You do not build two forms. One set of fields and rules serves every channel; the apps lay them out differently — mobile stacks fields one per row, web pairs them two-up. So:
1 · Author fields, validation and wording under All channels. 2 · Check the preview in web, then android/ios. 3 · Only if a channel genuinely needs a different arrangement, switch to Only {channel} and adjust the layout there.
Then work through the tabs
| Tab | Use it to |
|---|---|
| Fields | Add or remove fields; set type and widget; mark required; point at the label and placeholder wording keys; define dropdown options; and set per-field validation — length, pattern, format, plus the error-message key for each rule. |
| Layout | Order fields and group them into rows. A field with no layout row is invisible to the customer — the preview flags exactly this. |
| Rules | Rules spanning several fields (“if A is filled, B is required”). Write the expression, give it an error key, and test it against sample data right there. Single-field validation stays in the Fields tab. |
| Raw JSON | The escape hatch — the resolved schema and UI config. You shouldn't normally need it. |
| Translations | The wording behind every key this form uses, per language, with gaps called out. |
Watch the live preview as you type
The preview on the right renders the screen the way the selected channel's app renders it, and it is interactive: choose values, watch conditional fields appear, watch validation fire. Fill sample populates plausible data in one click.
Below it, Generated change shows the exact patch your edits produce. If that panel is empty you haven't changed anything that differs from the base — which is a perfectly good outcome.
Finish with Save to workspace →. Overlays don't publish individually here; they're bundled into the country's single change set.
- Every form that needed changing shows an overlay count on the hub table.
- Each edited form's preview looks right in web and in android.
- No field you intended to show appears under “Not shown to the customer”.
4 Account products, privacy & consent#
Hub section 3 → Edit country data · /country-data/{CC}
Not every piece of country configuration is a form. The account the customer picks, the currencies on offer and the privacy notice are database-backed country data: the app fetches them from onboarding lookup endpoints. Terms & conditions and other consent documents remain linked content on their FormEngine forms. A country with a perfect journey but no account products still cannot onboard anyone.
Account products
One entry per account the customer can open, keyed by currency + residency + scheme code — that combination must be unique, and the studio blocks publishing if you repeat one. Each product carries what the picker renders: account type, description, features, requirements, the tariff guide link, and the optional cost/special notes.
Currencies are derived, not authored. The currency list a client sees is simply the distinct currencies across this country's account products — add a product in USD and USD appears. The service builds the list the same way and then describes each code from the shared DataLookup catalogue. Removing an editor row does not delete an existing database row; deletions need an explicit reviewed migration.
Cloning a country copies its products as a starting point. Always revisit them:
scheme codes, requirements and the tariff guide are country-specific, and a copied
SB100 from Kenya is almost certainly wrong elsewhere.
Privacy and consent documents
The privacy notice is a database-backed lookup. Configure its published document link, journey,
content type (url) and media type (application/pdf) here. It must use
https. Use Open the document ↗ to confirm the link resolves before
publishing — a 404 here is a legal problem, not a UI one.
Follow the service's current ownership. Terms & conditions has no
lookup of its own: configure it in the confirm form editor, where the live
app reads x-linkedContent.links[]. Configure other consent documents in their
corresponding form's x-linkedContent. Those edits publish as FormEngine country
patches.
What this produces
One native EF data migration under
OmniDos.Onboarding.Infra/Migrations. It upserts account products and privacy-policy
rows through the database-migration path DigitalOnboardingSvc already runs at startup. In a
draft workspace it goes out with the rest of the change set; for a live country it downloads
as a bundle of its own.
- Zero blocking issues on the country data screen.
- Every account the country actually offers is listed, in every currency it offers.
- The currency chips match what you expect a customer to see.
- The privacy link opens the right, current document.
- Terms and other consent links are configured on their service-owned forms.
5 Wording & languages#
Form editor → Translations tab
Every visible string is a key (identity.documentNumber.label),
and the text behind it is authored per language. Nothing in the config holds literal customer
text — that's what makes one form serve many languages. The service swaps the key for the
text as it answers the request, so a customer can switch language mid-journey and the form
itself never changes.
- The Translations tab lists the keys this form uses and which languages are missing. Fill the gaps for every language the country launches in.
- In the preview, switch Lang to check each language, and toggle keys to reveal the raw key behind any string.
- If you see a key where you expect words (e.g.
address.county.label), that key has no text for the selected language. Add it.
Shared wording. Form-level keys are usually shared across countries, so changing that text affects every country using it. When wording should differ for your country only, point the field at a country-specific key instead.
- Walking the journey with keys off shows real sentences on every screen — no dotted keys.
- Each launch language is filled for the keys your country introduced.
6 Walk it as a customer#
Hub → Preview journey (or /preview/{CC}) · the step that catches what reading config never does
Walk the whole path, exactly as a customer would:
- Fill sample fills the current screen; Fill & next fills and advances. Or type real values to test a specific case.
- The step rail shows the whole path; branch steps carry their branch id. Click any step to jump.
- The Branches tab marks each branch entered or skipped — with the value that decided it, so “why didn't FATCA fire?” answers itself.
- The Data tab shows what's been captured step by step, with Copy JSON for the whole payload.
- Switch channel and Lang to check every surface and language.
Read the two panels below the phone
Blocking the CTA
Why the Continue button is grey — field by field, with the rule that isn't satisfied.
Not shown to the customer
Fields that exist in the config but will never render: no layout row, hidden by design, or waiting on a visibility rule (it names which).
Cover these cases before you publish
- Web from first screen to last.
- Android / iOS from first screen to last — the mobile layout is one field per row and has a sticky button.
- Every branch you expect to fire — change the country of residence, decline a waiver, leave optional fields empty.
- Every language the country launches in.
- The review / confirm screens — do they show the data captured earlier?
What the preview mocks. Camera and fingerprint steps show a
stand-in image rather than using a real device — deliberately, so you can walk past a
capture step and check everything after it. Named branch predicates
(isFatcaCountry, isCrsCountry) are approximated; the
backend decides for real. Treat branch entry here as a strong signal, then confirm in the
lower environment after merge.
- You reached “Journey complete” in web and in mobile.
- Each branch you expect entered when you gave it the triggering answer.
- No screen showed a raw wording key or an unexpected empty field.
7 Review & download#
Hub section 4
The final section shows the complete change set — the journey file, one file per overlay, the country data migration, the translation store and one copy catalogue per language — with every path listed. The download is blocked while any lint error remains, and the panel tells you which.
Download {CC} bundle → gives you a single zip containing everything, which you hand to the repository in step 8. Inside:
| In the zip | What it's for |
|---|---|
| repo/ | Your files, already at the paths the service expects. Copy this over the repo root and everything lands where it belongs — no deciding where files go. |
| PR.md | A ready-written pull request description: what changed, which country, every file, and the review checklist. Paste it in rather than writing one. |
| manifest.json | Where the configuration was read from and when — the record that says the bundle wasn't built on stale data. |
| README.txt | The step-8 instructions, for whoever opens the zip without this guide open. |
See what's in the bundle if you want to understand the files themselves.
- The file list matches what you changed — nothing unexpected.
- Zero blocking issues.
- The zip is on your machine and its
manifest.jsonnames a source you trust.
8 Raise the pull request#
The studio has no access to the repository — by design, so it never holds a credential that could write to it. You take the bundle the last few metres.
You need repo access for this step. It calls for a clone of
DigitalOnboardingSvc and enough Git to push a branch. If you don't have
both, hand the zip to someone who does — everything they need is inside it, and nothing
about the configuration changes in transit.
- Unzip it and copy the files in. The
repo/folder mirrors the repository's own structure, so a single copy puts every file at its correct path:unzip {CC}-config-bundle.zip cp -R {CC}-config-bundle/repo/. /path/to/DigitalOnboardingSvc/ cd /path/to/DigitalOnboardingSvc && git statusgit statusshould list exactly the files the studio showed you in step 7. If it lists more, you copied from the wrong level. - Branch, commit and push.
git checkout -b config/{cc}-onboarding git add . && git commit -m "Add {CC} onboarding configuration" git push -u origin config/{cc}-onboarding - Open the pull request against the lower-environment branch your team
uses, and paste
PR.mdas the description. It already lists the country, the files and what reviewers should check, so there's nothing to write. - Get it reviewed — ideally by someone who can re-walk the journey. Give them the review checklist.
- Merge — the lower environment then picks the configuration up.
- Verify in the lower environment, especially anything the preview only approximates: branch predicates, real document capture, the actual workflow and OTP.
- Then talk about production.
Don't skip step 6. The studio guarantees the config is structurally valid and shows you how it renders. It cannot prove the backend's predicates, capture SDKs or workflow behave as you expect. One pass through the lower environment closes that gap.
✓ You're finished when…#
- The journey lints clean — zero errors, warnings understood.
- Every form the journey uses renders correctly on web and mobile.
- Required fields, formats and cross-field rules behave in the preview.
- Branches enter and resume where you intended.
- No raw wording keys in any launch language.
- Account products cover every currency the country offers, and both legal links open.
- The change set is one PR, reviewed and merged.
- Behaviour confirmed in the lower environment.
▤ Change a country that already exists#
No workspace is involved — you're editing live configuration, so changes are handed off one at a time, each as its own small bundle and its own pull request. Everything else (scope, tabs, preview, lint) works exactly as above.
| Task | Where | Then |
|---|---|---|
| Journey change add / reorder / remove a step, adjust a branch |
Catalogue → country → Open journey builder → | Preview it, resolve lint, then Download journey bundle. |
| Account product, currency or privacy link add an account or change the privacy URL |
Catalogue → country → Country data → | Resolve any blocking checks, then Download database migration bundle. |
| Terms or consent link change linked legal content |
Catalogue → country → the owning form editor → | Edit x-linkedContent, preview it, then download the country overlay bundle. |
| Form change for one country relabel a field, add validation, adjust layout |
Catalogue → country → form Edit → | Set the scope, change, check the preview, then Download overlay bundle. |
◎ Reviewing someone else's change#
You don't need to read patch JSON to review well. Ask five questions:
- Does the description match the files? A “relabel one field” change shouldn't touch the journey.
- Is the scope right? A channel-specific file
(
…_web_ui_patch.json) where the change should apply everywhere — or the reverse — is the most common mistake. - Are titles and labels wording keys, with translations included? A change
that adds keys but touches neither
translations/translations.jsonnorLocales/{lang}.jsonshows raw keys to customers — the service falls back to English and then to the key itself. - Do branch anchors still make sense if steps were reordered?
- Has the author walked it? Ask which branches fired and which channels they checked.
The fastest review is to check the branch out, run the studio, and walk the journey yourself.
⟨⟩ Hand it to the client teams#
The web, iOS and Android apps don't read the files you publish — they call the onboarding API and render whatever comes back. API Response Mock shows exactly those payloads, rebuilt from your configuration, so a client team can stub against them before anything ships.
Open it from the sidebar (Client contract → API Response Mock), from a
country page, or from the journey builder. Address: /api-mock/{CC}. It is
read-only — nothing you do there changes configuration.
What's on the page
| Panel | Mirrors | Use it for |
|---|---|---|
| Journey config response | GET /journey-configs?countryCode=&journeyId=&channel= |
The flow the client renders: steps, sequence, step types, workflow anchors, branches. |
| Lookup responses | GET /onboarding/lookups/{currencies, account-products, privacy-policy} |
Country data — the account picker, the currency list and the legal documents. |
| Form config responses | the form object on GET /onboarding/onboardings/{id}/steps/{stepId} |
One per step — the resolved schema + uiSchema the client builds the screen from. |
Pick the channel before you copy. The page resolves the same way the service
does: a variant matching that channel exactly, otherwise the channel-less one, with
channel-specific overlays applied on top of the shared ones. Switching between
web and ios is the quickest way to prove a channel overlay
actually lands.
Taking it away — three buttons
| Button | You get | Good for |
|---|---|---|
| Copy all JSON | One bundle on the clipboard: the journey config response plus every step's form config response. | Pasting straight into a chat or a ticket. |
| Download .json | That same bundle as a single file. | Attaching to a ticket or an email. |
| Download .zip | A folder with each config as its own file — journey-config.json,
forms/{formKey}.json one per step, lookups/*.json, and a README.txt
recording the country, journey, channel, endpoints and the caveats below. |
Client teams who want one fixture file per response in their test suite. |
Each panel also has its own Copy JSON, and so does every JSON block elsewhere in the studio (raw form JSON, generated patches, the journey seed file, the workspace change set, the walkthrough's captured payload).
The bundle's outer wrapper is studio metadata. In the single-file
bundle, only journeyConfig and the entries of formConfigs are
verbatim API payloads — generatedBy and request just record what
was generated and for which country/journey/channel. In the zip there is no wrapper at
all: every .json file is exactly one response.
Four things the mock deliberately doesn't fake
hashandcontext. The live form payload carries a server-computed hash of the resolved documents plus a context dictionary. Both are produced at resolve time, so the mock leaves them out rather than inventing values — clients should treat them as present but opaque.- Branch conditions stay server-side. A branch reaches the client with
triggerHintonly; the real condition tree and any step exit rules are evaluated by the backend and never serialized. otpEnabledis internal. Clients seeworkflowConfigwithinitiateAfterStepIdandcompleteOnStepIdonly.- Names on the wire differ from the seed files. Step types are PascalCase
(
REVIEW_FORMin the file becomesReviewFormin the response), and nulls are serialized rather than dropped — a branch with noresumeBeforeStepstill shows"resumeBeforeStep": null. - A
STATUS_PAGEstep carries no form. It resolves one server-side, but the client receives"form": nulland polls the step's status endpoint instead.
It shows what you've configured, not what's deployed. For an existing country the mock reflects the published seed data; inside a draft workspace it reflects your unsaved draft. Say which one you sent when you share it.
While editing a single form there's a smaller version of the same thing: the API mock tab in the form editor shows that one form's payload with your current, unpublished edits applied.
! What blocks a handoff#
Three independent checks. Use all three — they catch different classes of mistake.
1 · Journey lint — structure
Errors block the download:
- A step with no id, or a duplicate id.
- A step whose type needs a form but has none, or that references an unknown form.
- A NewGen group (
sourceReference) on a step that isn't a FORM step, a group NewGen doesn't return (contacts,preferredAddress,sourceOfIncome,biometrics), or one group mapped by two steps of the same journey. - Workflow settings pointing at something that isn't a main step.
- A branch whose trigger or resume anchor isn't a main step, that has no condition, or whose condition reads a step after its trigger.
- An exit rule with an empty condition or an unknown action.
Warnings — fix if you can: a title that isn't a wording key; no workflow settings; a branch with no steps; an exit rule with no reason.
Step ids don't have to match their form key, and one form may back several steps — the
service binds each step to its own formKey. Country review also warns when a
form mapped to a NewGen group has no property for one of that group's parameters.
2 · Form validation — per screen
Per-field rules (required, length, pattern, format) and cross-field rules. The preview enforces both live: the button stays grey until the screen would pass, and Blocking the CTA names each offender.
3 · The walkthrough — sequence
The only check that covers data carried between steps, branches firing, review screens filling themselves in, and each channel rendering.
⌸ What's in the bundle#
Database-backed country data is its own migration. It upserts account products and privacy policies under the Onboarding module. Consent content that the service reads from forms remains a FormEngine country patch.
Under repo/ the studio writes files the backend already understands, at the paths
it already reads. The naming is a contract, not a preference:
| What | Path in the repository |
|---|---|
| Journey | src/Modules/FormEngine/OmniDos.FormEngine.Infra/SeedData/JourneyConfig/{CC}.json |
| Country overlay | src/Modules/FormEngine/OmniDos.FormEngine.Infra/SeedData/Patches/{CC}/{form}_{cc}_v{n}_[{channel}_]{schema|ui}_patch.json |
| Country data | src/Modules/Onboarding/OmniDos.Onboarding.Infra/Migrations/{timestamp}_Seed{CC}CountryData.cs |
| Translations (authoring) | translations/translations.json |
| Translations (what the service reads) | src/Modules/FormEngine/OmniDos.FormEngine/Locales/{lang}.json |
Wording ships as two shapes of the same thing. The config holds keys, never
text; the service substitutes the text as it answers each request, reading one flat
key → text catalogue per language according to the caller's
Accept-Language. Those catalogues are what runs. The file under
translations/ is the pivot of them — every language for a key on one row, which
is the only shape a person can actually edit — and the studio generates the catalogues from
it, so both ship in the same pull request as the config that introduced the keys.
The catalogues are whole-file replacements. They are written from
the entire store, not just the keys you touched, so copy them in as they come. And a
language the service doesn't list in FormCopyLanguages.Supported — today
en and fr — is generated but never loaded; the Translations
screen flags that, and adding it there is a one-line backend change that belongs in the
same PR.
Overlays are precise lists of edits against a base form version, not copies of it. That's why a small change stays a small file — and why improving a base form still reaches every country.
? Troubleshooting#
“Could not load base form …”
The studio couldn't fetch that form's seed files. Usually a stale browser tab pointing at
a dev server that is no longer running — hard-reload the page. If it
persists, check the browser's Network tab for the failing mock/forms/…
request and confirm the form exists at that version.
Strings show as dotted keys instead of words
That key has no text for the selected language. Add it in the form editor's Translations tab. In a cloned draft, keys re-keyed to your country fall back to the source country's wording until you author your own.
The Continue button stays grey
Read Blocking the CTA under the phone — it names each field and why. Usual causes: a required field with no value, a pattern or format mismatch, or a cross-field rule that isn't satisfied.
A field I added doesn't appear
It has no layout row. Add it in the Layout tab — the Not shown to the customer panel lists exactly this case.
“{CC}'s journey isn't complete enough to edit”
The journey file exists but is missing parts the studio needs to render it faithfully —
usually workflowConfig, sometimes a variant with no steps. Rather than open
an editor that fills those gaps with defaults (which would then be saved back as if you
had chosen them), the service withholds the journey and lists what's missing.
Add the named fields to SeedData/JourneyConfig/{CC}.json and reload. This
is normal for countries that were scaffolded but never finished.
A branch never fires
Open the Branches tab in the preview: it shows each condition part and the value it read. Usually the field is empty (an earlier step wasn't filled), the source step comes after the trigger, or the value doesn't match what the condition expects.
Download is disabled
Either nothing has changed, or lint errors remain. The footer of the panel says which.
My draft vanished
Drafts are saved by the studio server and associated with this browser through a secure session cookie. Refreshing or reopening the studio keeps the draft; a different browser, cleared cookies, or a different studio deployment starts a different session. Download the bundle before clearing your session or moving to another deployment.
git status shows files I didn't change
You copied from the wrong level. It must be the contents of repo/
that land on the repo root — note the trailing /. in
cp -R {CC}-config-bundle/repo/. /path/to/DigitalOnboardingSvc/. Without it
you get a nested repo/ directory instead. git checkout . to
undo, then copy again.
≡ Tables & conventions#
Step types
| Type | Needs a form | Use for |
|---|---|---|
FORM | yes | Ordinary data capture. |
REVIEW_FORM | yes | “Let's confirm your info” — prefilled read-only summary with Looks good / Change details. |
CONFIRM_FORM | yes | Final check before submitting. |
CONSENT_FORM | yes | Accept or decline a document (waiver, declaration). |
STATUS_PAGE | yes, but the client never sees it | A waiting screen while the service checks something with a third party (TZ: nida_validation). See below. |
INFO | no | An informational screen with no fields. |
STATUS_PAGE, in more detail
This one behaves unlike the rest, so it's worth reading before you use it.
- It binds a form, but the client is sent
form: null. The schema is there so the service can validate the data it fetches itself and pass it to later steps viax-source— for TZ, the name and date of birth NIDA returns, whichreview_informationthen displays. Leave the form's fieldsx-hiddenand itslayoutempty; nothing there is ever rendered. You must still setformKeyandformVersion: the service rejects the whole journey without them. - The customer sees a waiting screen and a button. The button polls
GET /onboardings/{id}/steps/{stepId}/status. Submitting a payload to the step is rejected outright. - The reply's
actionCodedecides what happens next —00proceed (the reply carries the next step and its form),80retry (the service reverts the earlier capture steps and sends the customer back to re-capture),99kill (the journey ends). No action code yet means the check is still running and the client keeps polling. - None of that behaviour is configurable here. Which upstream check runs, and where a retry sends the customer, live in the service. What you control is where the step sits in the journey, which form backs it, and its wording keys. The API response mock page shows the replies for each action code, and the Journey preview lets you walk all three.
Widgets
| Widget | Renders as |
|---|---|
text · multiline-text | Single or multi-line entry. |
selector | Dropdown of the options you defined. |
checkbox | A single tick — consent, confirmation. |
phone-number | Dial-code picker plus number. |
country-picker | Country list. |
document-array | File upload with size and type limits. |
call-to-action · call-to-action-box | Native capture — ID scan, selfie, fingerprints. Mocked in the preview. |
remote-content | A fetched document with accept / decline. |
Condition operators
EQUALS, NOT_EQUALS, IN, NOT_IN,
GREATER_THAN, LESS_THAN, IS_EMPTY,
IS_NOT_EMPTY. Named predicates: isFatcaCountry,
isCrsCountry.
Wording key conventions
Dotted and lower-camel: identity.documentNumber.label,
address.error.zipCode.minLength,
retail_onboarding.tz.step.basic_info.title. Journey keys carry the country
segment (.tz.) and are rewritten when you clone; form keys are usually shared
across countries.
Where things live in the studio
| Screen | Address | Purpose |
|---|---|---|
| Catalogue | /catalogue | Browse base forms and countries. |
| New country | /new-country | Create and manage the draft workspace; review & download. |
| Journey builder | /journeys/{CC} | Steps, branches, workflow, exit rules, lint. |
| Form editor | /editor/{CC}/{form} | Fields, layout, rules, raw JSON, translations + live preview. |
| Journey preview | /preview/{CC} | Step-by-step interactive walkthrough. |
| Country data | /country-data/{CC} | Account products, derived currencies and privacy notice. |
| API Response Mock | /api-mock/{CC} | The payloads client apps receive; copy or download them for the frontend teams. |
Glossary
| Base form | The shared definition of a form, used by every country. |
|---|---|
| Overlay / patch | The country's (and optionally channel's) differences from a base form. |
| Journey | The ordered set of screens for a country, plus its branches. |
| Branch | A conditional detour that rejoins the main flow. |
| Exit rule | A condition that ends the journey early. |
| Channel | web, android, ios or ussd. |
| Workspace / draft | Your in-progress new country, held in this browser until published. |
| Lint | The structural check on a journey; errors block publishing. |
| Country data | Database-backed per-country configuration: account products, the currencies they imply and the privacy notice. Fetched by the client from lookup endpoints. |
| Account product | One openable account — currency + residency + scheme code, plus what the picker shows. |
| API response mock | The journey- and form-config payloads a client app receives, rebuilt from your configuration. |