Skip to main content

Documentation Index

Fetch the complete documentation index at: https://ekso.dev/llms.txt

Use this file to discover all available pages before exploring further.

Source platforms have custom fields. Ekso has process fields. The migrator bridges the two with a 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_10026 numeric IDs that vary per tenant.
  • Linear has a single first-class estimate decimal.
  • Azure DevOps uses Custom.<FieldName> reference names.
  • Zendesk has typed ticket_field IDs.
  • Gemini has named custom fields.
Without a field map, every source custom field falls through to 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 run apply --field-map migration.fields.yaml, the Apply layer’s ProcessFieldApplier runs before any item writes:
  1. It reads your YAML.
  2. For each entry, it queries the destination DataProcess (the one you passed to --process) for fields with that name.
  3. If the field exists, it’s a no-op.
  4. If the field doesn’t exist, it calls POST /api/field to create it on the process. The field’s kind (text / decimal / picker / toggle) and any picker values come from the YAML.
  5. If field creation fails (permission denied, validation error), apply exits with code 6 before writing any items.
This means after 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

<source>:
  <source-field-id>: { ekso: <EksoFieldName>, kind: <kind>, ...kind-specific }
Top-level keys are source names: jira, linear, devops, zendesk, gemini. The migrator reads only the section matching the source you’re migrating.

Field kinds

kindStoresExample source fields
textFree-form stringJira sprint name, Linear project label
decimalNumeric valueStory points, estimates, custom durations
pickerOne value from a fixed listSeverity, Risk, Phase
toggleTrue / false”Has-PR-attached”, “Customer-facing”

text example

jira:
  customfield_10018: { ekso: Sprint, kind: text }
The Apply layer creates a text field named Sprint on the destination process if it doesn’t exist, then writes the source value into it on every item.

decimal example

jira:
  customfield_10026: { ekso: StoryPoints, kind: decimal }

linear:
  estimate: { ekso: StoryPoints, kind: decimal }

devops:
  Microsoft.VSTS.Scheduling.StoryPoints: { ekso: StoryPoints, kind: decimal }
All three sources can map their estimate field to the same Ekso 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:
jira:
  customfield_10031:
    ekso: Severity
    kind: picker
    picker:
      Critical: P0
      High:     P1
      Medium:   P2
      Low:      P3
The Apply layer creates a 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

zendesk:
  has_attachments: { ekso: HasAttachments, kind: toggle }
Zendesk’s 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

jira:
  customfield_10026: { ekso: StoryPoints, kind: decimal }
  customfield_10018: { ekso: Sprint,      kind: text }
  customfield_10031:
    ekso: Severity
    kind: picker
    picker:
      Critical: P0
      High:     P1
      Medium:   P2
      Low:      P3
Discovering Jira custom-field IDs. Jira’s per-project custom field IDs are visible in the URL of the field’s edit page in admin (customfield_10026), or via the REST API:
curl -u [email protected]:ATATT3... \
    https://acme.atlassian.net/rest/api/3/field \
    | jq '.[] | select(.custom == true) | {id, name}'

Linear

linear:
  estimate: { ekso: Estimate, kind: decimal }
Linear’s first-class estimate field is the only thing most teams need to map. Custom fields beyond that are uncommon on Linear.

Azure DevOps

devops:
  Microsoft.VSTS.Scheduling.StoryPoints: { ekso: StoryPoints, kind: decimal }
  Microsoft.VSTS.Common.Severity:
    ekso: Severity
    kind: picker
    picker:
      "1 - Critical": P0
      "2 - High":     P1
      "3 - Medium":   P2
      "4 - Low":      P3
  Custom.RootCause: { ekso: RootCause, kind: text }
Discovering DevOps reference names. From the work-item form: open any work item, click the field, the reference name shows in the field properties. Or via REST:
curl -u :PAT https://dev.azure.com/your-org/your-project/_apis/wit/fields?api-version=7.1 \
    | jq '.value[] | {referenceName, name, type}'

Zendesk

zendesk:
  "360001234567": { ekso: AffectedComponent, kind: text }
  "360001234568":
    ekso: CustomerTier
    kind: picker
    picker:
      Free: tier-free
      Pro:  tier-pro
      Ent:  tier-ent
Zendesk ticket-field IDs are numeric strings — quote them in YAML so they’re treated as keys, not numbers. Discovering Zendesk ticket field IDs:
curl -u [email protected]/token:API_TOKEN \
    https://acme.zendesk.com/api/v2/ticket_fields.json \
    | jq '.ticket_fields[] | {id, title, type}'

Gemini

gemini:
  PullRequestUrl: { ekso: PullRequestUrl, kind: text }
  Severity:
    ekso: Severity
    kind: picker
    picker:
      Critical: P0
      High:     P1
      Medium:   P2
      Low:      P3
Gemini custom fields are accessed by name. SQL mode reads them from dbo.IssueCustomFields; API mode pulls them from the issue payload.

Without a field map

You can run apply 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

After apply 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.
The created fields appear under the process’s field list in the admin UI. You can rename, reorder, or delete them after the fact — the migration doesn’t lock anything.

Failure modes

SituationBehaviour
YAML references a kind the migrator doesn’t supportexit 2 (usage error) before any work happens
picker field with no picker: value listexit 6 (validation) — picker fields need values
Field creation fails (permission denied)exit 4 (forbidden), no items written
Source value doesn’t match any picker valuethe source value is preserved in Meta, the item field is left null, and apply continues
Decimal field gets a non-numeric source valueMeta fallback for that item, warn at end

Where to next