QUESTPIE
Build Your BackendArchitecture

Codegen

What questpie generate produces — the .generated/ directory, AppConfig, AppContext, and type augmentation.

Running questpie generate scans your file conventions and produces the .generated/ directory. This is the bridge between your definitions and the typed runtime.

What Gets Generated

.generated/
├── index.ts        — App instance, type exports, createContext helper
└── module.ts       — Merged module with all entities

Generated Types

// Auto-generated — do not edit

// Entity type maps
export type AppCollections = {
	appointments: typeof appointments;
	barbers: typeof barbers;
	services: typeof services;
	// ...
};

export type AppGlobals = {
	siteSettings: typeof siteSettings;
};

export type AppRoutes = {
	createBooking: typeof createBooking;
	getActiveBarbers: typeof getActiveBarbers;
	// ...
};

export type AppJobs = {
	sendAppointmentConfirmation: typeof sendAppointmentConfirmation;
	notifyBlogSubscribers: typeof notifyBlogSubscribers;
	// ...
};

export type AppServices = {
	blog: BlogService;
};

export type AppEmails = {
	appointmentConfirmation: typeof appointmentConfirmationEmail;
	// ...
};

// Flat config type for client SDK
export type AppConfig = {
	collections: AppCollections;
	globals: AppGlobals;
	routes: AppRoutes;
	// auth, locales, etc.
};

Module Augmentation

Codegen augments AppContext so every handler gets typed DI:

declare global {
	namespace Questpie {
		interface AppContext {
			db: Database;
			email: MailerService<AppEmailTemplates>;
			queue: QueueClient<AppJobs>;
			collections: AppCollections;
			globals: AppGlobals;
			session: Session | null;
			blog: BlogService; // Custom service
			// ... all services
		}
	}
}

This is why you can destructure { collections, queue, email, blog } in any handler — codegen generates the type augmentation from your file structure.

App Instance

export const app = createApp(mergedModule, runtimeConfig);

export async function createContext(options?: {
	accessMode?: "system" | "user";
	locale?: string;
}) {
	return app.createContext(options);
}

How to Use Generated Types

Client SDK

import type { AppConfig } from "#questpie";

export const client = createClient<AppConfig>({
	baseURL: "http://localhost:3000",
	basePath: "/api",
});

Server-side scripts

import { app, createContext } from "#questpie";

const ctx = await createContext({ accessMode: "system" });
const posts = await app.collections.posts.find({}, ctx);

Typed admin hooks

admin/hooks.ts
import { createTypedHooks } from "@questpie/admin/client";
import type { AppConfig } from "#questpie";

export const {
	useCollectionList,
	useCollectionItem,
	useCollectionCreate,
	useCollectionUpdate,
	useCollectionDelete,
	useGlobal,
	useGlobalUpdate,
} = createTypedHooks<AppConfig>();

When to Re-Run Codegen

Re-run questpie generate when you:

  • Add, rename, or delete collection/global/route/job files
  • Add or remove modules in modules.ts
  • Add or remove plugins in questpie.config.ts
  • Change file convention structure

You do not need to re-run codegen when you:

  • Change field options (label, required, default)
  • Modify hook logic
  • Update access control rules
  • Change runtime config values

The #questpie Alias

The #questpie import in collection/global files resolves to a generated helper that provides typed builders:

import { collection } from "#questpie";
// Provides field builder with autocompletion for relation targets, etc.

How Discovery Works

Codegen starts by scanning your project directories for files matching known conventions. Each entity category has a declared directory and file pattern:

CategoryDirectoryPatternKey Strategy
collectionscollections/*.tsFilename → camelCase
globalsglobals/*.tsFilename → camelCase
routesroutes/*.tsFilename → camelCase
jobsjobs/*.tsFilename → camelCase
servicesservices/*.tsFilename → camelCase
emailsemails/*.tsFilename → camelCase
seedsseeds/*.seed.tsFilename → camelCase
blocksblocks/*.tsFilename → camelCase

Files prefixed with _ (e.g., _helpers.ts) are ignored — use them for shared utilities within a category directory.

Discovery also excludes .d.ts and .d.mts files to avoid picking up stale declaration artifacts.

Plugin System

Codegen is extensible through plugins. Each plugin can contribute category declarations, modify generated output, or add entirely new generated files.

Core Plugins

PluginWhat It Does
coreCodegenPluginDiscovers collections, globals, routes, jobs, services, emails, seeds
adminPluginDiscovers admin views, blocks, components, fields, branding

Category Declarations

Plugins declare categories with a CategoryDeclaration object:

{
  name: "collections",
  dirs: ["collections"],
  prefix: "collection",
  registryKey: "collections",
  keyFromProperty: undefined, // Uses filename-based keys
  extensions: [".ts", ".mts"],
}

Key properties:

  • dirs: Which directories to scan
  • prefix: Used in generated variable names
  • registryKey: The key in the module registry (maps to AppCollections, etc.)
  • keyFromProperty: If set, reads the key from a property of the exported value (e.g., views use name property). Otherwise, derives the key from the filename.

Running Codegen

# One-time generation
bunx questpie generate

# Watch mode — re-generates on file changes
bunx questpie dev

Development Workflow

In development, use questpie dev which watches for file changes and re-runs codegen automatically:

bunx questpie dev

This watches for:

  • New, renamed, or deleted files in convention directories
  • Changes to modules.ts or questpie.config.ts
  • Plugin configuration changes

Field option changes, hook logic changes, and access control rule changes do not require re-running codegen.

On this page