How CRUD Screens Are Generated

How Thunder UI turns SDK module names, groups and permissions into a route tree with list, form and detail pages.

src/core/router.tsx builds the entire route tree at module load from the installed SDK, plus whatever you registered in src/overrides/routes.tsx.

A backend module that does not appear here is usually a stale SDK, not a routing problem. See Two Kinds of Update.

The Build Order

StepWhat happens
1. Module namesCome from ThunderSDK.getModuleNames(), plus any key in src/overrides/routes.tsx that is neither an SDK group nor a built-in route key.
2. Per moduleIf an override exists without merge: true, it replaces the generated route entirely. Otherwise the route is generated and the override is spread over it - so merge: true changes name/icon/priority while keeping the generated children.
3. GroupingUses ThunderSDK.getGroup(name) - the .group("Label") on the backend router, deliberately shared across several route files. Each group becomes a parent route whose path is the group name lowercased with whitespace replaced by dashes. A group's index redirects to its first visible child.
4. VisibilityRequires ThunderSDK.isPermitted(name, "get") or isPermitted(name, "create"), and the override's display must allow it. A group shows only if a child is visible.
5. Built-insoverview, wallet and notifications are prepended, and are replaceable by name.

Step 3 is why .group("Label") on the backend matters for more than the OpenAPI spec: it is what organises the dashboard's navigation. See Controlling the UI From the Backend and Routes.

Generated Children Per Module

Child pathComponentCondition
indexListPage, or the lists overridealways
form, form/:idFormPageonly if the module has create or update
:idViewPagealways

A detail component fetches its own data - see Views. When no views override is registered for a module, its detail route goes to that module's edit form.

URL Shape

URLPage
/redirects to /select-tenant
/select-tenanttenant picker
/:tenant/overviewlanding page
/:tenant/<group>/<module>list
/:tenant/<group>/<module>/formcreate
/:tenant/<group>/<module>/form/:idedit
/:tenant/<group>/<module>/:iddetail

The :tenant segment is pushed into the SDK via ThunderSDK.plugins.essentials.setTenant(). See Authentication & Multi-Tenancy.

TRouteObject

Every entry in the tree - generated or yours - is a React Router RouteObject with Thunder UI's navigation metadata on top:

type TRouteObject = {
  name?: string                        // nav label; passed through t()
  priority?: number | (() => number)   // ascending; ties keep declaration order
  group?: string
  icon?: TablerIcon
  display?: boolean | (() => boolean)
  children?: TRouteObject[]
  button?: React.ComponentType         // rendered beside the nav entry
} & RouteObject

display and priority may be functions, re-evaluated whenever navigation renders - which is what makes permission-gated visibility work. The full set of patterns lives in Route Overrides.


On this page