migration.fields.yaml file you author by hand.
This page is the canonical format reference. Per-source pages link here for examples specific to each source.
What this solves
Every issue tracker has its own custom-field model:- Jira uses
customfield_10026numeric IDs that vary per tenant. - Linear has a single first-class
estimatedecimal. - Azure DevOps uses
Custom.<FieldName>reference names. - Zendesk has typed
ticket_fieldIDs. - Gemini has named custom fields.
DataItem.Meta losslessly — the data is preserved, but not searchable or filterable as a real field. With a field map, each source field maps to an Ekso process field that is searchable, filterable, and editable like any other field.
How auto-create works
When you runapply --field-map migration.fields.yaml, the Apply layer’s ProcessFieldApplier runs before any item writes:
- It reads your YAML.
- For each entry, it queries the destination
DataProcess(the one you passed to--process) for fields with that name. - If the field exists, it’s a no-op.
- If the field doesn’t exist, it calls
POST /api/fieldto create it on the process. The field’skind(text / decimal / picker / toggle) and any picker values come from the YAML. - If field creation fails (permission denied, validation error),
applyexits with code6before writing any items.
apply succeeds, your destination process has every field your YAML promised, populated with the source-platform values. You can audit and remove fields post-migration via the admin UI.
YAML format
jira, linear, devops, zendesk, gemini. The migrator reads only the section matching the source you’re migrating.
Process mapping (_process)
Most sources use a Type / Issue Type / Work Item Type to distinguish Bugs from Stories from Tasks. Ekso’s equivalent is the Process. The optional _process key under each source maps inbound type names to Ekso process slugs:
apply walks the cache, each item’s source type is looked up in _process to pick the destination process. If a type isn’t listed (or the source has no type concept at all, like Linear), the item falls back to the default process passed on the command line as --process.
Pre-flight validation. Before any items are written,
apply verifies that every process slug it could need actually exists on the destination Ekso tenant — both the --process default and every value under any _process block. If any one is missing, apply aborts at the start with exit code 6 (validation). Half-applied migrations against a half-complete process catalogue are worse than no migration.Field kinds
kind | Stores | Example source fields |
|---|---|---|
text | Free-form string | Jira sprint name, Linear project label |
decimal | Numeric value | Story points, estimates, custom durations |
picker | One value from a fixed list | Severity, Risk, Phase |
toggle | True / false | ”Has-PR-attached”, “Customer-facing” |
text example
Sprint on the destination process if it doesn’t exist, then writes the source value into it on every item.
decimal example
StoryPoints field — handy when you’re consolidating multiple sources into one Ekso tenant.
picker example
Picker fields are the most expressive. Map source values to Ekso picker values explicitly:
Severity picker field on the process with the four destination values (P0, P1, P2, P3), then maps each Jira severity to its Ekso counterpart on every item. Source values not in the picker mapping fall back to DataItem.Meta.
toggle example
has_attachments boolean becomes an Ekso toggle field. Source values of true / 1 / "yes" / "true" (case-insensitive) become Ekso true; everything else becomes false.
Worked examples per source
Jira
customfield_10026), or via the REST API:
Cloud authenticates with your email and API token against /rest/api/3; Data Center / Server authenticates with a Personal Access Token sent as a bearer header against /rest/api/2.
- bash / zsh (macOS, Linux)
- PowerShell (Windows)
Linear
estimate field is the only thing most teams need to map. Custom fields beyond that are uncommon on Linear.
Azure DevOps
Zendesk
Gemini
dbo.IssueCustomFields; API mode pulls them from the issue payload.
Without a field map
You can runapply without --field-map. Source custom fields fall through to DataItem.Meta losslessly. Each source field becomes a Meta key prefixed with the source name (e.g. Meta.jira_customfield_10026 = "5"). This is fine for archival migrations where the destination process schema doesn’t matter. For active use, write the YAML.
What gets created on the destination process
Afterapply runs with a field map, your destination process has:
- A new field for every entry in the YAML (created if it didn’t already exist).
- Every item populated with values from the source field.
Failure modes
| Situation | Behaviour |
|---|---|
| YAML references a kind the migrator doesn’t support | exit 2 (usage error) before any work happens |
picker field with no picker: value list | exit 6 (validation) — picker fields need values |
| Field creation fails (permission denied) | exit 4 (forbidden), no items written |
| Source value doesn’t match any picker value | the source value is preserved in Meta, the item field is left null, and apply continues |
| Decimal field gets a non-numeric source value | Meta fallback for that item, warn at end |
Where to next
- Identity resolution — author mapping.
- Command reference — every flag.
- Troubleshooting — exit codes.