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 identifier

  • email - User email address

  • name - Display name

  • first_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 identifier

  • name - Role display name

  • description - Role description (custom)

For Resource, common fields include:

  • unique_id (required) - Unique resource identifier

  • name - Resource name

  • resource_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

STRING

Text values

"Engineering", "Active"

BOOLEAN

True/False values

true, false

NUMBER

Numeric values

42, 3.14

STRING_LIST

Array of strings

["admin", "user"]

TIMESTAMP

ISO 8601 date/time

"2024-01-15T10:30:00Z"

TIMESTAMP_AD

Windows AD timestamp

133456789012345678

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

01-15-2024

MM/DD/YYYY

01/15/2024

YYYY-MM-DD

2024-01-15

YYYY-MM-DDTHH:MM

2024-01-15T10:30

ISO 8601

2024-01-15T10:30:00Z

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 LocalUser destinations — silently skipped for other types

  • Empty and None values are skipped automatically

  • Setting map_to_identity: true does not prevent the value from also being mapped to destination_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

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 data

  • BOOLEAN - True/False values

  • INTEGER - Whole numbers

  • FLOAT - Decimal numbers

  • DATETIME - Date and time

  • STRING_LIST - Array of strings

  • CUSTOM_OBJECT - Complex nested objects

Available Transforms

Common YAML transforms:

Transform

Purpose

Example

jinja_render

Template rendering

"{{row.field.upper()}}"

date_convert

Convert to date

Auto-converts timestamps

to_string

Convert to string

Any value → string

validate_boolean

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