The properties contract
Every setting of a block lives in a single object field: properties. What may go in it is not a matter of habit — it is declared, block type by block type, in a schema file, and validation refuses the rest.
That is what makes the dev panel possible: it knows no block in particular, it reads the schema and builds the fields it finds there. Adding a property to a block therefore means adding a line to its schema, not code to the panel.
What a declared property looks like
"vl_InputType": {
"section": "blocParameters", // which accordion of the panel shows it
"control": "select", // HOW the panel renders it
"valueType": "integer", // the 4D TYPE actually stored
"label": "BSPK_inputType", // a translation key, never literal text
"values": [ ... ], // closed list, enforced
"visibleWhen": { ... }, // hidden until a condition holds
"dependents": [ ... ] // fields to redraw when this one changes
}
control is not valueType
This is the confusion that costs the most, because it produces no message at all.
control is the widget shown in the panel. valueType is the 4D type of the stored value. The two differ far more often than you would expect: vl_InputType is rendered as a dropdown and stored as an integer.
Trap: writing "0" where 0 is expected raises nothing at write time. It shows up later — a query that returns nothing, or a field behaving oddly. No error is raised anywhere along that chain. When in doubt, read the Type column on the object's reference page.
The possible types are: text, integer, boolean, enum, object, collection.
The flags, and what they decide
A schema does not only describe a type: it carries flags that change the behaviour of the panel, of the rendering, and even of the security check.
| Flag | What it does |
|---|---|
| mandatory | The property is needed for the block to be complete. It does not block saving — see below. |
| internal | No input field: the value is written by the panel's own machinery. |
| supportTranslation | The value is stored per language, not as a plain value. |
| allow4D | The value may be a 4D formula instead of a literal. |
| htmlAttr | The value is emitted as an HTML attribute on the rendered element. |
| securityConstraint | The value is read back by the security check when a form is submitted. |
| needRefocus | Changing this value requires the panel field to be redrawn. |
| multiple | The value is a collection, not a plain value. |
| store: "root" | The value is not in the bag: it is a real table field. Only one case today, the block name. |
What blocks a write, and what does not
Validation distinguishes two kinds of problem, and the distinction is deliberate.
- Errors are structural: unknown property, wrong 4D type, CSS target that does not exist. They signal a bug, so the write is refused, loudly.
- "Missing" is about completeness: a mandatory property left empty. The write is accepted — work in progress is legitimate. The block simply carries an incompleteness badge.
An unknown property name is therefore a hard error, with three tolerated families, because free HTML attributes are legitimate: data-*, aria-* and role are allowed through.
Translatable fields are objects
A property flagged supportTranslation does not hold a string but one value per language:
"vt_FieldLabel": { "fr": "Nom du client", "en": "Customer name" }
Validation checks each language separately. Writing a bare string where the language object is expected is the mistake to avoid. Note as well that rich text and long text are stored base64 encoded inside those language values.
Multi-value fields
With multiple, the value is a collection and each item is validated individually. The closed list is only enforced when the field is not a combo box: a combo exists precisely to accept values outside the list — an unusual file extension, say — and refusing them would contradict the field's own definition.
Conditional fields
Two keys work together, and you have to think of both:
- visibleWhen goes on the field that hides: it names the other property and the expected value. The star means "as soon as the other one has any value at all".
- dependents goes on the field that triggers: it lists the fields to redraw when this one changes.
Trap: forget an entry in dependents and the panel keeps showing a stale field. No error, just a value that no longer matches what the user has just picked.
What comes next
Styling does not go through properties: it has its own field, described in Styling: the CSS cube and Tailwind. And the full property list of a block type is on its own page, under Blocks.

