> ## Documentation Index
> Fetch the complete documentation index at: https://docs.voxworks.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Variable Types & Schema Validation

> Design your own custom variable types with a field-by-field schema builder, choose how strictly incoming data is validated, and install ready-made types from the catalogue.

## What are Custom Variable Types?

A custom variable type is the blueprint for a kind of record your business tracks — a property listing, an insurance policy, a survey question, an account preference set. The type defines the fields each record carries (name, data type, format, allowed values), and every custom variable you create from that type follows the same structure. On the [Custom Variables page](/custom-variables/overview), each type gets its own tab, and its fields become sortable table columns.

Types matter beyond tidy tables: every field of a typed custom variable becomes readable in your call scripts as `{{custom.<type_name>.<field_path>}}` (typed per your schema — a Text field such as `{{custom.property_listing.address}}` might read `"12 Wattle St, Brisbane QLD 4000"`), so a well-designed type is also a well-designed interface between your data and your voice agent.

You manage types from **Custom Variables → Custom Variable Types**. The dialog opens with two tabs — **Installed** (your team's types) and **Catalogue** (ready-made industry templates) — plus a **Create Type** button.

***

## Creating a type

Click **Create Type** to open the type editor. Give the type a name in the **Enter type name...** field and optionally **Add a description...** below it.

| Setting     | Description                                                                                                                                 | Default      |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
| Name        | The display name shown on tabs, forms and the type list, e.g. "Property Listing".                                                           | — (required) |
| Description | Optional help text shown in the type list.                                                                                                  | Empty        |
| Validation  | How strictly saved data must match the schema: **Off**, **Warn** or **Strict** (see [Validation modes](#validation-modes-off-warn-strict)). | Off          |
| Fields      | The field schema — see below.                                                                                                               | No fields    |

As you type the name, an internal type identifier is generated from it automatically (lowercase, words joined with underscores — "Property Listing" becomes `property_listing`). That identifier is what scripts use in `{{custom.property_listing.address}}` references, and it is fixed once the type is created — renaming the display name later never changes it, so existing variable references keep working.

Editing an installed type reopens the same editor; you can change the display name, description, validation mode and fields at any time. Deleting a type asks for confirmation and cannot be undone.

***

## Building the field schema

The **Fields** section lists one row per field, with badges counting the fields you've defined and how many are required. Add rows with **Add Field** (or **Add First Field** on an empty type), and remove them with the bin icon.

Each row has three inline inputs — a **Display Label**, a path (shown as `$.path.to.field`) and a type selector — plus a **More** button that expands the remaining options.

| Option                           | Applies to | Description                                                                                                                                                                                                                         |
| -------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Display Label                    | All fields | The human-readable name shown in tables and entry forms. Until you edit the key or path manually, both are derived from the label automatically.                                                                                    |
| Path                             | All fields | Where the value lives inside the record's data, written JSONPath-style — `$.address` for a top-level field, `$.address.city` for a nested one. The path (minus the leading `$.`) is also the segment scripts use to read the field. |
| Type                             | All fields | **Text**, **Number**, **Yes/No**, **List** or **Object**.                                                                                                                                                                           |
| Field Key (internal)             | All fields | The snake\_case identifier the value is stored under. Auto-derived from the label; edit it to pin your own. On an existing type, renaming a field's label never silently changes its key — stored data keeps its address.           |
| Required (for strict validation) | All fields | Marks the field as mandatory. Only enforced when the type's validation mode is Strict; an empty text value counts as missing.                                                                                                       |
| Format                           | Text       | **Plain text**, **Email**, **URL**, **Date & Time**, **Date** or **Currency**. Shapes the entry input and adds format checks.                                                                                                       |
| Allowed values (optional)        | Text       | A comma-separated list, e.g. `hot, warm, cold`. The entry form becomes a dropdown limited to these values.                                                                                                                          |
| Minimum / Maximum                | Number     | Numeric bounds for the value.                                                                                                                                                                                                       |
| Description (optional)           | All fields | Help text shown beside the field in entry forms.                                                                                                                                                                                    |

**Object** fields hold nested structures: expand the row and click **Add property** to define child fields, each with the same options.

### Two other ways to define fields

* **Edit JSON** — switches the field list to a raw JSON view of the schema for power editing; **Field view** switches back (invalid JSON must be fixed first).
* **Import JSON** — paste a sample data record (for example, one item of an API response you plan to send in) and click **Generate Fields**. Field keys, types and nested objects are inferred automatically, and common formats like email, URL, date and currency are guessed from the key names. Review the generated fields before saving.

### How field settings shape data entry

When someone clicks **Add Custom Variable** and picks your type, the **New Custom Variable** form is built from the schema: required fields are marked with `*`, Text fields with allowed values render as dropdowns, Email and URL formats get matching inputs, Number fields carry your minimum/maximum, Yes/No fields are checkboxes, and List/Object fields accept JSON. Field descriptions appear as inline help text.

***

## Validation modes (Off / Warn / Strict)

Each type has a single **Validation** setting that governs saves of data against it — whether entered in the app, bulk-loaded through a [CSV import](/custom-variables/csv-import) or submitted through the API.

| Mode   | UI description                               | What actually happens                                                                |
| ------ | -------------------------------------------- | ------------------------------------------------------------------------------------ |
| Off    | Accept any data without validation           | No checks run. Any shape of data saves.                                              |
| Warn   | Accept data but show warnings for mismatches | Checks run and mismatches are recorded as warnings, but the save always succeeds.    |
| Strict | Block saves that do not conform to schema    | The save is rejected with a "Validation failed" message listing every problem found. |

The checks cover: required fields present (an empty text value counts as missing), values matching their declared type, text length within any minimum/maximum character limits (length limits aren't in the field builder — set them through the **Edit JSON** view), Email and URL formats, values within the allowed-values list, and numbers within their minimum/maximum. Fields left empty that aren't required always pass — validation never demands data you didn't provide.

Data arriving through the API is held to the core of the same contract under Strict: required fields and matching data types are enforced before anything is stored, and a failed check rejects the request with the reason.

**Choosing a mode:** start with Off while you're still shaping the schema, move to Warn once real data flows in (nothing breaks, but drift becomes visible), and switch to Strict when downstream scripts and automations depend on the fields being reliably present and correctly typed.

***

## One record per contact

By default, every custom variable is an independent record — one property listing can link to several contacts, and one contact can hold many listings. Some types work better the other way around: an "account preferences" type should exist exactly once per contact.

For that, a type can be configured as unique-by-contact. Instead of creating separate records, data saved against the type is stored directly on the contact, and any new data for the same contact merges into the one record. This option isn't in the type editor — it's set when the type is created through the API, and the full storage behaviour is covered in [Linking Custom Variables to Contacts](/custom-variables/linking-contacts). Speak to the Voxworks team for further custom configuration.

***

## The type catalogue

The **Catalogue** tab of the Custom Variable Types dialog lists ready-made types you can adopt without building a schema yourself. Each card shows the type's name, version, field count, category and industry tags, plus any supported integrations. Click **Add to Team** to install a copy; installed entries show a tick and read **Added**. Every catalogue entry is browsable and installable by any team, regardless of your own account's industry.

Installed catalogue types appear on the **Installed** tab with a **Catalogue** badge and behave exactly like types you built yourself (own tab, columns, script variables); each arrives with every field at its designed starting value, ready to fill in. Types carrying a **Default** badge are Voxworks' recommended starting set for a matching industry — install them from the Catalogue tab the same way as any other entry.

The catalogue currently spans two industries: six survey question types for phone research, and four real estate types built around external CRM integrations.

For CATI-style phone research, consumed by the Survey tools:

| Type                       | What it does                                                                                                                                                                     |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Survey — RO — SC           | Prompted single-choice question: reads the question and up to 10 options aloud, captures one selection. Options support rotate, fixed, silent, terminate and disabled behaviour. |
| Survey — RO — MC           | Prompted multiple-choice question: reads options aloud, captures one or more selections, with exclusive and specify option behaviour and optional confirmation.                  |
| Survey — RO — Battery — SC | Prompted rating battery: reads a preamble and scale once, then collects one rating per item — approval ratings, satisfaction grids.                                              |
| Survey — NRO — SC          | Unprompted single-choice question: asks without reading options, matching the caller's free-text answer against a hidden code frame.                                             |
| Survey — NRO — MC          | Unprompted multiple-choice question: matches one or more answers against a hidden code frame, with optional confirmation.                                                        |
| Survey — NRO — O           | Unprompted open-ended question: captures the caller's free-form answer, with optional internal validation.                                                                       |

Survey types carry both the question setup you author (question text, options, rotation) and result fields the survey tools write back during calls — for example `{{custom.survey_ro_sc.result}}` (Text, e.g. `"The Greens"`). Leave result fields empty when authoring instances.

For real estate teams, carrying integration tags for Rex, Vault RE, Agentbox and Domain:

| Type                        | What it does                                                                                          |
| --------------------------- | ----------------------------------------------------------------------------------------------------- |
| Real Estate Property        | Canonical property and listing record shared across the connected real estate integrations.           |
| Real Estate Contact Profile | Real estate metadata layered onto a contact record, linked the same way as any other custom variable. |
| Property Enquiry / Lead     | An enquiry or lead record, linked to a contact and optionally to a property record.                   |
| Real Estate Agent           | Canonical agent/user record shared across the connected real estate integrations.                     |

***

## Using typed fields in scripts

Every installed type exposes its fields to call scripts, tool inputs and Code Steps under the `custom` namespace as `{{custom.<type_name>.<field_path>}}` — the type identifier plus the field's path with the leading `$.` dropped:

* `{{custom.property_listing.address}}` — Text, e.g. `"12 Wattle St, Brisbane QLD 4000"`
* `{{custom.property_listing.price.asking}}` — Number (a nested field under an Object), e.g. `850000`
* `{{custom.survey_ro_sc.question}}` — Text, e.g. `"Which party will you vote for?"`

Type `/` in any script field that supports variables to open the picker — installed custom variable types are listed first, and choosing a field inserts the reference for you.

At the start of each call, the agent loads every installed type at its default values, then overlays the data of the custom variables linked to the contact on the call — so which record a script reads is decided by [contact linking](/custom-variables/linking-contacts). Writes from Code Steps persist back to the record immediately.

***

## Next Steps

* [Custom Variables Overview](/custom-variables/overview) — the workspace tour: type tabs, views, add forms and the detail pane.
* [Linking Custom Variables to Contacts](/custom-variables/linking-contacts) — how records attach to contacts and reach your scripts at call time.
* [Importing Custom Variables from CSV](/custom-variables/csv-import) — bulk-load typed records with the `custom.{type}.{field}` header convention.
* [Variables](/flows/variables) — the full variable reference, including the `custom` namespace rules.
