Building a Module End-to-End
This lesson walks through creating a real module from scratch — an OrderBrowser that lists transport orders in a data grid. You will scaffold it, customise the routes and entity, add a toolbar, wrap the grid in a card, wire up a Create button, and validate the result.
Prerequisites
npx cxtmsavailable in your project (added in Topic 2)- A
.cx-schema/directory in the project root (populated duringapp install) - A
modules/directory in the project root
Step 1 — Scaffold with the Grid Template
Run cxtms create module with the grid template and pre-populate the entity fields using --options:
bash1npx cxtms create module "OrderBrowser" --template grid --options '[2 {"name": "orderId", "type": "number", "label": "Order ID", "required": true},3 {"name": "orderNumber", "type": "text", "label": "Order Number", "required": true},4 {"name": "customerName", "type": "text", "label": "Customer"},5 {"name": "status", "type": "text", "label": "Status"},6 {"name": "dispatchDate", "type": "date", "label": "Dispatch Date"}7]'
The CLI creates modules/order-browser-module.yaml with a complete but generic structure. The generated file contains:
- A
moduleblock with a freshappModuleIdUUID - An
entitiesblock with the five fields wired in - Default
ReadandWritepermissions - A single route pointing to an
OrderBrowser/Listcomponent - A
dataGridcomponent with a default view including all five columns
Step 2 — Read the Generated File
bash1cat modules/order-browser-module.yaml
Take note of the sections that need customisation:
- The
entityKinddefaults toOther— update it toOrder - The route
pathmay be a generic placeholder — update it to/orders - The default view column labels use raw field names — update them to friendly display names
- The
onRowClickaction uses a placeholder path — update it to the real detail route
Step 3 — Update the Entity and Route
Open modules/order-browser-module.yaml and make these changes:
Entity: set the correct entityKind
yaml1entities:2 - name: Order3 entityKind: Order # Was: Other4 extension: false5 displayName:6 en-US: "Transport Order"7 fields:8 - name: orderId9 fieldType: number10 displayName: { en-US: "Order ID" }11 isCustomField: false12 props:13 allowOrderBy: true14 allowFilter: true15 - name: orderNumber16 fieldType: text17 displayName: { en-US: "Order Number" }18 isCustomField: false19 props:20 allowOrderBy: true21 allowFilter: true22 - name: customerName23 fieldType: text24 displayName: { en-US: "Customer" }25 isCustomField: false26 props:27 allowFilter: true28 - name: status29 fieldType: text30 displayName: { en-US: "Status" }31 isCustomField: false32 props:33 allowFilter: true34 - name: dispatchDate35 fieldType: date36 displayName: { en-US: "Dispatch Date" }37 isCustomField: false38 props:39 allowOrderBy: true40 allowFilter: true
Route: correct the path and add a title
yaml1routes:2 - name: orderBrowserList3 path: "/orders" # Was: generic placeholder4 component: OrderBrowser/List5 props:6 title:7 en-US: "Orders"8 icon: "package"9 permission: "OrderBrowser/Read"
Step 4 — Add a Toolbar with a Create Button
Find the OrderBrowser/List component in the components section. Add a toolbar component above the dataGrid in the layout children:
yaml1components:2 - name: "OrderBrowser/List"3 displayName: { en-US: "Order Browser" }4 permissions: "OrderBrowser/Read"5 layout:6 component: layout7 name: pageLayout8 props:9 orientation: vertical10 children:11 - component: toolbar12 name: pageToolbar13 props:14 title:15 en-US: "Transport Orders"16 buttons:17 - component: button18 name: createOrderBtn19 props:20 label: { en-US: "New Order" }21 icon: plus22 options:23 variant: primary24 onClick:25 - navigate: "~/orders/create"26 - component: dataGrid27 name: ordersGrid28 # existing dataGrid config below...
Step 5 — Wrap the DataGrid in a Card
Surround the dataGrid with a card component to give the grid a Material UI card container with a subtle header:
yaml1 - component: card2 name: ordersCard3 props:4 options:5 variant: outlined6 header:7 title: "All Orders"8 children:9 - component: dataGrid10 name: ordersGrid11 props:12 refreshHandler: orders13 views:14 - name: all15 displayName: { en-US: "All Orders" }16 columns:17 - name: orderId18 isHidden: true19 - name: orderNumber20 label: { en-US: "Order #" }21 sticky: left22 - name: customerName23 label: { en-US: "Customer" }24 - name: status25 label: { en-US: "Status" }26 showAs:27 component: badge28 props:29 label: "{{ status }}"30 options:31 colors:32 active: { label: "Active", bgcolor: "#e8f5e9", dot: "#4caf50" }33 pending: { label: "Pending", bgcolor: "#fff8e1", dot: "#ffc107" }34 completed: { label: "Completed", bgcolor: "#e3f2fd", dot: "#1e88e5" }35 cancelled: { label: "Cancelled", bgcolor: "#fce4ec", dot: "#f44336" }36 default: { label: "Unknown", bgcolor: "#f5f5f5", dot: "#9e9e9e" }37 - name: dispatchDate38 label: { en-US: "Dispatch Date" }39 showAs:40 component: text41 props:42 value: "{{ format dispatchDate L }}"43 orderBy:44 - name: dispatchDate45 direction: DESC46 options:47 query: orders48 rootEntityName: Order49 entityKeys: [orderId]50 navigationType: navigate51 enableDynamicGrid: true52 enableViews: true53 enableSearch: true54 enablePagination: true55 enableColumns: true56 enableFilter: true57 defaultView: all58 onRowClick:59 - navigate: "~/orders/{{ orderId }}"
Step 6 — Validate
Run the validator and fix any reported issues before deploying:
bash1npx cxtms modules/order-browser-module.yaml
Common validation issues at this stage:
- Missing
defaultentry in abadgecolor map dataGrid.optionsmissing required fields (query,rootEntityName,entityKeys,navigationType,enableDynamicGrid,enableViews,enableSearch,enablePagination,enableColumns,enableFilter,defaultView,onRowClick)- A component name referenced in
routesthat does not exist incomponents - A localized string written as a bare string instead of
{ en-US: "..." }
Fix each issue, re-run the validator, and repeat until it reports no errors.
Step 7 — Deploy for Testing
Once validation passes, deploy to your development organisation:
bash1npx cxtms appmodule deploy modules/order-browser-module.yaml --org 42
Open the CX application, navigate to /orders, and verify:
- The toolbar renders with the "New Order" button
- The data grid loads with the five columns
- The "Status" column renders colored badges
- The "Dispatch Date" column shows a formatted date
- Clicking a row navigates to
~/orders/{{ orderId }}
Summary
In this lesson you:
- Scaffolded an
OrderBrowsermodule withnpx cxtms create module --template grid --options - Updated the
entityKindfromOthertoOrderand corrected the route path - Added a
toolbarwith a primary "New Order" button that navigates to a create route - Wrapped the
dataGridin acardcomponent with an outlined variant and header - Configured the
statuscolumn to render abadgewith a color map - Validated with
npx cxtmsand deployed withcxtms appmodule deploy
This is the standard workflow for every new module. The next topic extends this foundation with forms, field components, and mutation patterns for creating and editing records.