Skip to main content

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.

Monorepo vs. standalone paths

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:

apps/saasframe/src/modules/inventory/index.ts
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:

apps/saasframe/src/modules/inventory/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:

apps/saasframe/src/modules/inventory/setup.ts
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:

apps/saasframe/src/modules/inventory/di.ts
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:

apps/saasframe/src/modules.ts
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
apps/saasframe/src/modules/inventory/backend/inventory/page.tsx
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;
apps/saasframe/src/modules/inventory/backend/inventory/page.meta.ts
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.

Existing reference modules

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.

Publish your module

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.