Skip to main content

Module Registry

Explore the API
Launch the OpenAPI Explorer to browse the live REST specs, inspect request and response schemas, and execute calls against your environment with an API key.

Open Saasframe keeps module metadata in a generated file rather than a dedicated REST endpoint. When you run yarn generate, the generator writes generated/modules.generated.ts, which exports the module graph consumed by the API dispatcher, navigation builders, and the OpenAPI generator.

import { modules, modulesInfo } from '@/generated/modules.generated'

modulesInfo.forEach((entry) => {
console.log(entry.id, entry.title)
})

const authApis = modules
.find((m) => m.id === 'auth')
?.apis?.map((api) => ('handlers' in api ? api.path : api.path))

Shapes and helpers

The exported types live in packages/shared/src/modules/registry.ts:

  • Module — top-level container with id, optional info, arrays of frontendRoutes, backendRoutes, apis, features, customFieldSets, and more.
  • ModuleApiRouteFile — shape for file-based API routes discovered under packages/<pkg>/src/modules/<module>/api/**/route.ts; includes path, handlers, optional module-level requireAuth/requireRoles, and an openApi descriptor if provided.
  • ModuleRoute — metadata for frontend/back-office pages (pattern, guards, titles).
  • ModuleInfo — optional descriptor exported from each module’s index.ts (name, title, description, dependencies, etc.).

These structures are stable runtime contracts and can be imported anywhere in your application (Next.js server components, scripts, CLI tooling).

Enumerating APIs at runtime

import { modules } from '@/generated/modules.generated'
import type { ModuleApiRouteFile } from '@saasframe/shared/modules/registry'

const apiIndex = modules.flatMap((module) => {
return (module.apis ?? [])
.filter((api): api is ModuleApiRouteFile => 'handlers' in api)
.map((api) => ({
moduleId: module.id,
path: api.path,
methods: Object.keys(api.handlers),
requireAuth: api.metadata?.GET?.requireAuth ?? api.metadata?.POST?.requireAuth ?? false,
requireFeatures: api.metadata?.GET?.requireFeatures ?? api.metadata?.POST?.requireFeatures ?? []
}))
})

console.table(apiIndex)
  • The array includes entries for every enabled module; modules without HTTP handlers (for example, catalog at the time of writing) contribute an empty list.
  • Route-level metadata matches the per-method guards exported alongside the handler (packages/shared/src/modules/registry.ts:188), so you can surface RBAC hints in client SDKs.
  • The dispatcher in src/app/api/[...slug]/route.ts calls findApi(modules, method, pathname) to resolve the handler; your tooling can do the same for dry runs or static analysis.

Features and ACL seeding

Each module declares its feature flags in <module>/acl.ts. The generator hoists them into module.features, enabling tooling to seed default roles or audit coverage:

import { modules } from '@/generated/modules.generated'

const featureMatrix = modules.flatMap((module) =>
(module.features ?? []).map((feature) => ({
module: module.id,
feature: feature.id,
title: feature.title ?? feature.id
}))
)

Combine this with the Auth module’s GET /auth/features endpoint when you need a remote-friendly list; the HTTP route pads the same data with translations (packages/core/src/modules/auth/api/features.ts:5).

When to regenerate

  • Run yarn generate whenever you add, rename, or delete APIs, pages, ACL declarations, or DI registrars.
  • The OpenAPI routes (/api/docs/openapi, /api/docs/markdown) call buildOpenApiDocument(modules, ...), so the explorer immediately reflects new endpoints.
  • The CLI scaffolding (packages/cli/src/saasframe.ts) uses modulesInfo to enable module-aware commands.

Rather than hitting a /modules HTTP endpoint, import the generated registry to inspect enabled modules, enumerate APIs, or reason about feature coverage. This keeps build artifacts deterministic while still giving you the structured metadata needed for automation.

For a human-readable list of every module that ships with Open Saasframe, plus the requires graph that ties them together and how it is enforced, see Module dependency graph.