The Boring Tool
Format reference
Field keys, tokens, stored JSON, and generate semantics for TBT custom documents.
This page describes the two custom document types registered by TheBoringTool (TBT) for Mendix Studio Pro 11.x:
| Document type id | Readable name | Typical use |
|---|---|---|
TheBoringTool.ValidationExpression | TBT Validation Expression | Reusable validation condition + feedback message for Validate… and DTO… |
TheBoringTool.AttributePreset | TBT Attribute Preset | Reusable attribute definition for the Attributes picker |
Content is stored as JSON inside a Studio Pro custom blob document. You edit it through the TBT form editor (not as raw JSON). The editor debounces changes and writes a normalized JSON blob via the model API.
Source of truth for this page: development branch schemas, Svelte editors, generate/DTO emitters, and catalog merge code.
1. TBT Validation Expression
1.1 What it is and where it lives
A TBT Validation Expression is a reusable validation template: a Mendix-style boolean expression, an optional parameter list (with optional operators), and a message template. At generate time TBT inlines the template into an Exclusive Split (expression condition) on an attribute validation microflow / DTO attribute nanoflow, and substitutes the message for Validation Feedback.
- Document type id:
TheBoringTool.ValidationExpression - Readable name in Studio Pro:
TBT Validation Expression - New-document name hint:
ValidationExpression - App Explorer: any module / folder. TBT loads all documents of this type in the app; there is no seeded library folder for validation expressions.
- Create / edit: add a document of type TBT Validation Expression, then edit fields in the tab editor. There is no Save button in the form — changes persist automatically (debounced). Users do not edit raw JSON.
1.2 Editor fields and stored keys
Section headings in the editor: General, Params, Expression.
| Editor label | Stored key | Type | Allowed values | Required | Default | Notes |
|---|---|---|---|---|---|---|
| (implicit) | schemaVersion | number literal 1 | always 1 | yes (always written) | 1 | Invalid / missing version is coerced to 1 on parse |
| Label | label | string | any | optional | "" | Catalog display name; if empty, the Studio Pro document name is used as the preset label |
| Documentation | documentation | string | any | optional | "" | Shown collapsed in Validate/DTO preset descriptions |
| Applies to | appliesTo | ValueType[] | String, Integer, Long, Decimal, DateTime, Boolean | optional | [] | Checkboxes. Unknown types dropped; duplicates removed. HashedString attributes match when String is included |
| Server-only | serverOnly | boolean | true / false | optional | false | See §1.3 |
| Default for attribute names | defaultForAttributes | string[] | attribute name hints | optional | [] | Chip list. Trimmed; empties dropped; case-insensitive dedupe (first casing wins) |
| Param *N* name | params[].name | string | any (used as $Name / {Name} token) | optional per param | "" | Exact name is used in tokens; longer names substituted first |
| Param *N* type | params[].type | ValueType | same as Applies to | optional | String if invalid | |
| Required (on param) | params[].required | boolean | optional | false | Used by Validate/DTO UI validation of usage values | |
| Param *N* default | params[].default | string | number | boolean | null | typed; empty clears; literal null → null | optional | omitted | Invalid typed input dropped |
| Operator | params[].operator | { options: string[]; default: string } | optional | omitted | Enabling Operator seeds options: [">=", ">"], default: ">=" | |
| Param *N* operator options | params[].operator.options | string[] | comma-separated in the form | required if Operator on | Trimmed; empties / duplicates dropped. Empty list → operator removed on normalize | |
| Param *N* default operator | params[].operator.default | string | must be one of options | required if Operator on | first option if invalid | |
| Expression | expression | string | Mendix expression template | optional | "" | Placeholders in §1.3 |
| Message | message | string | feedback template | optional | "" | Placeholders in §1.3 |
Raw JSON is never presented for editing; only the form above.
1.3 Tokens, expression syntax, and semantics
Expression placeholders
| Token | Meaning |
|---|---|
$Value | Replaced with the attribute path, e.g. $Order/Age |
$ParamName | Replaced with a typed literal from the usage value (or param default). Name must match params[].name exactly. Longer names win (so $MaxValue is not eaten by $Max) |
{ParamName.operator} | Replaced with the chosen operator string (usage → param default → first option) |
The inlined result must be a valid Mendix microflow/nanoflow boolean expression (Exclusive Split expression condition). TBT does not compile-check it beyond string substitution.
Empty values: generated flows run the expression as-is, also when the attribute is empty. Write $Value = empty or ... unless the rule should reject empty values (for example, a "not blank" rule).
Literal formatting when substituting `$ParamName`:
| Param type | Empty usage | Non-empty usage |
|---|---|---|
Integer / Long / Decimal | 0 | trimmed text as-is (e.g. 42) |
Boolean | false | true only if trimmed lower-case is true, else false |
DateTime | empty | if value starts with [, used as-is; else quoted Mendix string (' → '') |
String (default) | '' | quoted Mendix string |
Message placeholders
| Token | Meaning |
|---|---|
{AttributeName} | Attribute name at generate / UI prefills |
{ParamName} | Param usage value (display = trimmed string); longer names first |
{1} | Left intact for Validation Feedback argument wiring (attribute value) |
`serverOnly`
- `false` (default): available to both Validate… (microflow) and DTO… attribute nanoflow paths.
- `true`: still included on the Validate… microflow preset list (
includeServerOnly: true). On the DTO nano path, expressions withserverOnly: trueare excluded (includeServerOnly: false). Tracked as skipped document ids for nano copy.
`appliesTo` matching
A preset is offered for an attribute when appliesTo contains the attribute’s type, or the attribute is HashedString and appliesTo contains String.
`defaultForAttributes` auto-select
When Validate/DTO adds an attribute that has no rules yet, TBT looks for expression presets whose defaultForAttributes contains the attribute name (case-insensitive exact). If several match, the one with the smallest label via localeCompare wins.
1.4 What generate writes into the model
Validate… (attribute validation microflow) for an expression rule:
- Exclusive Split caption = preset
label(or document name). - Expression split condition =
inlineValidationExpression(...)result (e.g.$Person/Age >= 18). - Split documentation =
TBT.ExpressionUsage:+ JSON{ documentId, params }for round-trip reload. - On the
falsebranch: call to mark validation context invalid + Validation Feedback on the attribute. - Feedback template text = the (already substituted) message from the dialog; argument
{1}is bound to$Entity/Attribute.
DTO… (attribute nanoflow): same inlining + TBT.ExpressionUsage: documentation + substituted message, on the nanoflow exclusive split — but only if the expression is not serverOnly.
Expression rules never use Mendix Rules; they always use an expression exclusive split.
1.5 Errors, warnings, and normalization
| Situation | User-visible / logged text |
|---|---|
| Persist failure in editor | Could not save validation expression. (+ details) |
| Document missing on load | Document was not found. |
| Unreadable / non-JSON contents | Document contents could not be read. Expected JSON matching the IR schema. |
| Hand-edited split no longer matches preset | Tooltip: Condition no longer matches the preset; Save will rewrite it |
| Required param empty in Validate UI | parameter {name} is required for {preset label} (wording from generate editor) |
Normalization on parse / save (silent):
- Non-object JSON → empty document (
schemaVersion: 1, empty strings/arrays,serverOnly: false). - Unknown keys dropped.
- Invalid
appliesToentries dropped; invalid param objects dropped; invalid paramtype→String. - Invalid param
defaultfor the type → omitted. - Operator with no options → omitted; default operator coerced into
options. defaultForAttributes: trim, drop empties, case-insensitive dedupe.
1.6 Examples
Example A — simple (no params)
Editor
| Field | Value |
|---|---|
| Label | Not blank |
| Documentation | Trimmed string must not be empty |
| Applies to | String |
| Server-only | unchecked |
| Default for attribute names | (none) |
| Expression | trim($Value) != '' |
| Message | {AttributeName} must not be blank |
Stored JSON
{
"schemaVersion": 1,
"label": "Not blank",
"documentation": "Trimmed string must not be empty",
"appliesTo": ["String"],
"serverOnly": false,
"defaultForAttributes": [],
"params": [],
"expression": "trim($Value) != ''",
"message": "{AttributeName} must not be blank"
}For attribute Name on entity Person, generate inlines to trim($Person/Name) != '' and message Name must not be blank.
Example B — realistic with params + operators
Editor
| Field | Value |
|---|---|
| Label | Range |
| Documentation | Inclusive/exclusive bounds via operators |
| Applies to | Integer, Long, Decimal |
| Server-only | unchecked |
| Default for attribute names | Age |
| Param 1 name / type / required / default | Lower / Integer / yes / 0 |
| Param 1 Operator | on; options >=, >; default >= |
| Param 2 name / type / required / default | Upper / Integer / yes / 100 |
| Param 2 Operator | on; options <=, <; default <= |
| Expression | $Value = empty or ($Value {Lower.operator} $Lower and $Value {Upper.operator} $Upper) |
| Message | {AttributeName} must be between {Lower} and {Upper} |
Stored JSON
{
"schemaVersion": 1,
"label": "Range",
"documentation": "Inclusive/exclusive bounds via operators",
"appliesTo": ["Integer", "Long", "Decimal"],
"serverOnly": false,
"defaultForAttributes": ["Age"],
"params": [
{
"name": "Lower",
"type": "Integer",
"required": true,
"default": 0,
"operator": { "options": [">=", ">"], "default": ">=" }
},
{
"name": "Upper",
"type": "Integer",
"required": true,
"default": 100,
"operator": { "options": ["<=", "<"], "default": "<=" }
}
],
"expression": "$Value = empty or ($Value {Lower.operator} $Lower and $Value {Upper.operator} $Upper)",
"message": "{AttributeName} must be between {Lower} and {Upper}"
}With usage Lower=18/>=, Upper=65/<= on $Order/Age → $Order/Age = empty or ($Order/Age >= 18 and $Order/Age <= 65), message Age must be between 18 and 65.
Example C — server-only
Same as Example A but Server-only checked → "serverOnly": true. Offered in Validate…; omitted from DTO… nanoflow expression presets.
2. TBT Attribute Preset
2.1 What it is and where it lives
A TBT Attribute Preset describes one attribute to create on a domain entity (type, length, default, required, optional calculated microflow, and catalog search metadata).
- Document type id:
TheBoringTool.AttributePreset - Readable name:
TBT Attribute Preset - New-document name hint:
AttributePreset - Seeded folder: module
TheBoringTool→ folderAttributePresets(created/filled on extension register if missing) - Override key: stored field
name— exact, case-sensitive. App-module presets with the samenameoverride TheBoringTool-shipped ones - Create / edit: form editor (auto-persist). Raw JSON is not user-edited. You can also add presets under any module; the picker merges the whole catalog
2.2 Editor fields and stored keys
Sections: Attribute, Catalog.
| Editor label | Stored key | Type | Allowed values | Required | Default | Notes |
|---|---|---|---|---|---|---|
| (implicit) | schemaVersion | 1 | always 1 | yes | 1 | Coerced to 1 on parse |
| Name | name | string | Mendix attribute name; also catalog key | should be non-empty to appear in catalog | "" | Trimmed on parse. Empty name → skipped in catalog merge |
| Type | type | enum | String, Integer, Long, Decimal, DateTime, Boolean | yes | String if invalid | |
| Length | length | number | ≥ 0 integer; String only; 0 = unlimited | optional | omitted | Hidden for non-String; stripped if type ≠ String |
| Default value | defaultValue | string | Mendix StoredValue wire format | optional | omitted | Hidden when Calculated; stripped if calculated |
| Required | required | boolean | optional | omitted / false | Hidden when Calculated; stripped if calculated | |
| Calculated (creates CAL_ microflow under Private) | calculated | boolean | optional | omitted / false | When true, clears defaultValue and required | |
| Tags | tags | string[] | search chips | optional | [] | Trim; drop empties; case-insensitive dedupe |
| Default for entity names | defaultForEntities | string[] | entity name hints | optional | [] | Soft ranking only (never hides) |
| Aliases | aliases | string[] | extra search terms | optional | [] | Name is always searchable |
2.3 Semantics
`name`
Emitted as the attribute name on the target entity. Same string is the catalog override key.
`type` / `length` / `defaultValue`
lengthapplied only when type isString(0= unlimited in Mendix).defaultValuewritten ontoDomainModels$StoredValue.defaultValueas the string you entered (Boolean examples in the seed use"true"; Decimal uses"0").
`required`
When true (and not calculated), after creating the attribute TBT best-effort adds a Required validation rule (DomainModels$RequiredRuleInfo) with error text:
{AttributeName} is required.
If a Required rule for that attribute already exists, it is left alone. Failures are logged; attribute emit still succeeds.
`calculated`
When true:
- Ensures microflow
CAL_{entity}_{attribute}under folderModel/{entity}/Private(folder/name templates are settings-overridable; pattern idcal). - Stub End return:
''/false/empty/0by type (Longreturn type becomesIntegerin the Microflow API). - Attribute value =
CalculatedValuewith that microflow,passEntity: true. - If pattern
calis disabled in Settings → emit error:Calculated attributes are disabled in Settings (pattern cal). - Existing microflow with that name → reuse (no recreate).
Tags / aliases / `defaultForEntities`
- Picker search scores name (exact 100, prefix 80, contains 60), then aliases (55/45), then tags (30).
- If the target entity name matches
defaultForEntities(case-insensitive exact), score gets +15 (softBoost). Presets are never hidden solely because the entity does not match.
2.4 What the Attributes picker writes
For each selected preset (plan: skip if attribute name already on the entity):
| Result | Meaning |
|---|---|
| created | Attribute pushed onto the entity; domain model saved if any created |
| skipped / exists | Picker status: {name} already exists — skipped. |
| error | e.g. Could not load the target entity. or calculated disabled message |
Also creates Required rule and/or calculated microflow as above.
2.5 Errors, warnings, and catalog diagnostics
| Level | Message (from code) |
|---|---|
| editor persist | Could not save attribute preset. |
| load | Document was not found. |
| load | Document contents could not be read. Expected JSON matching the IR schema. |
| picker | Attribute name is required. |
| picker | {name} already exists — skipped. |
| emit | Could not load the target entity. |
| emit | Calculated attributes are disabled in Settings (pattern cal). |
| catalog warn | Multiple Attribute Preset documents in module '{module}' share key '{key}' ({docs}); using '{winner}'. |
| catalog info | '{module}.{doc}' overrides TBT default ({libDocs}) for key '{key}'. |
| catalog warn | Multiple non-TBT modules define Attribute Preset key '{key}' ({modules}); using '{module}.{doc}'. |
Normalization: unknown keys dropped; invalid type → String; non-String length dropped; calculated clears defaultValue/required; string lists trimmed/deduped case-insensitively.
2.6 Examples
Example A — simple Boolean
Editor: Name Active; Type Boolean; Default value true; Tags status; Aliases IsActive, Enabled.
{
"schemaVersion": 1,
"name": "Active",
"type": "Boolean",
"defaultValue": "true",
"tags": ["status"],
"defaultForEntities": [],
"aliases": ["IsActive", "Enabled"]
} Example B — realistic String (seeded Email)
Editor: Name Email; Type String; Length 254; Required unchecked; Tags contact; Default for entity names Person, Customer, User, Contact; Aliases EmailAddress.
{
"schemaVersion": 1,
"name": "Email",
"type": "String",
"length": 254,
"required": false,
"tags": ["contact"],
"defaultForEntities": ["Person", "Customer", "User", "Contact"],
"aliases": ["EmailAddress"]
} Example C — calculated
Editor: Name Total; Type Decimal; Calculated checked (Default value / Required hidden).
{
"schemaVersion": 1,
"name": "Total",
"type": "Decimal",
"calculated": true,
"tags": [],
"defaultForEntities": [],
"aliases": []
}On entity Order this creates attribute Total (calculated) and microflow Order.CAL_Order_Total under Model/Order/Private (stub returning 0), unless pattern cal is disabled.
3. Related flows (quick map)
| UI | Validation Expression | Attribute Preset |
|---|---|---|
| Document editor | Form → normalized JSON blob | Form → normalized JSON blob |
| Validate… | Expression presets (plus Mendix Rules from an optional Validation/Rules/{Type} folder, if present); MF path includes serverOnly | — |
| DTO… | Expression presets on attribute nanoflows; excludes serverOnly | — |
| Attributes picker | — | Merge catalog → emit attributes / rules / CAL_ MFs |