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:
- Field Definitions - Declare which JPD fields exist and their types
- 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-123or just123)fields.summary- Issue titlefields.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 generationcombine-labels.ts- Merge multiple label sourcesderive-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-fieldsto check actual type
"Required field is empty"
- Field marked as
required: truehas no value - Either make it optional or ensure field is populated
Troubleshooting
Field Not Mapping
Check:
- Field ID is correct (use
discover-fields) - Field path includes
.valuefor select fields - Template syntax is valid
- Field has a value in JPD
Debug:
DEBUG=1 pnpm run dev -- --dry-run
Labels Not Created
Check:
- Template produces valid label name
- Label name has no special characters (except
-,_) - Label name length < 50 characters
Template Not Rendering
Check:
- Variable names match JPD field structure
- Using correct syntax:
{{variable}}not{variable} - Filters are spelled correctly:
| lowercasenot| lower
Next Steps
- Configure Status Workflows - Map statuses between systems
- Define Labels - Create label color scheme
- Set Up Hierarchy - Enable Epic/Story/Task relationships
- Advanced Configuration - Rate limiting, GitHub Projects
Start with essential fields (title, priority, status) then add more fields incrementally. Test each addition with dry-run before committing.