Add a section
Goal: create a new kind of record (a "table") in Dédalo by authoring ontology nodes — usually with no code at all.
A section is the Dédalo equivalent of an SQL table, but it is not a physical
table: it is an ontology node with model: "section" plus the generic
section-read engine (src/core/section/read.ts, readSection) that reads,
writes, relates and renders the records that belong to it. There is no
per-section module — the horizontal engine handles every section by its
ontology definition. Adding a section therefore means authoring nodes, not
writing code.
Before you start, read the concept and reference pages this guide builds on — it does not repeat them:
- Sections concept — the
matrixtable model, typed-JSONB storage, and thesection/sections/section_recordfamily. sectionreference — modes, the relations bag, children resolution.- Ontology authoring — the node shape, the
propertiesgrammar, the regenerate (compile) step that makes an edit live. - request_config examples — copy-pasteable list and edit configs.
When do you need this — ontology-only vs code
| You want to… | What to author | Need code? |
|---|---|---|
| A new record type with standard fields, layout, search and lists | A section node + component_* children + grouper layout |
No |
| Group fields into panels/tabs | section_group / section_tab grouper nodes |
No |
| Tune what columns the list shows, the edit form layout, pagination | request_config in the section node's properties |
No |
| Bespoke server logic on the section itself (custom record creation, a non-standard relations rule, a special save path) | A change in the section engine (src/core/section/) |
Yes (very rare) |
The overwhelming majority of sections are ontology-only. Bespoke server behavior is the last row — see When engine code is warranted. This is the same flow as the generic authoring procedure in Ontology authoring → Step-by-step; this page is the section-specific walk-through.
Step-by-step
You author every node in the Ontology area (System administration → Ontology). Each step below is one node or one field on a node.
1. Create the section node
Create a child record under the area (or TLD root) that will contain the section, and set:
| field | value |
|---|---|
model |
section |
parent |
the container node (e.g. an area tipo); auto-set when you create under a node |
tld |
the two-or-more-letter TLD prefix (e.g. rsc) |
lg-* term |
the section's display label, per language |
The node's tipo is TLD + the editable record's section_id — a record
with section_id = 197 under TLD rsc compiles to node rsc197. That numeric
suffix is exactly the section_tipo that ends up in the matrix table.
{
"tipo": "rsc197",
"parent": "tch188",
"model": "section",
"model_tipo": "dd6",
"tld": "rsc",
"lg-eng": "People", "lg-spa": "Personas", "lg-cat": "Persones"
}
2. Point the section at its physical table (matrix_table)
A section does not invent a table — the engine resolves one with
getMatrixTableFromTipo() (src/core/ontology/resolver.ts). That function reads
a related term of model matrix_table off the section node; its term value
is the physical table name. If you author no matrix_table relation it
falls back to the shared matrix table, which is the normal case for an
ordinary section. Author a matrix_table term only when the section must live
in its own physical table.
Built-in exceptions
Two core sections short-circuit this lookup by tipo constant:
the projects section → matrix_projects and the users section →
matrix_users (the fixed cases in getMatrixTableFromTipo()). You do not
author those.
3. Add the component children (the "columns")
A section's columns are its component children. Create each field as a node whose:
parent= the section tipo (logical ownership), andparent_grouper= the layout container it renders under (the section itself, or asection_group/section_tabfrom step 4).
Pick each component's model for what it stores — see
Add a component for the base-class decision and the full
component checklist. Literal components (e.g. component_input_text) store their
value in the record's typed JSONB columns; relation-bearing components
(e.g. component_portal) write locators into the
record's shared relations array, which the section owns (see
Relations are section-owned).
[
{ "tipo": "rsc85", "model": "component_input_text",
"parent": "rsc197", "parent_grouper": "rsc197", "lg-eng": "Name" },
{ "tipo": "rsc86", "model": "component_input_text",
"parent": "rsc197", "parent_grouper": "rsc197", "lg-eng": "Surname" }
]
At runtime the section-read engine walks the recursive children and filters them
by model (emitDdoData in src/core/section/read.ts dispatches each child by
its resolved model) — you never register them anywhere.
Record identity is automatic
Every section gets an implicit component_section_id that surfaces the
record's section_id; you do not author the id field (it is emitted by the
component_section_id short-circuit in emitDdoData). Parent/child links
between records use component_relation_parent / component_relation_children
component nodes when you need a hierarchy.
4. Group fields into panels and tabs (layout)
Layout is done with grouper models, which hold no data and produce no
tools — they exist purely to organise the form. The recognised groupers are
exactly (GROUPER_MODELS in src/core/concepts/section.ts):
section_group · section_group_div · section_tab · tab
To add a panel, create a section_group (or section_tab) node under the
section, then point your component children's parent_grouper at that grouper
instead of at the section. The component's parent stays the section — only the
layout placement moves. See section_group
and section_tab for the grouper reference.
[
{ "tipo": "rsc197a", "model": "section_group",
"parent": "rsc197", "parent_grouper": "rsc197", "lg-eng": "Identity" },
{ "tipo": "rsc85", "model": "component_input_text",
"parent": "rsc197", "parent_grouper": "rsc197a", "lg-eng": "Name" }
]
5. Configure list vs edit with request_config
What the list view shows and how the edit form is laid out are driven by the
request_config descriptor in the section node's properties (authored in
ontology17; the runtime builder is src/core/relations/request_config/ — v5
zero-config auto-derive and v6 explicit; see
Ontology authoring → properties).
The list and edit modes are separate configs distinguished by the type /
mode they target. Do not invent the JSON — copy the closest scenario from
request_config examples:
- list columns → Section List Configuration
- edit form → Section Edit Configuration
A list config selects which component tipos appear as columns via a show.ddo_map;
an edit config lays out the same children for the record form. With no
request_config the section still works with sensible defaults — add it to
control ordering, hidden fields, pagination and views.
6. Wire the section into a menu
A section that no menu links to is reachable only by direct tipo. Add it to the
ontology menu tree (model menu) under the relevant area so it appears in
the UI navigation, exactly as existing sections are wired.
7. Regenerate (compile) so the edit goes live
Edits live in the editable ontology layer; the runtime reads the compiled
flat dd_ontology table. Nothing is live until you regenerate the TLD — see
Ontology authoring → How changes apply live.
After regenerating, open the section: readSection resolves the model and builds
the response from your nodes — no code deploy and no schema migration.
Worked example — a "People" section
A minimal People section with two literal fields and one relation to
interviews, stored in the shared matrix table (no matrix_table relation
authored). This mirrors the full
worked example in the sections concept doc.
[
{ "tipo": "rsc197", "model": "section", "parent": "tch188",
"model_tipo": "dd6", "tld": "rsc",
"lg-eng": "People", "lg-spa": "Personas" },
{ "tipo": "rsc197a", "model": "section_group",
"parent": "rsc197", "parent_grouper": "rsc197", "lg-eng": "Identity" },
{ "tipo": "rsc85", "model": "component_input_text",
"parent": "rsc197", "parent_grouper": "rsc197a", "lg-eng": "Name" },
{ "tipo": "rsc86", "model": "component_input_text",
"parent": "rsc197", "parent_grouper": "rsc197a", "lg-eng": "Surname" },
{ "tipo": "rsc198", "model": "component_portal",
"parent": "rsc197", "parent_grouper": "rsc197", "lg-eng": "Interviews",
"relations": [ /* target section/components: the interview section */ ] }
]
A saved record then lives in matrix as (section_tipo = "rsc197", section_id = N)
with data holding rsc85/rsc86 values plus the shared relations array for
the portal — no other wiring. Regenerate, add it to a menu, and the section is
fully usable: list, edit, search and the relations bag all come from the generic
section-read engine.
When engine code is warranted
Almost never. There is no per-section module to hook. The generic engine
(src/core/section/read.ts, src/core/section/record/) handles every section by
its ontology definition, so a section that needs genuinely bespoke server
behavior (a non-standard record-creation rule, a special save path, a bespoke
relations policy) is expressed in the engine, keyed on the section tipo or
model. Behavior is horizontal, never per-model. Before reaching for engine code,
exhaust the node-authoring options above — the vast majority of sections need
none.
If you only need a different storage shape per component, that belongs in the
component (its descriptor.ts column), not in the section engine — see
Add a component.
Common pitfalls
- Forgetting to regenerate. The editable layer is not the runtime layer. An un-compiled section will not load. Always regenerate the TLD after editing.
parentvsparent_grouperconfusion.parentis logical ownership (always the section);parent_grouperis layout placement (the section, asection_group, or asection_tab). Mixing them up drops fields from the form or detaches them from the section.- Expecting a grouper to store data.
section_group/section_group_div/section_tab/tabare layout only — they hold no value and emit no tools. - Authoring a
matrix_tablerelation you did not mean to. Without it the section correctly defaults to the sharedmatrixtable. Add it only for a section that needs its own physical table. - Setting
modeltosectionon a node meant to be something else. The section-read engine dispatches by the node's resolved model; a mismatched model routes the record into the wrong engine path (or refuses it), andgetMatrixTableFromTipo()returnsnullfor a non-section tipo — keep the model accurate. - Trying to change a shared node's
modelfrom a local override.modelandis_modelare always read from the canonical node (see Ontology authoring).