QUESTPIE
Start Here

Project Structure

File convention layout, discovery rules, and generated output.

QUESTPIE uses a file convention — your project structure determines what gets discovered and wired into the runtime. No manual registration required.

Standard Layout

Mermaid

Discovery Rules

Codegen discovers files based on directory name and export pattern:

DirectoryExportKey derivationExample
collections/Default or namedFactory string argcollection("blog-posts")blogPosts
globals/Default or namedFactory string argglobal("siteSettings")siteSettings
routes/DefaultFilename → camelCase/slash pathcreate-booking.tscreateBooking
jobs/DefaultFilename → camelCasesend-email.tssendEmail
routes/ (raw)DefaultFilename → slash pathwebhook.tswebhook
services/DefaultFilename → camelCaseblog.tsblog
blocks/Named exportsFactory string arg → export nameblock("hero")hero
emails/DefaultFilename → camelCaseappointment-confirmation.tsappointmentConfirmation

Factory string args are the runtime identity. Hyphens are camelized ("blog-posts"blogPosts), while underscores are preserved ("site_settings"site_settings).

Single-file conventions

Some configs are single files instead of directories:

FilePurpose
questpie.config.tsRuntime config (DB, adapters, secrets)
modules.tsModule dependencies array
config/auth.tsAuth config via authConfig() (Better Auth options)
config/app.tsApp config via appConfig() (locale, access, hooks)
config/admin.tsAdmin config via adminConfig() (sidebar, dashboard, branding, UI locale)
config/openapi.tsOpenAPI config via openApiConfig() (spec info, Scalar)

Nested routes

Routes support nested directories for namespacing:

Mermaid

The .generated/ Directory

Running bunx questpie generate creates .generated/ with:

index.ts — App instance and types

// Auto-generated — do not edit
import type { AppCollections, AppGlobals, AppRoutes, AppJobs } from "./module";

export type AppConfig = {
	collections: AppCollections;
	globals: AppGlobals;
	routes: AppRoutes;
	// ... auth, locales
};

export { app, createContext } from "./app";
export type { AppConfig, AppCollections, AppGlobals, AppRoutes, AppJobs };

Module augmentation

The generated code 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;
			// ... custom services
		}
	}
}

This is why collections, queue, email are available and typed in every handler — codegen generates the augmentation from your file structure.

The #questpie Imports

QUESTPIE uses Node.js subpath imports (the "imports" field in package.json) to wire your code to the generated output:

ImportResolves toUse for
#questpie.generated/index.tsapp instance, AppConfig type
#questpie/factories.generated/factories.tscollection(), global(), sidebar(), etc.

Collection, global, and admin singleton files import from #questpie/factories:

import { collection } from "#questpie/factories";

The generated factories file provides typed builders with all plugin extensions (e.g. .admin(), .form(), .list()) and the merged field set (builtins + module-contributed fields like richText, blocks).

Routes, jobs, and seeds import from the bare questpie package:

import { route } from "questpie/services";
import { job } from "questpie/services";
import { seed } from "questpie/services";

By-Feature Layout

For larger projects, you can organize by feature instead of by type:

Mermaid

Both layouts can coexist. Codegen scans all configured paths.

What's Next

On this page