This guide adds a new entity screen to an existing module app. You declare the entity in the module's metadata and add one sidebar section; the web app then shows a tab with a list, a create form, and a record card without any change in webapp/. Background is on metadata rendering. Writing the backend routes for the entity is covered by write a backend module.
Prerequisites¶
- The module exists and is loaded by the module registry; see module lifecycle.
- The entity has HTTP routes for list, get, create, update, and delete under the module's prefix
/api/v1/<module>(src/cluster_erp/core/module_base.py:37-38). - The module has an app in the app catalogue with
screen: "module", so it opens as a module app (webapp/js/home.js:2429). The catalogue issrc/cluster_erp/modules/app_store/catalog.json.
Steps¶
- Declare an
EntityMetafor the entity in the module'smetadata.py. Give labels in English and Turkish, theentity_type, and the endpoint paths relative to the module prefix. Source:src/cluster_erp/core/metadata.py:553-649.
From src/cluster_erp/modules/currencies/metadata.py:302-313:
python
PAYMENT_TERM_META = EntityMeta(
label={"en": "Payment Term", "tr": "Ödeme Koşulu"},
label_plural={"en": "Payment Terms", "tr": "Ödeme Koşulları"},
entity_type="payment_term",
is_settings_entity=True,
endpoints={
"list": EndpointMeta(method="GET", path="/payment-terms/"),
"create": EndpointMeta(method="POST", path="/payment-terms/"),
"get": EndpointMeta(method="GET", path="/payment-terms/{id}"),
"update": EndpointMeta(method="PUT", path="/payment-terms/{id}"),
"delete": EndpointMeta(method="DELETE", path="/payment-terms/{id}"),
},
-
Add a
list_viewwith columns and filters. Setsearch_enabled=Trueif the list endpoint acceptssearch; the list then shows a search field and the entity becomes searchable in the command palette. Source:webapp/js/gen-list.js:582-597,webapp/js/home.js:1241-1243. -
Add a
formwith field groups. Usefield_type="decimal"for money so the value travels as a string. Source:webapp/js/gen-form.js:503-506, money and Decimal.
From src/cluster_erp/modules/currencies/metadata.py:365-373:
python
FieldMeta(
key="code",
label={"en": "Code", "tr": "Kod"},
field_type="string",
required=True,
max_length=20,
weight="hero",
immutable_on_edit=True,
),
-
If the record needs a card with several sections, add a
detail_viewwith tabs. Without one, a row opens a read-only record card built from the form. Source:webapp/js/gen-list.js:816-844. -
Register the entity in
ModuleMeta.entitiesand add a sidebar section withview_type="list"that names the entity key. Source:webapp/js/module-app.js:115-116.
From src/cluster_erp/modules/currencies/module.py:132-141:
python
SidebarSectionMeta(
key="payment_term_list",
label={
"en": "Payment Terms",
"tr": "Ödeme Koşulları",
},
view_type="list",
icon="payments",
entity="payment_term",
),
-
If the screen needs a specific permission beyond the entity's own codes, set
required_permissionon the section. Source:webapp/js/module-app.js:106-108. -
Restart the server. The metadata document is built once per process, and permission codes for each entity are seeded from metadata at start-up. Source:
src/cluster_erp/server/app.py:1530-1543andsrc/cluster_erp/server/app.py:634-858at commit 92f8051. -
Grant the new
<entity_type>.read,.create,.update, and.deletecodes to the roles that need them. The systemadminrole receives every code automatically. See roles and permissions.
Verify the result¶
- Open the module app. A new tab with the section label appears when the module has more than one tab (
webapp/js/module-app.js:148-168). - The tab shows Add New (
common.add) for a user with<entity_type>.create, and the list rows for a user with read access (webapp/js/gen-list.js:396-406,webapp/js/gen-list.js:702-711). - A row tap opens the card; Edit (
common.edit) switches to the form (webapp/js/gen-form.js:2318-2346). - The Turkish label shows when the language is Turkish (
webapp/js/meta.js:284-288).
Troubleshooting¶
| Symptom | Cause in code | Fix |
|---|---|---|
| The server does not start after the change | EntityMeta checks that detail form tabs cover every form group and raises ValueError at import (src/cluster_erp/core/metadata.py:617-649) |
Make the detail form tabs list every non-create_only group |
| The server does not start and names a sub-entity tab | A sub_entity_table tab points at an entity without columns (src/cluster_erp/server/app.py:547-589) |
Give the child entity a list_view or sub_table |
| The tab is missing | The section has hidden_from_bar, or the user lacks required_permission (webapp/js/module-app.js:105-108) |
Remove the flag or grant the permission |
| The tab shows No results found | The section's entity does not match a key in ModuleMeta.entities (webapp/js/gen-list.js:358-362) |
Use the same key in both places |
| Add New is missing | No create endpoint, or the user lacks <entity_type>.create (webapp/js/gen-list.js:396) |
Declare the endpoint or grant the permission |
| The change does not appear | The metadata document is cached per server process (src/cluster_erp/server/app.py:1530-1543) |
Restart the server |
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| sidebar section | SidebarSectionMeta |
One screen of a module, shown as a tab of its module app. | kenar çubuğu bölümü | src/cluster_erp/core/metadata.py:664 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.