Skip to main content

Field Mappings

Field mappings define how JPD custom fields translate to GitHub issue properties and labels. This is the heart of the sync configuration.

Understanding Field Mappings

Field mappings consist of two parts:

  1. Field Definitions - Declare which JPD fields exist and their types
  2. Field Mappings - Define how those fields map to GitHub

Field Definitions

Define JPD custom fields in the fields section:

fields:
priority:
field_id: "customfield_10001" # JPD internal field ID
field_type: "select" # Field data type
required: false # Whether field must have a value

impact_score:
field_id: "customfield_10002"
field_type: "number"
required: false

Field Properties

field_id (required)

  • JPD's internal field identifier
  • Find with: pnpm run discover-fields YOUR_PROJECT
  • Format: customfield_XXXXX

field_type (required)

  • The data type of the field
  • Valid types: select, multiselect, number, text, user, array, date

required (optional)

  • Whether the field must have a value
  • Defaults to false
  • If true, sync fails if field is empty

Basic Field Mapping

Map fields using the mappings section:

mappings:
# Map issue title
- jpd: "fields.summary"
github: "title"

Mapping Properties

jpd (required)

  • Path to the JPD field value
  • Use dot notation: fields.fieldname.value
  • Access nested properties: fields.priority.value

github (required)

  • Target GitHub property
  • Options: title, body, labels

Mapping to Different GitHub Properties

Map to Title

mappings:
- jpd: "fields.summary"
github: "title"
template: "[{{fields.project.key}}-{{fields.id | idonly}}] {{fields.summary}}"

Result:

[MTT-123] Implement new navigation system

Map to Labels

mappings:
- jpd: "fields.customfield_10001.value" # Priority field
github: "labels"
template: "priority:{{fields.customfield_10001.value | lowercase}}"

Result:

Labels: priority:high, priority:critical

Map to Issue Body

mappings:
- jpd: "fields.customfield_10002" # Impact score
github: "body"
template: |
**Customer Impact:** {{fields.customfield_10002}}/10

**Description:**
{{fields.description}}

Result:

**Customer Impact:** 8/10

**Description:**
Customers are requesting this feature...

Templates

Templates use Handlebars-like syntax for dynamic content:

Basic Variable Substitution

template: "{{fields.summary}}"

Accessing Nested Fields

# Select field with value property
template: "{{fields.priority.value}}"

# Array field (first element)
template: "{{fields.customfield_10001[0].value}}"

# User field
template: "{{fields.assignee.displayName}}"

Project and Issue Metadata

template: "[{{fields.project.key}}-{{fields.id | idonly}}] {{fields.summary}}"

Available metadata:

  • fields.project.key - Project key (e.g., MTT)
  • fields.id - Full issue ID (e.g., MTT-123 or just 123)
  • fields.summary - Issue title
  • fields.status.name - Current status

Template Filters

Filters transform values in templates:

lowercase

Convert to lowercase:

template: "priority:{{fields.priority.value | lowercase}}"

Input: High
Output: priority:high

uppercase

Convert to uppercase:

template: "SEVERITY:{{fields.severity.value | uppercase}}"

Input: critical
Output: SEVERITY:CRITICAL

slugify

Convert to URL-safe slug:

template: "theme:{{fields.customfield_10001[0].value | slugify}}"

Input: Expand Horizons
Output: theme:expand-horizons

Slugify rules:

  • Converts to lowercase
  • Replaces spaces with hyphens
  • Removes special characters
  • Safe for labels and URLs

idonly

Extract numeric ID from issue key:

template: "JPD-{{fields.id | idonly}}"

Input: MTT-123
Output: JPD-123

Input: 123 (if just the number)
Output: JPD-123

Advanced Field Mappings

Conditional Content

Skip empty fields using conditional logic:

mappings:
- jpd: "fields.customfield_10001"
github: "body"
template: |
{{#if fields.customfield_10001}}
**Priority:** {{fields.customfield_10001.value}}
{{/if}}

Multiple Fields in One Mapping

Combine multiple JPD fields:

mappings:
- jpd: "fields"
github: "body"
template: |
# {{fields.summary}}

**Priority:** {{fields.priority.value}}
**Impact:** {{fields.customfield_10002}}/10
**Theme:** {{fields.customfield_10003[0].value}}

## Description
{{fields.description}}

Array Fields (Multi-Select)

Handle multi-select fields:

mappings:
- jpd: "fields.customfield_10001" # Multi-select field
github: "labels"
template: "{{#each fields.customfield_10001}}theme:{{this.value | slugify}} {{/each}}"

Input: ["Mobile App", "Web Platform"]
Output: theme:mobile-app theme:web-platform

Custom Transform Functions

For complex transformations, use custom TypeScript functions:

Create Transform Function

transforms/derive-priority.ts:

export default function(data: Record<string, any>): string {
const impact = data.fields?.customfield_10001 || 0;
const reach = data.fields?.customfield_10002 || 0;

const score = impact * reach;

if (score > 80) return 'priority:critical';
if (score > 50) return 'priority:high';
if (score > 20) return 'priority:medium';
return 'priority:low';
}

Reference in Config

mappings:
- jpd: "fields"
github: "labels"
transform_function: "./transforms/derive-priority.ts"

See existing transforms in the transforms/ directory:

  • build-issue-body.ts - Rich issue body generation
  • combine-labels.ts - Merge multiple label sources
  • derive-priority.ts - Calculate priority from RICE scores

Complete Examples

Minimal Field Mapping

fields:
priority:
field_id: "customfield_10001"
field_type: "select"

mappings:
- jpd: "fields.summary"
github: "title"

- jpd: "fields.customfield_10001.value"
github: "labels"
template: "priority:{{fields.customfield_10001.value | lowercase}}"

Comprehensive Field Mapping

fields:
priority:
field_id: "customfield_10001"
field_type: "select"
required: false

theme:
field_id: "customfield_10002"
field_type: "array"
required: false

impact_score:
field_id: "customfield_10003"
field_type: "number"
required: false

mappings:
# Title with issue key
- jpd: "fields.summary"
github: "title"
template: "[{{fields.project.key}}-{{fields.id | idonly}}] {{fields.summary}}"

# Priority label
- jpd: "fields.customfield_10001.value"
github: "labels"
template: "priority:{{fields.customfield_10001.value | lowercase}}"

# Theme labels (multi-select)
- jpd: "fields.customfield_10002"
github: "labels"
template: "{{#each fields.customfield_10002}}theme:{{this.value | slugify}} {{/each}}"

# Rich issue body
- jpd: "fields"
github: "body"
template: |
**Impact Score:** {{fields.customfield_10003}}/10

**Theme:** {{fields.customfield_10002[0].value}}

## Description
{{fields.description}}

---
🔗 [View in JPD]({{jpd_url}})

Discovering Field IDs

Before creating mappings, discover available fields:

pnpm run discover-fields YOUR_PROJECT

Output:

Field Discovery Results:

ID | Type | Status | Sample Value
-----------------------|-------------|--------|----------------
customfield_10001 | select | ✓ Set | High
customfield_10002 | array | ✓ Set | ["Mobile App"]
customfield_10003 | number | ✓ Set | 8
customfield_10004 | text | ✓ Set | Additional context...

Use the IDs in your field definitions.

Field Types Reference

select

Single-select dropdown:

fields:
priority:
field_id: "customfield_10001"
field_type: "select"

# Access value
template: "{{fields.customfield_10001.value}}"

multiselect / array

Multi-select or checkbox fields:

fields:
themes:
field_id: "customfield_10002"
field_type: "array"

# Loop through values
template: "{{#each fields.customfield_10002}}{{this.value}} {{/each}}"

number

Numeric fields:

fields:
impact:
field_id: "customfield_10003"
field_type: "number"

# Direct access
template: "Impact: {{fields.customfield_10003}}/10"

text

Text fields:

fields:
notes:
field_id: "customfield_10004"
field_type: "text"

# Direct access
template: "{{fields.customfield_10004}}"

user

User/assignee fields:

fields:
assignee:
field_id: "assignee"
field_type: "user"

# Access user properties
template: "Assigned to: {{fields.assignee.displayName}}"

date

Date fields:

fields:
due_date:
field_id: "duedate"
field_type: "date"

# Access date value
template: "Due: {{fields.duedate}}"

Validation

The connector validates field mappings:

pnpm run validate-config

Checks:

  • Field IDs exist in JPD
  • Field types match actual types
  • Required fields are present
  • Template syntax is valid
  • Transform functions exist

Common validation errors:

"Field customfield_10001 not found"

  • Field ID is incorrect
  • You don't have permission to access the field
  • Field doesn't exist in this project

"Field type mismatch: expected select, got text"

  • field_type in config doesn't match actual JPD field type
  • Run discover-fields to check actual type

"Required field is empty"

  • Field marked as required: true has no value
  • Either make it optional or ensure field is populated

Troubleshooting

Field Not Mapping

Check:

  1. Field ID is correct (use discover-fields)
  2. Field path includes .value for select fields
  3. Template syntax is valid
  4. Field has a value in JPD

Debug:

DEBUG=1 pnpm run dev -- --dry-run

Labels Not Created

Check:

  1. Template produces valid label name
  2. Label name has no special characters (except -, _)
  3. Label name length < 50 characters

Template Not Rendering

Check:

  1. Variable names match JPD field structure
  2. Using correct syntax: {{variable}} not {variable}
  3. Filters are spelled correctly: | lowercase not | lower

Next Steps

Field Mapping Strategy

Start with essential fields (title, priority, status) then add more fields incrementally. Test each addition with dry-run before committing.