How to Configure Field Mappings¶
TL;DR¶
Map source data fields to OAA destination fields, including type conversion, field renaming, and data transformation.
Prerequisites¶
An OAA integration with config.yaml and sources
Understanding of OAA field types (LocalUser, LocalRole, Resource, etc.)
Source data available (REST API, database, CSV, etc.)
Knowledge of your source’s field structure
Steps¶
Step 1: Understand Available OAA Fields¶
For LocalUser, common fields include:
unique_id(required) - Unique user identifieremail- User email addressname- Display namefirst_name- First name (custom)last_name- Last name (custom)phone_number- Phone number (custom)account_status- Active/inactive (custom)
For LocalRole, common fields include:
unique_id(required) - Unique role identifiername- Role display namedescription- Role description (custom)
For Resource, common fields include:
unique_id(required) - Unique resource identifiername- Resource nameresource_type- Type of resource (required)
Step 2: Create Basic Field Mappings¶
In your config.yaml, map source fields to OAA fields:
sources:
users:
destination: LocalUser
source_type: custom
connection: my_api
params:
resource: /users
field_mapping:
# Map source field to OAA field
user_id:
destination_field: unique_id
email_address:
destination_field: email
full_name:
destination_field: name
first_name:
destination_field: first_name
custom_type: STRING
last_name:
destination_field: last_name
custom_type: STRING
phone:
destination_field: phone_number
custom_type: STRING
status:
destination_field: account_status
custom_type: STRING
Step 2b: Use Shorthand Syntax for Simple Mappings¶
For simple 1:1 field mappings where the source field maps directly to the destination field, you can use shorthand syntax instead of the full nested structure.
String shorthand — when the source field key maps directly to the destination field:
field_mapping:
# These are equivalent:
email: email # shorthand
email: # full syntax
destination_field: email
Dict shorthand — when you need additional options but the destination field matches the key:
field_mapping:
# These are equivalent:
department: # shorthand dict
source_field: dept
custom_type: STRING
department: # full syntax
destination_field: department
source_field: dept
custom_type: STRING
You can even use an empty dict {} when the source field name, destination field name, and no additional options are needed:
field_mapping:
email: {} # destination_field inferred as "email"
Mixed syntax example:
sources:
users:
destination: LocalUser
source_type: csv
csv_filepath: data/users.csv
field_mapping:
# String shorthand — simple 1:1 mapping
id: unique_id
email: email
name: name
# Dict shorthand — needs custom_type but destination matches key
department:
source_field: dept
custom_type: STRING
# Full syntax — needs transforms
hire_date:
destination_field: hire_date
custom_type: TIMESTAMP
transforms:
- name: date_convert
Both shorthand forms are fully backward compatible with the existing full nested syntax. Use whichever is clearest for each field.
Step 3: Use Custom Veza Fields¶
Fields in your source data that don’t map to standard OAA properties can be added as custom fields using the custom_type parameter. This tells Veza what data type to expect for the custom property.
Available custom_type values:
Type |
Description |
Example |
|---|---|---|
|
Text values |
|
|
True/False values |
|
|
Numeric values |
|
|
Array of strings |
|
|
ISO 8601 date/time |
|
|
Windows AD timestamp |
|
For complete details on custom properties and supported timestamp formats, see What are Veza Custom Properties?.
Example with multiple custom types:
sources:
users:
destination: LocalUser
source_type: custom
connection: my_api
params:
resource: /users
field_mapping:
user_id:
destination_field: unique_id
is_active:
destination_field: is_active
custom_type: BOOLEAN # Boolean custom field
department:
destination_field: department
custom_type: STRING # String custom field
hire_date:
destination_field: hire_date
custom_type: TIMESTAMP # Timestamp custom field
login_count:
destination_field: login_count
custom_type: NUMBER # Numeric custom field
certifications:
destination_field: certifications
custom_type: STRING_LIST # List of strings
Step 3b: Working with Timestamps¶
When mapping timestamp fields, you can use the date_convert transform to automatically convert various date formats to ISO 8601 format.
Supported input formats:
Input Format |
Example |
|---|---|
MM-DD-YYYY |
|
MM/DD/YYYY |
|
YYYY-MM-DD |
|
YYYY-MM-DDTHH:MM |
|
ISO 8601 |
|
All formats are converted to ISO 8601: YYYY-MM-DDTHH:MM:SSZ
Example with timestamp conversion:
sources:
users:
destination: LocalUser
field_mapping:
hire_date:
destination_field: hire_date
custom_type: TIMESTAMP
transforms:
- name: date_convert # Handles multiple formats automatically
last_login:
destination_field: last_login
custom_type: TIMESTAMP
transforms:
- name: date_convert
# Active Directory timestamp (no conversion needed)
ad_pwd_last_set:
destination_field: ad_pwd_last_set
custom_type: TIMESTAMP_AD # Use as-is, no transform
For complete timestamp format details and examples, see What are Veza Custom Properties?.
Step 4: Use Field Transforms¶
Add transformations for complex mappings:
sources:
users:
destination: LocalUser
source_type: custom
connection: my_api
params:
resource: /users
field_mapping:
# Combine first and last name
full_name:
destination_field: name
transforms:
- name: jinja_render
options:
template: "{{row.first_name}} {{row.last_name}}"
# Extract domain from email
email_domain:
destination_field: email_domain
source_field: email
custom_type: STRING
transforms:
- name: jinja_render
options:
template: "{{row.email.split('@')[1]}}"
# Convert timestamp to date
created_timestamp:
destination_field: created_date
transforms:
- name: date_convert
Step 5: Rename Fields¶
If source field name differs from what you want to use:
field_mapping:
# Source field name: user_id_code
# Map to: unique_id
user_id_code:
destination_field: unique_id
# Source field name: contact_email
# Map to: email
contact_email:
destination_field: email
# Source field name: person_name
# Map to: name
person_name:
destination_field: name
Step 6: Add Custom Fields¶
Add fields that aren’t standard OAA fields:
field_mapping:
department:
destination_field: department
custom_type: STRING
job_title:
destination_field: job_title
custom_type: STRING
employee_id:
destination_field: employee_id
custom_type: STRING
location:
destination_field: location
custom_type: STRING
cost_center:
destination_field: cost_center
custom_type: STRING
Step 7: Map a Field to a User Identity¶
Add map_to_identity: true to any field on a LocalUser source to attach that value
as an identity. Veza uses identities to correlate the same person across integrated
systems.
sources:
users:
destination: LocalUser
field_mapping:
user_id:
destination_field: unique_id
email:
destination_field: email
map_to_identity: true # add email as a correlating identity
upn:
destination_field: upn
custom_type: STRING
map_to_identity: true # multiple identities are allowed
Only applies to
LocalUserdestinations — silently skipped for other typesEmpty and
Nonevalues are skipped automaticallySetting
map_to_identity: truedoes not prevent the value from also being mapped todestination_field— both happen
For conditional or computed identity logic, use a record-transform hook instead. See How to Map User Identities for full details.
Step 8: Test Mappings¶
Validate and run to test:
# Validate configuration
uv tool run --from oaa-runner oaa app validate --config=config.yaml
# Run integration
uv tool run --from oaa-runner oaa app run --config=config.yaml --save-json
# Check output to verify fields are mapped correctly
jq '.local_users[0]' output.json
Field Mapping Patterns¶
Modern Pattern (Recommended)¶
field_mapping:
source_field_name:
destination_field: target_field_name
custom_type: STRING
transforms:
- name: jinja_render
options:
template: "{{row.source_field}}"
Legacy Pattern (Still Supported)¶
field_mapping:
source_field_name:
destination_field: target_field_name
is_custom: True
veza_field_type: STRING
transforms:
- name: jinja_render
options:
template: "{{row.source_field}}"
Both patterns work identically.
Examples¶
Real Integration: Abnormal Security¶
From gxo/abnormal/config.yaml:
sources:
users:
destination: LocalUser
source_type: custom
connection: abnormal
params:
resource: /v1/users
field_mapping:
user_id:
destination_field: unique_id
email-to-name:
destination_field: name
source_field: email
email:
destination_field: email
custom_type: STRING
local_login_enabled:
destination_field: local_login_enabled
custom_type: BOOLEAN
sso_enabled:
destination_field: sso_enabled
custom_type: BOOLEAN
Real Integration: ThousandEyes¶
From netapp/thousandeyes/config.yaml:
sources:
users:
destination: LocalUser
source_type: custom
connection: api
params:
resource: users
field_mapping:
email:
destination_field: email
name:
destination_field: name
uid:
destination_field: unique_id
dateRegistered:
destination_field: created_at
lastLogin:
destination_field: last_login_at
Real Integration: IHG Centrify¶
From ihg/centrify/config.yaml:
sources:
users:
destination: LocalUser
source_type: custom
connection: centrify_db
params:
query: |
SELECT * FROM users
field_mapping:
ADUser_Name:
destination_field: unique_id
ADUser_ObjectName:
destination_field: name
ADUser_FullName:
destination_field: display_name
ADUser_CanonicalName:
destination_field: ADUser_CanonicalName
is_custom: True
veza_field_type: STRING
ADUser_SamAccountName:
destination_field: ADUser_SamAccountName
is_custom: True
veza_field_type: STRING
Available Custom Types¶
When adding custom fields, use these type values:
STRING- Text dataBOOLEAN- True/False valuesINTEGER- Whole numbersFLOAT- Decimal numbersDATETIME- Date and timeSTRING_LIST- Array of stringsCUSTOM_OBJECT- Complex nested objects
Available Transforms¶
Common YAML transforms:
Transform |
Purpose |
Example |
|---|---|---|
|
Template rendering |
|
|
Convert to date |
Auto-converts timestamps |
|
Convert to string |
Any value → string |
|
Validate boolean |
“true”/”false” → boolean |
Troubleshooting¶
Error: destination_field not found
Check that destination_field is set for every mapping
Verify field name matches OAA documentation
For custom fields, ensure custom_type is specified
Error: source_field not found in data
Verify source provides this field
Check field name spelling
Use
source_field:to rename if needed
Field appears empty in output
Verify source has data in this field
Check transforms don’t return empty values
Add logging to PRE_TRANSFORM hook to debug
Wrong type in output
Check custom_type matches data
Add type conversion transform if needed
Verify source data type
Debugging Mappings¶
Add logging to understand mappings:
# In modules/hooks.py
@hook(event=OAAHookEvent.PRE_TRANSFORM, target='users')
def debug_fields(event, source=None, **kwargs):
"""Log first record's fields for debugging"""
first_user = next(source.stream)
logger.info(f"Available fields: {first_user.fields}")
for field, value in first_user.items():
logger.debug(f" {field}: {value} (type: {type(value).__name__})")
Next Steps¶
How to Use PRE_TRANSFORM Hooks - Transform before mapping
How to Use RECORD_TRANSFORM Hooks - Transform after mapping
How to Create User-to-Role Relationships - Link entities