Skip to main content

DTO Mapping & Extras

When the user confirms their booking, the form engine builds a FormDto from the session's answers. This page explains how form field answers are routed to the DTO and what happens with extra contextual information.

How DTO Building Works

For each answered field, the engine determines where to put the value:

Answer → look up fieldDefinition → get dtoField (or use field name)
├── dtoField is in defaults.dtoFields list → FormDto.fields (main DTO)
└── dtoField is NOT in the list → FormDto.extras (keyed by field name)

After all answers are routed, conversationExtras (contextual info captured during chat) are merged into FormDto.extras.

dtoField Auto-Derive

When a field definition omits dtoField, the field name is used automatically:

# These are equivalent:
email:
type: email
dtoField: email # explicit

email:
type: email
# dtoField auto-derives to "email"
tip

You only need explicit dtoField when the form field name differs from the desired DTO key. Most fields can omit it.

Explicit dtoField Mapping

Use explicit dtoField when the form field name should map to a different DTO key:

# Form field "preferredDate" maps to DTO key "date"
preferredDate:
type: date
dtoField: date
promptKey: fields.date.ask
labelKey: fields.date.label
descriptionKey: fields.date.description

Common use case: multiple form fields across branches that represent the same concept:

# All three map to the same "timeslot" DTO field
preferredTimeCampus:
type: smartSelect
dtoField: timeslot

preferredTimeMoveout:
type: select
dtoField: timeslot

preferredTimeFlexible:
type: time
dtoField: timeslot

This is valid because these fields are in different branches — only one is active at a time.

Multiple Fields, Same dtoField

Fields in different branches can legitimately share a dtoField:

# OK: preferredDate exists in both branches, but only one branch is active
formFlow:
- serviceType:
homeCleaning:
- preferredDate: {} # dtoField: date
windowCleaning:
- preferredDate: {} # dtoField: date (same — fine, different branch)

However, fields on the same path must NOT share a dtoField (for main DTO fields). The system validates this at config load time:

# ERROR: fieldA and fieldB are sequential (same path), both map to "sharedName"
formFlow:
- fieldA: {}
- fieldB: {}

fieldDefinitions:
fieldA:
type: text
dtoField: sharedName
fieldB:
type: text
dtoField: sharedName

Error at startup:

Fields 'fieldA' and 'fieldB' both map to dtoField 'sharedName' on the same form path

Extras

Fields whose effective dtoField is NOT in the defaults.dtoFields list are routed to FormDto.extras. They are keyed by field name (not dtoField value) to prevent overwrites.

defaults:
dtoFields: [serviceType, date, name, email] # main DTO fields
persistOthersTo: extras

fieldDefinitions:
windowCount: # not in dtoFields → extras
type: number
windowType: # not in dtoFields → extras
type: select

Result in the DTO:

{
"fields": {
"serviceType": "windowCleaning",
"date": "2026-04-05",
"name": "John",
"email": "john@example.com"
},
"extras": {
"windowCount": 12,
"windowType": "outsideOnly"
}
}

Conversation Extras

During the chat, the LLM can capture contextual information that the user mentions but isn't covered by any form field. These are stored separately via the addExtraInfo tool:

  • petInfo: "I have a friendly dog at home"
  • keysLocation: "Keys are with the neighbor in unit 4B"
  • customerAvailabilityNote: "Won't be home before 10am"

These are merged into FormDto.extras alongside answer-based extras:

{
"fields": { "name": "John", "email": "john@example.com" },
"extras": {
"windowCount": 12,
"petInfo": "I have a friendly dog at home",
"keysLocation": "Keys are with the neighbor in unit 4B"
}
}

Complete DTO Example

For a window cleaning booking:

{
"sessionId": "a1b2c3d4-e5f6-...",
"customerId": "SE00123",
"username": "user@example.com",
"configId": "customers/SE00123",
"configVersion": "abc123",
"submittedAt": "2026-03-23T19:05:33",
"fields": {
"serviceType": "windowCleaning",
"date": "2026-04-05",
"timeslot": "09:00",
"name": "Prasannjeet Singh",
"email": "prasannjeet@duck.com",
"phone": "0700617660",
"personalNumber": "199110107673",
"address": "Lyan 62, LGH 1008",
"postNumber": "35252",
"city": "Vaxjo"
},
"extras": {
"windowType": "outsideOnly",
"windowCount": 12,
"petInfo": "I have a dog at home."
}
}