Appearance
Wire a page's data
What you're doing
You have a page with a core.grid block and it shows zero rows and no error message. This is the single most common confusion on the platform. This guide explains the one chain that connects a grid to a table.
The mental model
page.metadata.dataSource ──names──▶ a Data Provider file ──points at──▶ a REST base path
"oeq-equipment-provider" metadata/provider/ /api/v1/entities/oeq_equipment/recordsThree things must line up exactly:
- The page's top-level
metadata.dataSourceis a string. - That string equals the
namefield of a file inspk-assembly/metadata/provider/. - That provider's
definition.basePathis a real REST path that supportssearch.
The grid block itself has no data configuration. It renders whatever the page's dataSource provider returns.
The complete example
Page (equipment-register.json), last two lines of the file:
json
"metadata": { "dataSource": "oeq-equipment-provider" },
"modules": ["office-equipment"]
}Provider (spk-assembly/metadata/provider/oeq-equipment-provider.json) — the real file:
json
{
"name": "oeq-equipment-provider",
"description": "Data Provider for oeq_equipment (grid + CRUD).",
"definition": {
"kind": "rest",
"connectionRef": "self",
"basePath": "/api/v1/entities/oeq_equipment/records",
"supports": ["search", "get", "create", "update"]
},
"metadata": {},
"modules": []
}The string "oeq-equipment-provider" appears in both files, spelled identically. That is the whole wiring.
Line by line
metadata.dataSource— page-level. Not insidedefinition, not on any block. A grid with no matchingdataSourceshows nothing.- provider
name— the lookup key. Must matchdataSourcecharacter for character. kind: "rest"— the provider calls a REST endpoint.connectionRef: "self"— this ERP's own backend. (External systems use a named connection.)basePath— for an entity you own, it's always/api/v1/entities/<entityName>/records. The Entity Engine servessearchat<basePath>/query,getat<basePath>/{id},createatPOST <basePath>,updateatPUT <basePath>/{id}.supports— which of those the grid/dialogs may use.
Filtering the grid
The grid's externalFilter property binds a page state object. Its keys become query filters:
json
"externalFilter": { "source": "binding", "binding": { "scope": "page", "key": "gridExternalFilter" } }A search input then writes into it:
json
"events": { "committed": { "source": "action-chain", "actions": [
{ "id": "a0", "order": 0, "type": "setValue",
"config": { "field": "page.gridExternalFilter.search", "value": "${event.new}" } }
] } }Now typing in the box filters the grid on search; picking a status filters on status. (${event.new} — the committed value — see Add a create/edit form.)
Refreshing the grid after a write
Bind refreshTrigger to a page value and flip it at the end of a save chain:
json
{ "id": "a2", "order": 2, "type": "setValue",
"config": { "field": "page.gridRefreshTick", "value": "${!page.gridRefreshTick}" } }When you need a computed / joined read
If the grid needs columns from more than one table, or server-side aggregation, the provider's basePath can point at a Data Service or Data View instead of a raw entity. See Add a data provider, data view, or data service.
How to verify it worked
The semantic linter catches the mismatch before you publish:
bash
erp plugin test office-equipment/spk-assemblyIf the wiring is broken you get:
PAGE-DS-001 equipment-register.json: core.grid present but metadata.dataSource
"oeq-equipmnt-provider" does not match any metadata/provider/*.json nameWhen it's right, erp plugin test is silent on PAGE-DS-001 and after publish:
bash
erp api get "/api/v1/entities/oeq_equipment/records/query?size=3"returns real rows — which is exactly what the grid will show.
Common mistakes
| Symptom | Cause | Fix |
|---|---|---|
| grid empty, no error | dataSource typo / absent / put on the grid block | move it to page metadata.dataSource, match the provider name |
| grid empty but the entity has rows | provider basePath wrong | for your own entity it's /api/v1/entities/<name>/records |
| grid loads once, never refreshes after save | no refreshTrigger binding | bind it and toggle it at the end of the save chain |
| filter box does nothing | externalFilter not bound, or committed writes the wrong key | bind gridExternalFilter; write page.gridExternalFilter.<key> |
| "0 rows" only for some users | row-level permissions | expected — check the user's role |