Skip to content

Customization Guide ​

This template is designed to be trimmed down or extended. Start by deciding what you need, then remove or customize accordingly.

What's optional ​

ComponentPurposeRemove if...
apps/webVue 3 frontendBuilding an API-only service or using a different frontend
apps/mcpAI assistant toolsNot using AI integrations
apps/docsDocumentation siteUsing external docs or none
packages/cliCommand-line interfaceNo CLI needed
packages/playwrightE2E testsNot testing the web app
packages/uiShared UI presetNo web frontend

Always keep: apps/api, packages/shared, packages/logger, packages/eslint-config, packages/ts-config, packages/test-utils.

Removing components ​

API-only setup ​

bash
# Remove frontend and CLI
rm -rf apps/web apps/docs apps/mcp packages/cli packages/playwright packages/ui

# Update turbo.json - remove tasks referencing deleted apps
# Update package.json - remove workspace references
# Update docker-compose files - remove web/docs/mcp services
# Update helm/ - remove web/docs/mcp templates

Update turbo.json:

jsonc
{
  "tasks": {
    "build": {
      "dependsOn": ["^build"]
      // Remove apps/web, apps/docs from outputs
    }
  }
}

Keeping specific apps ​

If you only need API + Web (no CLI, no MCP):

bash
rm -rf apps/mcp packages/cli
# Update turbo.json, docker-compose, helm accordingly

Replacing the example domain ​

The project resource is a placeholder. Replace it with your actual domain:

1. Rename resource files ​

bash
# Rename the resource folder
mv apps/api/src/resources/projects apps/api/src/resources/products

# Rename files inside
mv products/projects.ts products/products.ts
# etc.

2. Update Prisma schema ​

Replace project.prisma content:

prisma
// apps/api/prisma/product.prisma

model Product {
  id             String   @id @default(uuid())
  name           String
  description    String?
  price          Decimal
  ownerId        String
  organizationId String?
  createdAt      DateTime @default(now())
  updatedAt      DateTime @updatedAt

  @@index([ownerId])
  @@index([organizationId])
  @@map("product")
}

Then regenerate:

bash
bunx prisma generate
bunx prisma migrate dev --name add_products

3. Update access control ​

Edit apps/api/src/modules/auth/access-control.ts:

ts
export const accessControl = createAccessControl({
  organization: ['update', 'delete'],
  member: ['create', 'update', 'delete'],
  invitation: ['create', 'update', 'delete'],
  role: ['create', 'read', 'update', 'delete'],
  // Replace project with your resource
  product: ['create', 'read', 'update', 'delete'],
  setting: ['read', 'update'],
  audit: ['read'],
})

Update role definitions (ownerRole, adminRole, memberRole) accordingly.

4. Update routes ​

Rename and edit route files to use your new resource.

Adding new resources ​

Follow this pattern for each new domain resource:

1. Create resource folder ​

txt
apps/api/src/resources/orders/
├── index.ts        # Re-exports
├── router.ts       # Fastify routes
├── business.ts     # Business logic (pure functions)
├── queries.ts      # Database queries
├── schemas.ts      # Zod validation schemas
├── constants.ts    # Resource constants
└── business.spec.ts # Tests

2. Add to access control ​

ts
// access-control.ts
export const accessControl = createAccessControl({
  // ... existing
  order: ['create', 'read', 'update', 'delete', 'cancel'],
})

3. Add Prisma model ​

Create apps/api/prisma/order.prisma:

prisma
model Order {
  id             String      @id @default(uuid())
  // ... your fields
  ownerId        String
  organizationId String?

  @@index([ownerId])
  @@index([organizationId])
  @@map("order")
}

4. Register routes ​

ts
// apps/api/src/resources/index.ts
import orderRoutes from './orders/router.js'

export async function registerResources(app: FastifyInstance) {
  await app.register(projectRoutes, { prefix: '/projects' })
  await app.register(orderRoutes, { prefix: '/orders' })  // Add
}

Customizing authentication ​

Disable features ​

Edit apps/api/src/modules/auth/index.ts:

ts
const auth = betterAuth({
  // Remove plugins you don't need
  plugins: [
    // twoFactor(),        // Remove for no 2FA
    // organization(),     // Remove for single-tenant
    // apiKey(),           // Remove if no API keys needed
    bearer(),              // Keep for session auth
    openAPI(),             // Keep for Swagger
  ],
})

Single-tenant mode: Remove organization() plugin and update permission middleware to skip org context.

Enable Keycloak SSO ​

Set env vars:

bash
AUTH__KEYCLOAK__ENABLED=true
AUTH__KEYCLOAK__ISSUER=https://keycloak.example.com/realms/myrealm
AUTH__KEYCLOAK__CLIENT_ID=my-client
AUTH__KEYCLOAK__CLIENT_SECRET=***

Customizing modules ​

Disable audit ​

bash
MODULES__AUDIT=false

When disabled, app.auditLogger is a no-op — no code changes needed.

Add a new module ​

  1. Create folder: apps/api/src/modules/billing/
  2. Implement AppModule interface:
ts
// apps/api/src/modules/billing/index.ts
import type { AppModule } from '~/types/module.js'

export const billingModule: AppModule = {
  name: 'billing',

  register: async (app) => {
    // Register routes, decorators
    app.decorate('billing', billingService)
  },

  onReady: async (app) => {
    // Initialize after server starts
  },

  onClose: async (app) => {
    // Cleanup on shutdown
  },
}
  1. Register in apps/api/src/modules/index.ts:
ts
import { billingModule } from './billing/index.js'

export const modules: AppModule[] = [
  authModule,
  auditModule,
  billingModule,  // Add
]
  1. Add toggle:
ts
// config.ts
modules: {
  audit: env.MODULES__AUDIT !== 'false',
  billing: env.MODULES__BILLING === 'true',  // Add
},

Customizing the web app ​

UI components ​

Components follow the shadcn-vue model: they are vendored into apps/web/src/components/ui/ (built on Reka UI primitives + Tailwind), not installed from a component library. To customize a component, edit its file directly — there is no upstream package to override or fork. To add one, copy a shadcn-vue component into a new folder under components/ui/ and adapt its imports (cn comes from @template-monorepo-ts/ui).

Change theming ​

Design tokens live in apps/web/src/assets/index.css — semantic tokens (--background, --primary, --border, …) derived from two runtime palette scales (--surface-N, --primary-N). The admin theme settings rewrite those scales at runtime from the palettes in apps/web/src/lib/palette.ts (see stores/theme.ts). Edit index.css for static changes (radius, fonts, dark-scheme mapping); use the theme settings' preset JSON for per-deployment CSS variable overrides.

Helm customizations ​

API-only deployment ​

yaml
# helm/values.yaml
web:
  enabled: false
docs:
  enabled: false
mcp:
  enabled: false

External database ​

yaml
api:
  env:
    DATABASE_URL: "postgresql://user:pass@external-db:5432/mydb"

cnpg:
  enabled: false

External Redis ​

yaml
api:
  env:
    AUTH__REDIS_URL: "redis://external-redis:6379"

redis:
  enabled: false

Common customization scenarios ​

ScenarioActions
SaaS multi-tenantKeep everything, enable orgs, add quotas
Internal APIRemove web/mcp/cli, disable public signup
Single-tenant appRemove org plugin, simplify permissions
Headless CMS APIReplace project with content types, add media resource
E-commerce backendAdd product, order, payment resources

Checklist after customization ​

  • [ ] Update package.json workspace paths
  • [ ] Update turbo.json pipeline
  • [ ] Run bun install to refresh lockfile
  • [ ] Run bunx prisma generate
  • [ ] Run bun run compile (no type errors)
  • [ ] Run bun run test (all pass)
  • [ ] Update docker-compose.*.yml
  • [ ] Update helm/values.yaml
  • [ ] Update README.md