Step 2: Create your first module
With the base app running, add a custom inventory module directly inside the app. Modules placed under apps/saasframe/src/modules/<id>/ (or src/modules/<id>/ in a standalone app) are automatically resolved when you register them with from: '@app' -- no separate package setup required.
This tutorial uses monorepo paths (apps/saasframe/src/modules/). If you scaffolded a standalone app with create-saasframe-app, replace every apps/saasframe/src/ prefix with src/. Everything else is identical.
1. Scaffold the module
mkdir -p apps/saasframe/src/modules/inventory
touch apps/saasframe/src/modules/inventory/{index.ts,acl.ts,setup.ts,di.ts}
Populate the metadata in index.ts so the module shows up in registries:
import type { ModuleInfo } from '@saasframe/shared/modules/registry';
export const metadata: ModuleInfo = {
name: 'inventory',
title: 'Inventory',
version: '0.1.0',
description: 'Track stock levels for sellable items.',
};
Declare the RBAC features you plan to enforce in acl.ts:
export const features = [
{ id: 'inventory.view', title: 'View inventory', module: 'inventory' },
{ id: 'inventory.create', title: 'Create inventory', module: 'inventory' },
{ id: 'inventory.edit', title: 'Edit inventory', module: 'inventory' },
{ id: 'inventory.delete', title: 'Delete inventory', module: 'inventory' },
];
export default features;
Every module that declares features in acl.ts should also create a setup.ts file with defaultRoleFeatures. Without this, the features exist but no role will have them assigned by default -- users would need to manually grant them from the admin panel:
import type { ModuleSetupConfig } from '@saasframe/shared/modules/setup';
export const setup: ModuleSetupConfig = {
defaultRoleFeatures: {
admin: ['inventory.*'],
employee: ['inventory.view'],
},
};
export default setup;
Create an Awilix registrar in di.ts even if you do not wire services yet -- the file keeps the structure predictable:
import type { AppContainer } from '@saasframe/shared/lib/di/container';
export function register(_: AppContainer) {
// Register services when you introduce business logic.
}
2. Enable the module
Open apps/saasframe/src/modules.ts and add the new entry to the enabledModules array:
export const enabledModules: ModuleEntry[] = [
// ... existing modules ...
{ id: 'inventory', from: '@app' }, // new module
];
The from: '@app' value tells the generator to look for the module inside apps/saasframe/src/modules/inventory/.
Run the generators so the new module is included in the registry:
yarn generate
3. Add the first page
Create the backend page directory and metadata:
mkdir -p apps/saasframe/src/modules/inventory/backend/inventory
touch apps/saasframe/src/modules/inventory/backend/inventory/page.tsx
touch apps/saasframe/src/modules/inventory/backend/inventory/page.meta.ts
const InventoryLanding = () => {
return (
<div className="container">
<h1 className="margin-bottom--md">Inventory</h1>
<p>Welcome! You will add data grids and forms in the next steps.</p>
</div>
);
};
export default InventoryLanding;
import type { PageMetadata } from '@saasframe/shared/modules/registry';
export const metadata: PageMetadata = {
title: 'Inventory',
group: 'Operations',
order: 20,
requireAuth: true,
requireFeatures: ['inventory.view'],
};
After adding pages, regenerate the module registry and clear the navigation cache so the sidebar picks up the new page:
yarn generate
yarn saasframe configs cache structural --all-tenants
Restart the dev server (or wait for hot reload) and open /backend. You should now see an Inventory link in the sidebar that leads to the placeholder page.
Browse apps/saasframe/src/modules/example/ for a full working module that exercises most platform features (entities, APIs, forms, grids, events, dashboard widgets, custom fields, and injection). Core modules from @saasframe/core can be customized by adding override files under apps/saasframe/src/modules/auth, apps/saasframe/src/modules/directory, etc.
If you've built a module that would be useful for others, you can publish it to the Official Modules repository. This is the recommended way to share your work with the community -- your module gets core-team review, npm publication, and one-command installation for all Open Saasframe users.