User Defined Fields (UDF)
You can create your own fields under the Clients, Users, Project, and Suppliers modules. In the Projects module, you can find the extra fields in the Overview tab, beneath the map.

User defined fields (UDF) can be only created in the Admin site. Normally, your Kompass Champion should have access on the Admin site.
To create your fields, you will initially have to create a Project UDF schema. Head to "Client/ User/ Project/ Supplier UDF Schema" (1) and click "Add UDF schema" (2).

Add a name and then you'll have to set the schema.

Please note that once you have create your UDF schema, you will have to allocate it to your org. Head to your org page and select the schema at the relevant module.
Understanding the Schema Definition
You can see the basic structure of a UDF schema below:
{
"type": "object",
"properties": {
"field_name": {
"type": [
"string",
"null"
],
"label": "Field Label",
"default": null,
"x-order": 1
}
}
}
Field Properties
As seen above, you can use certain properties under the field_name.
| Property | Description | Example |
|---|---|---|
type |
Field data type (see Field Types) | "string" , ["string", "null"] |
label |
Display label shown in the UI | "Company Name" |
default |
Default value when field is empty | null , "" , 0 , false |
enum |
List of allowed values (creates dropdown) | ["Active", "Inactive", "Pending"] |
There are additional custom extensions available from your fields (x- prefixed)
| Property | Description | Applies To | Example |
|---|---|---|---|
x-order |
Display order (lowest first) | All fields | 1 , 2 , 3 |
x-widget |
Override default widget (see Widgets) | Entity reference fields | "date" , "client" |
x-depends-on |
Parent field (clears when parent changes) | Required: contact , quote_item , task |
"client_id" , "project_id" |
x-module |
Module context for tags | Required: tags widget |
"clients" , "projects" |
x-multi-line |
Enable textarea mode | String fields (required if x-rows is set) |
true , false |
x-rows |
Number of textarea rows | String fields (requires x-multi-line: true ) |
3 , 5 , 10 |
x-template |
Jinja2 template for a calculated value | Required: calculated widget |
"{{ quote_item.price_total }}" |
x-realtime |
Recompute live while editing (boolean, default false) | Optional: calculated Widget ) |
3 , 5 , 10 |
x-decimal-places |
Fixed decimal precision (0–10) | Number fields (incl. calculated ) | 3 , 5 , 10 |
x-cols |
Grid column span, 1–12 (default 4) | String fields (requires x-multi-line: true ) |
3 , 5 , 10 |
Field Types
1. Text Field
This is a standard string field used for capturing text, such as notes or simple descriptions. You can allow it to be left empty by setting the type to ["string", "null"] .
- Example Use: A "Plot" description.
-
Code Definition:
JSON
"plot": {
"type": "string",
"label": "Plot",
"x-order": 1,
"default": null
}
-
Code Definition (with null allowed):
JSON
"plot": {
"type": [
"string",
"null"
],
"label": "Plot",
"x-order": 1,
"default": null
}
2. Checkbox (Yes/No)
This is a boolean field that appears as a simple checkbox. It's ideal for tracking true/false or yes/no information.
- Example Use: A "RAMS Sent" checkbox.
-
Code Definition:
JSON
"rams": {
"type": "boolean",
"label": "RAMS Sent",
"default": false
}
3. Dropdown List
This field allows users to select a single option from a predefined list. Use the enum property to list the available options. Including null in the enum list makes the field optional.
- Example Use: A "State Area" field.
-
Code Definition:
JSON
"section": {
"enum": [
"Central",
"East",
"North",
"South",
"West",
null
],
"type": [
"string",
"null"
],
"label": "State Area",
"x-order": 2,
"default": null
}
4. Date Field
This field provides a calendar picker. By setting the format to date , you ensure the input is correctly handled as a date.
- Example Use: An "Archive Date".
-
Code Definition:
JSON
"archive_date": {
"type": [
"string",
"null"
],
"format": "date",
"label": "Archive Date",
"x-order": 3,
"x-widget": "date",
"default": null
}
5. Number Field
This field is used for entering numerical data only. You can set a default value, such as 0 .
- Example Use: An "Acres" field.
-
Code Definition:
JSON
"acres": {
"type": [
"number",
"null"
],
"label": "Acres",
"x-order": 4,
"default": 0
}
Making Fields Required/Optional
Required Fields
Fields are required by default. To make a field mandatory:
{
"type": "object",
"properties": {
"company_name": {
"type": "string",
"label": "Company Name"
}
}
}
Optional Fields (Nullable)
To allow null values, use an array type with "null" :
{
"type": [
"string",
"null"
],
"label": "Optional Field",
"default": null
}
Examples:
// Optional string
{
"type": [
"string",
"null"
],
"label": "Middle Name",
"default": null
}
// Optional integer
{
"type": [
"integer",
"null"
],
"label": "Employee Number",
"default": null
}
// Optional number
{
"type": [
"number",
"null"
],
"label": "Discount Rate",
"default": null
}
UI Behaviour:
- Fields with
"null"in type array will show a clear button (×) when in edit mode - Clearing the field sets the value to
null - Empty strings in nullable fields are automatically converted to
null
Widgets
Widgets determine how fields are rendered in the UI. Most widgets are automatically selected based on the field type. The x-widget property is only used to specify special selector widgets.
Automatic Widget Selection
The widget is automatically determined by the field type:
| Type | Condition | Rendered As | UI Component |
|---|---|---|---|
string |
Has enum |
Dropdown | KSelect |
string |
No enum |
Text field | KTextField |
integer |
- | Number field | KTextField |
number |
- | Number field | KTextField |
boolean |
- | Checkbox | KCheckbox |
String Field Options:
x-multi-line: Enable textarea mode (true/false). Iftrueandx-rowsis not specified, defaults to 1 row.x-rows: Number of rows for textarea (integer, requiresx-multi-line: true)
Examples:
// Simple text field
{
"type": "string",
"label": "Name"
}
// Textarea field
{
"type": "string",
"label": "Description",
"x-multi-line": true,
"x-rows": 5
}
// Dropdown (enum)
{
"type": "string",
"label": "Status",
"enum": [
"Active",
"Inactive"
]
}
// Number field
{
"type": "integer",
"label": "Count"
}
// Checkbox
{
"type": "boolean",
"label": "Enabled"
}
Special Selector Widgets (via x-widget)
Use x-widget to specify special selector components for entity references. These must be explicitly set and require integer or array types.
Date Widget
{
"type": [
"string",
"null"
],
"label": "Due Date",
"x-widget": "date",
"default": null
}
Divider Widget
Renders a horizontal divider line that spans the full width. Useful for visually grouping fields.
{
"type": "null",
"x-widget": "divider",
"default": null
}
With text label:
{
"type": "null",
"x-widget": "divider",
"label": "Personal Information",
"default": null
}
Note: Divider widgets don't store data. Use the label property to display text in the middle of the divider line.
Client Widget
{
"type": [
"integer",
"null"
],
"label": "Client",
"x-widget": "client",
"default": null
}
Contact Widget
Requires: x-depends-on (field containing client ID)
{
"type": [
"integer",
"null"
],
"label": "Contact",
"x-widget": "contact",
"x-depends-on": "client_id",
"default": null
}
Filters contacts by the selected client.
Project Widget
{
"type": [
"integer",
"null"
],
"label": "Project",
"x-widget": "project",
"default": null
}
Quote Item Widget
Requires: x-depends-on (field containing project ID)
{
"type": [
"integer",
"null"
],
"label": "Quote Item",
"x-widget": "quote_item",
"x-depends-on": "project_id",
"default": null
}
Filters quote items by the selected project.
Task Widget
Requires: x-depends-on (field containing project ID or quote item ID)
{
"type": [
"integer",
"null"
],
"label": "Task",
"x-widget": "task",
"x-depends-on": "project_id",
"default": null
}
Filters tasks by the selected project or quote item. The x-depends-on value can be either "project_id" or "quote_item_id" .
Supplier Widget
{
"type": [
"integer",
"null"
],
"label": "Supplier",
"x-widget": "supplier",
"default": null
}
User Widget
{
"type": [
"integer",
"null"
],
"label": "Assigned To",
"x-widget": "user",
"default": null
}
Tags Widget
{
"type": [
"array",
"null"
],
"label": "Tags",
"x-widget": "tags",
"x-module": "clients",
"default": null
}
Options:
x-module: Module context for tags (e.g.,"clients","projects","tasks")
Calculated Fields
A calculated field displays a read-only value that Kompass works out for you from a formula, rather than something a user types in. Use one for figures derived from the record's own data — an effective multiplier, a net revenue total, a variance against target — that should always stay in step with the underlying numbers.
Calculated fields are available on Projects and Proposal Items, where the record carries the financial data a formula can draw on.
How they work
A calculated field uses the calculated widget and carries a Jinja2 template in the x-template property. When the record is saved, Kompass renders that template against the record's data and stores the result. The user sees the value but can't edit it — anything a user (or an import) tries to submit for a calculated field is ignored.
{
"effective_multiplier": {
"type": [
"number",
"null"
],
"label": "Effective Multiplier",
"x-widget": "calculated",
"x-template": "{{ (quote_item.price_total - quote_item.costs_budget_non_staff) / quote_item.costs_budget_staff }}",
"x-decimal-places": 2,
"x-realtime": true,
"default": null
}
}
Rules for a calculated field
A calculated field has a few requirements, all enforced when you save the schema:
x-widgetmust becalculatedandx-templatemust be a non-empty, valid Jinja2 template.- The type must be
numberorstring, and it must include"null", with adefaultofnull. A calculation can fail — a division by zero, or referenced data that isn't there yet on a draft — and when it does, Kompass storesnullrather than an error. - A calculated field cannot be listed in
required. The value is computed by the server, never submitted, so a required calculated field would fail every save. - Number results are rounded to
x-decimal-places(if set); string results are used exactly as rendered.
Live preview: x-realtime
By default, a calculated field recomputes only when the record is saved.
Add "x-realtime": true to have the field recompute live as the user types, before saving — useful for a figure that depends only on fields on the edit form in front of them (price, quantity, and so on).
Leave x-realtime off for figures that depend on data outside the edit form — invoices, payments, or other proposal items on the project. Those can't be worked out from the unsaved form alone, so they refresh on save. (On a project specifically, the live preview can only reference the record's own UDF values, because a project's financial figures are rolled up from its proposal items, which aren't part of the project's own edit form.)
Whether realtime or not, every calculated field is recomputed and stored on save.
What you can reference in a template
The data available to a template depends on the record type.
On a Proposal Item, the template can reference a quote_item object, including:
price_total , costs_budget_total , costs_budget_staff , costs_budget_non_staff , gross_profit , gross_margin , value , invoiced_net , paid_net , and work_in_progress .
It also carries breakdowns of cost and price by staff/non-staff, by cost category, and by nominal account (for example price_by_category , costs_by_nominal_account ), and a costs list.
Note: On a Proposal Item, the parent
projectis available but is descriptive only — it carriesid,name,client,managed_byand similar, but deliberately not the project's financial totals (those would sum every proposal item on the project). For financial calculations on a proposal item, always use the item's ownquote_item.*values.
On a Project, the template can reference a project object that does carry the rolled-up figures (such as costs_budget_staff , costs_budget_non_staff , and price_total_by_staff ), plus a quote_items list.
Referencing other fields
- Another UDF field: use
udf.other_field— so one calculated field can build on another. Kompass automatically works out the right order to calculate them in. (A circular reference — two fields that depend on each other — can't be resolved and will compute tonull.) - The previously-saved values: when editing an existing record, the template can compare the current value against what was saved.
savedmirrors the record's own values as they were before this edit (guard it with{% if saved %}, as it'snullfor a brand-new record), andchangesis a list of the fields that differ from the saved version. For example, this gives the price change made in the current edit:
{{ quote_item.price_total - saved.quote_item.price_total }}
A note on the staff / non-staff split
Several of the figures above split by staff versus non-staff. Staff costs (costs_budget_staff ) are the budgeted cost of the cost lines flagged as Staff; non-staff costs are the remainder, and the two together make up costs_budget_total . The staff, by-category and by-nominal-account price splits are notional — a proposal item has a single price, which Kompass apportions across each dimension in proportion to that dimension's share of cost. (See the cost options article for the Staff flag.)
Limitations
- Calculated fields are read-only and are not searchable or filterable.
- A failed calculation (bad data, division by zero, or a formula bug) stores
null— the field simply shows blank until the data supports a result.
Field Dependencies
Fields can depend on other fields using x-depends-on . When the parent field changes, dependent fields are automatically cleared.
Example: Client → Contact
{
"type": "object",
"properties": {
"client_id": {
"type": [
"integer",
"null"
],
"label": "Client",
"x-widget": "client",
"x-order": 1,
"default": null
},
"contact_id": {
"type": [
"integer",
"null"
],
"label": "Contact",
"x-widget": "contact",
"x-depends-on": "client_id",
"x-order": 2,
"default": null
}
}
}
Behaviour:
- Contact selector is filtered by selected client
- When client changes, contact field is cleared to
null - Dependency chains are supported (A → B → C)
Example: Project → Quote Item → Task
{
"type": "object",
"properties": {
"project_id": {
"type": [
"integer",
"null"
],
"label": "Project",
"x-widget": "project",
"x-order": 1,
"default": null
},
"quote_item_id": {
"type": [
"integer",
"null"
],
"label": "Quote Item",
"x-widget": "quote_item",
"x-depends-on": "project_id",
"x-order": 2,
"default": null
},
"task_id": {
"type": [
"integer",
"null"
],
"label": "Task",
"x-widget": "task",
"x-depends-on": "quote_item_id",
"x-order": 3,
"default": null
}
}
}
Complete Example
{
"type": "object",
"properties": {
"status": {
"type": "string",
"label": "Status",
"enum": [
"New",
"In Progress",
"Completed",
"On Hold"
],
"default": "New",
"x-order": 1
},
"client_id": {
"type": [
"integer",
"null"
],
"label": "Related Client",
"x-widget": "client",
"x-order": 2,
"default": null
},
"contact_id": {
"type": [
"integer",
"null"
],
"label": "Primary Contact",
"x-widget": "contact",
"x-depends-on": "client_id",
"x-order": 3,
"default": null
},
"priority": {
"type": "integer",
"label": "Priority Level",
"default": 3,
"x-order": 4
},
"budget": {
"type": [
"number",
"null"
],
"label": "Budget",
"default": null,
"x-order": 5
},
"due_date": {
"type": [
"string",
"null"
],
"label": "Due Date",
"x-widget": "date",
"x-order": 6,
"default": null
},
"is_billable": {
"type": "boolean",
"label": "Billable",
"default": true,
"x-order": 7
},
"notes": {
"type": [
"string",
"null"
],
"label": "Notes",
"x-multi-line": true,
"x-rows": 5,
"x-order": 8,
"default": null
},
"assigned_to": {
"type": [
"integer",
"null"
],
"label": "Assigned To",
"x-widget": "user",
"x-order": 9,
"default": null
},
"tags": {
"type": [
"array",
"null"
],
"label": "Tags",
"x-widget": "tags",
"x-module": "projects",
"x-order": 10,
"default": null
}
},
"required": [
"status",
"priority",
"is_billable"
]
}
Validation
Backend Validation
All UDF data is validated against the schema on save:
- Type checking (string, integer, number, boolean)
- Required field validation
- Enum value validation
- Field dependencies are checked
Frontend Validation
The UI provides real-time validation:
- Required fields show error if empty
- Number fields validate numeric input
- Integer fields validate integer input
- Nullable fields can be cleared
Validation Errors
Validation errors are returned in the format:
{
"udf": {
"field_name": "Error message"
}
}
Best Practices
- Always specify x-order: Ensures consistent field ordering
- Use null for optional fields: Include
"null"in type array - Set sensible defaults: Provide default values for all fields
- Use dependencies wisely: Create logical field relationships
- Keep labels concise: Use clear, short labels (2-4 words)
- Document custom widgets: Use comments to explain complex schemas
- Test validation: Verify required/optional field behaviour
- Consider field width: Fields are displayed in 3-column layout
Common Patterns
Optional Reference Field
{
"type": [
"integer",
"null"
],
"label": "Related Entity",
"x-widget": "client",
"default": null
}
Required Dropdown
{
"type": "string",
"label": "Category",
"enum": [
"A",
"B",
"C"
],
"default": "A"
}
Dependent Selectors
{
"parent_id": {
"type": [
"integer",
"null"
],
"x-widget": "client",
"x-order": 1
},
"child_id": {
"type": [
"integer",
"null"
],
"x-widget": "contact",
"x-depends-on": "parent_id",
"x-order": 2
}
}
Large Text Field
{
"type": [
"string",
"null"
],
"label": "Description",
"x-multi-line": true,
"x-rows": 8,
"default": null
}