Frontend Module
Defining a FrontendModule: pages, nav, slots.
A FrontendModule declares the UI a module contributes to a Crudy app: pages, sidebar items, settings tabs, custom login methods, an optional home page, and slot contributions into other modules' pages.
Shape
export interface FrontendModule {
manifest: ModuleManifest;
navItems?: NavItemDefinition[];
settingsTabs?: SettingsTabDefinition[];
routes?: RouteDefinition[];
defaultRoute?: string;
HomePage?: ComponentType;
RootComponent?: ComponentType;
loginMethods?: LoginMethodDefinition[];
slotContributions?: Partial<Record<SlotName, ComponentType<SlotContext>>>;
}manifest
Identity. The id must match the backend half's manifest.id.
manifest: { id: 'tasks', name: 'Tasks', version: '0.1.0' }navItems
Sidebar entries the module wants in the app shell.
import { ListChecks } from 'lucide-react';
navItems: [
{ href: '/tasks', label: 'Tasks', icon: ListChecks },
]hiddenByDefault ships the item to the hidden bucket in user preferences. divider inserts a separator above the item.
settingsTabs
Tabs added to the framework's Settings page. Each tab declares a morbac permission required to render; users without the permission do not see the tab.
settingsTabs: [
{
id: 'tasks-defaults',
label: 'Task Defaults',
permission: ['manage', 'tasks'],
component: TaskDefaultsTab,
},
]The component receives token and orgIds. Use these to call the data API and to filter by the current org selection.
routes
Pages the module contributes. Mounted under the framework's catch-all route.
routes: [
{ path: '/tasks', Component: TaskListPage },
{ path: '/tasks/new', Component: NewTaskPage, quota: { key: 'records' } },
{ path: '/tasks/about', Component: AboutTasksPage, public: true },
]Non-public routes receive ModulePageProps: token, userId, userName, userEmail, allOrgs, effectiveOrgIds, checkPermission, and the host UI primitives (AppShell, Card, Button, ...). Use the host primitives so the page respects the app's theme, density, and layout.
When quota: { key: 'records' } is set, the framework checks the current org's remaining quota for that key before rendering. If the cap is reached, the page renders a quota-exceeded state instead of the component.
Public routes receive no props. Use them for marketing pages, docs, or anything that must work without authentication.
defaultRoute
When set, the framework treats this path as the module's landing page. Used for navigation fallbacks.
defaultRoute: '/tasks'HomePage
Replaces the framework's home page when the module is registered. Only one module should set this per app; if more than one does, the last-wins behavior is undefined. The website module uses this to render the marketing landing page in the example app.
RootComponent
A component rendered once near the top of the React tree, inside CrudyProviders but above the routed pages. Use it for module-wide React context that other module pages need to consume.
loginMethods
Custom alternatives to the framework's password login form. Each entry contributes a button or form fragment to the login page.
loginMethods: [
{ id: 'oidc', Component: OidcLoginButton },
]The component receives an optional orgHint so it can pre-select the right tenant.
slotContributions
A way to inject UI into named extension points exposed by other modules or by the framework. The framework defines a few well-known slot names; modules can also define their own.
slotContributions: {
'user.detail.actions': InviteToTeamButton,
'org.detail.actions': ProvisionTaskSpaceButton,
}The component receives SlotContext: orgId, token, userId. Use these to call the data API. Slot components must respect the host theme and remain hidden when the current user lacks permission.
Minimal example
import type { FrontendModule } from '@crudy/frontend';
import { ListChecks } from 'lucide-react';
import { TaskListPage } from './pages/task-list';
export const TasksFrontend: FrontendModule = {
manifest: { id: 'tasks', name: 'Tasks', version: '0.1.0' },
navItems: [{ href: '/tasks', label: 'Tasks', icon: ListChecks }],
routes: [{ path: '/tasks', Component: TaskListPage }],
};