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     ). If true      and x-rows      is not specified, defaults to 1 row.
  • x-rows     : Number of rows for textarea (integer, requires x-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-widget  must be calculated and x-template must be a non-empty, valid Jinja2 template.
  • The type must be number  or string , and it must include "null" , with a default  of null . A calculation can fail — a division by zero, or referenced data that isn't there yet on a draft — and when it does, Kompass stores null  rather 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 project  is available but is descriptive only — it carries id , name , client , managed_by  and 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 own quote_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 to null .)
  • The previously-saved values: when editing an existing record, the template can compare the current value against what was saved. saved  mirrors the record's own values as they were before this edit (guard it with {% if saved %} , as it's null  for a brand-new record), and changes  is 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

  1. Always specify x-order: Ensures consistent field ordering
  2. Use null for optional fields: Include "null"      in type array
  3. Set sensible defaults: Provide default values for all fields
  4. Use dependencies wisely: Create logical field relationships
  5. Keep labels concise: Use clear, short labels (2-4 words)
  6. Document custom widgets: Use comments to explain complex schemas
  7. Test validation: Verify required/optional field behaviour
  8. 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
}
Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.

Still need help? Contact Us Contact Us