20 minlesson

Building a Module End-to-End

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 cxtms available in your project (added in Topic 2)
  • A .cx-schema/ directory in the project root (populated during app 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:

bash
1npx 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 module block with a fresh appModuleId UUID
  • An entities block with the five fields wired in
  • Default Read and Write permissions
  • A single route pointing to an OrderBrowser/List component
  • A dataGrid component with a default view including all five columns

Step 2 — Read the Generated File

bash
1cat modules/order-browser-module.yaml

Take note of the sections that need customisation:

  • The entityKind defaults to Other — update it to Order
  • The route path may be a generic placeholder — update it to /orders
  • The default view column labels use raw field names — update them to friendly display names
  • The onRowClick action 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

yaml
1entities:
2 - name: Order
3 entityKind: Order # Was: Other
4 extension: false
5 displayName:
6 en-US: "Transport Order"
7 fields:
8 - name: orderId
9 fieldType: number
10 displayName: { en-US: "Order ID" }
11 isCustomField: false
12 props:
13 allowOrderBy: true
14 allowFilter: true
15 - name: orderNumber
16 fieldType: text
17 displayName: { en-US: "Order Number" }
18 isCustomField: false
19 props:
20 allowOrderBy: true
21 allowFilter: true
22 - name: customerName
23 fieldType: text
24 displayName: { en-US: "Customer" }
25 isCustomField: false
26 props:
27 allowFilter: true
28 - name: status
29 fieldType: text
30 displayName: { en-US: "Status" }
31 isCustomField: false
32 props:
33 allowFilter: true
34 - name: dispatchDate
35 fieldType: date
36 displayName: { en-US: "Dispatch Date" }
37 isCustomField: false
38 props:
39 allowOrderBy: true
40 allowFilter: true

Route: correct the path and add a title

yaml
1routes:
2 - name: orderBrowserList
3 path: "/orders" # Was: generic placeholder
4 component: OrderBrowser/List
5 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:

yaml
1components:
2 - name: "OrderBrowser/List"
3 displayName: { en-US: "Order Browser" }
4 permissions: "OrderBrowser/Read"
5 layout:
6 component: layout
7 name: pageLayout
8 props:
9 orientation: vertical
10 children:
11 - component: toolbar
12 name: pageToolbar
13 props:
14 title:
15 en-US: "Transport Orders"
16 buttons:
17 - component: button
18 name: createOrderBtn
19 props:
20 label: { en-US: "New Order" }
21 icon: plus
22 options:
23 variant: primary
24 onClick:
25 - navigate: "~/orders/create"
26 - component: dataGrid
27 name: ordersGrid
28 # 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:

yaml
1 - component: card
2 name: ordersCard
3 props:
4 options:
5 variant: outlined
6 header:
7 title: "All Orders"
8 children:
9 - component: dataGrid
10 name: ordersGrid
11 props:
12 refreshHandler: orders
13 views:
14 - name: all
15 displayName: { en-US: "All Orders" }
16 columns:
17 - name: orderId
18 isHidden: true
19 - name: orderNumber
20 label: { en-US: "Order #" }
21 sticky: left
22 - name: customerName
23 label: { en-US: "Customer" }
24 - name: status
25 label: { en-US: "Status" }
26 showAs:
27 component: badge
28 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: dispatchDate
38 label: { en-US: "Dispatch Date" }
39 showAs:
40 component: text
41 props:
42 value: "{{ format dispatchDate L }}"
43 orderBy:
44 - name: dispatchDate
45 direction: DESC
46 options:
47 query: orders
48 rootEntityName: Order
49 entityKeys: [orderId]
50 navigationType: navigate
51 enableDynamicGrid: true
52 enableViews: true
53 enableSearch: true
54 enablePagination: true
55 enableColumns: true
56 enableFilter: true
57 defaultView: all
58 onRowClick:
59 - navigate: "~/orders/{{ orderId }}"

Step 6 — Validate

Run the validator and fix any reported issues before deploying:

bash
1npx cxtms modules/order-browser-module.yaml

Common validation issues at this stage:

  • Missing default entry in a badge color map
  • dataGrid.options missing required fields (query, rootEntityName, entityKeys, navigationType, enableDynamicGrid, enableViews, enableSearch, enablePagination, enableColumns, enableFilter, defaultView, onRowClick)
  • A component name referenced in routes that does not exist in components
  • 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:

bash
1npx cxtms appmodule deploy modules/order-browser-module.yaml --org 42

Open the CX application, navigate to /orders, and verify:

  1. The toolbar renders with the "New Order" button
  2. The data grid loads with the five columns
  3. The "Status" column renders colored badges
  4. The "Dispatch Date" column shows a formatted date
  5. Clicking a row navigates to ~/orders/{{ orderId }}

Summary

In this lesson you:

  1. Scaffolded an OrderBrowser module with npx cxtms create module --template grid --options
  2. Updated the entityKind from Other to Order and corrected the route path
  3. Added a toolbar with a primary "New Order" button that navigates to a create route
  4. Wrapped the dataGrid in a card component with an outlined variant and header
  5. Configured the status column to render a badge with a color map
  6. Validated with npx cxtms and deployed with cxtms 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.

Building a Module End-to-End - Anko Academy