Code Generation
FORGE's primary job is to turn a declarative configuration file into a fully working application. The code generation pipeline transforms Tera templates into production-ready source code for your chosen backend and frontend frameworks.
Generation Pipeline
Every code generation operation follows the same five-step pipeline:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 1. Command │────▶│ 2. Config │────▶│ 3. Context │
│ (CLI input) │ │ (forge.yaml)│ │ (variables) │
└──────────────┘ └──────────────┘ └──────────────┘
│
▼
┌──────────────┐ ┌──────────────┐
│ 5. Output │◀────│ 4. Render │
│ (files) │ │ (Tera engine)│
└──────────────┘ └──────────────┘- Command -- The user runs a CLI command (
forge new,forge make:model, etc.) - Config -- FORGE reads
forge.yamlto understand the project structure, enabled features, languages, and providers - Context -- A context object is built containing all template variables (app name, auth method, languages, etc.)
- Render -- The Tera engine processes
.teratemplates, evaluating conditionals and substituting variables - Output -- Generated files are written to the project directory with the
.teraextension stripped
Creating a New Project
The forge new command scaffolds an entire application from scratch:
forge new --name=myapp --backend=rust --frontend=nextjsThis single command generates:
myapp/
├── forge.yaml # Project configuration
├── forge.lock # Version tracking
├── .env.example # Environment template
├── .gitignore
│
├── apps/
│ ├── api/ # Rust + Axum backend
│ │ ├── Cargo.toml
│ │ ├── src/
│ │ │ ├── main.rs
│ │ │ ├── config/
│ │ │ ├── models/
│ │ │ ├── handlers/
│ │ │ ├── middleware/
│ │ │ ├── routes/
│ │ │ ├── services/
│ │ │ ├── dto/
│ │ │ ├── providers/
│ │ │ ├── error/
│ │ │ └── utils/
│ │ ├── migrations/
│ │ └── tests/
│ │
│ ├── web/ # Next.js public web app
│ │ ├── package.json
│ │ ├── next.config.js
│ │ ├── app/
│ │ ├── components/
│ │ └── lib/
│ │
│ └── admin/ # Next.js admin dashboard
│ ├── package.json
│ ├── app/
│ └── components/
│
└── infra/
├── docker/
│ ├── docker-compose.yml
│ └── docker-compose.dev.yml
└── caddy/
└── CaddyfileTIP
After generation, FORGE runs post-generation hooks automatically. For a Rust backend, this includes cargo check to verify the generated code compiles. For Next.js frontends, it runs npm install to set up dependencies.
Interactive Mode
Running forge new without flags launches an interactive wizard:
$ forge new
Welcome to FORGE Application Factory!
? Project name: myapp
? Display name: My Application
? Backend framework: (Use arrow keys)
❯ Rust (Axum) - High performance, type-safe
Laravel (PHP) - Rapid development, rich ecosystem
FastAPI (Python) - Modern, fast Python API
Node.js (Express) - JavaScript everywhere
? Frontend framework: (Use arrow keys)
❯ Next.js - React with SSR
Nuxt.js - Vue with SSR
Angular - Enterprise TypeScript
Vanilla - TypeScript + Vite
? Authentication method:
❯ Email + Password
Mobile + OTP
Both
? Default language: English (en)
? Additional languages: Arabic (ar)
? SMS provider: Twilio
? Email provider: SMTP
? Storage provider: Local filesystem
Creating myapp...
✓ Generated backend (Rust + Axum)
✓ Generated frontend (Next.js)
✓ Generated admin panel (Next.js)
✓ Generated infrastructure configs
✓ Created 17 database migrations
✓ Seeded default permissions and roles
✓ Initialized git repository
Your application is ready at ./myapp
Run `cd myapp && forge serve` to start developing.The make Commands
While forge new creates an entire project, the make commands generate individual components within an existing project. These are the workhorses of day-to-day development.
forge make:model
Generates a database model with its associated files. You can pass inline field definitions using name:type syntax to generate typed columns, Rust struct fields, and admin form inputs in one step.
# Without fields (empty struct, empty migration)
forge make:model Product
# With inline fields
forge make:model Product -m title:string price:decimal is_active:booleanThis creates:
| File | Purpose |
|---|---|
src/models/product.rs | Model struct with typed fields |
migrations/00018_create_products_table.sql | Database migration with columns |
forge make:model --all
The --all flag generates the complete resource stack — model, migration, handler, and admin CRUD pages:
forge make:model Product --all title:string price:decimal status:lookup category:belongs_toThis creates everything needed for full CRUD operations:
| File | Purpose |
|---|---|
src/models/product.rs | Model struct with typed fields |
src/dto/product.rs | Request and response types |
src/handlers/admin/products.rs | Admin CRUD handlers |
src/handlers/products.rs | Public handlers (if applicable) |
src/services/product.rs | Business logic service |
src/routes/admin.rs | Updated with product routes |
migrations/00018_create_products_table.sql | Migration with typed columns and FKs |
seeders/products.sql | Default seed data |
admin/app/(dashboard)/products/page.tsx | Admin list page |
admin/app/(dashboard)/products/create/page.tsx | Admin create form (with inputs per field) |
admin/app/(dashboard)/products/[id]/edit/page.tsx | Admin edit form |
WARNING
The --all flag also registers permissions (products.view, products.create, products.edit, products.delete) and adds the resource to the admin sidebar. Always review the generated code before committing.
forge make:migration
Generates a database migration file. Like make:model, you can pass inline fields to generate typed columns:
# Empty migration
forge make:migration add_price_to_products
# Migration with typed columns
forge make:migration create_products_table title:string price:decimal status:lookupCreates:
migrations/00019_add_price_to_products.sqlThe migration number is automatically incremented based on existing migrations.
forge make:handler
Generates an API handler:
forge make:handler ProductsCreates:
| File | Purpose |
|---|---|
src/handlers/products.rs | Handler functions |
src/handlers/admin/products.rs | Admin handler functions |
forge make:dto
Generates Data Transfer Object types:
forge make:dto ProductCreates:
| File | Purpose |
|---|---|
src/dto/product.rs | CreateProductRequest, UpdateProductRequest, ProductResponse |
forge make:service
Generates a business logic service:
forge make:service ProductCreates:
| File | Purpose |
|---|---|
src/services/product.rs | Service with CRUD methods |
forge make:seeder
Generates a database seeder:
forge make:seeder ProductsCreates:
| File | Purpose |
|---|---|
seeders/products.sql | INSERT statements for default data |
forge make:page
Generates frontend pages:
forge make:page Products --adminCreates:
| File | Purpose |
|---|---|
admin/app/(dashboard)/products/page.tsx | List page with data table |
admin/app/(dashboard)/products/columns.tsx | Table column definitions |
admin/app/(dashboard)/products/create/page.tsx | Create form |
admin/app/(dashboard)/products/[id]/edit/page.tsx | Edit form |
Field Definitions
The make:model and make:migration commands accept inline field definitions using name:type syntax. Each field is parsed into a fully-resolved definition that flows through every generated file — from SQL columns to Rust struct fields to admin form inputs.
Inline Syntax
Pass fields as positional arguments after the model or migration name:
forge make:model Product --all title:string price:decimal is_active:boolean status:lookupEach field follows the pattern:
<field_name>:<field_type>Type Reference
| Type | SQL | Rust | TypeScript | Form |
|---|---|---|---|---|
string | VARCHAR(255) | String | string | Text input |
text | TEXT | String | string | Textarea |
integer | INTEGER | i32 | number | Number input |
bigint | BIGINT | i64 | number | Number input |
decimal | DECIMAL(10,2) | BigDecimal | number | Number input |
boolean | BOOLEAN | bool | boolean | Switch toggle |
date | DATE | NaiveDate | string | Date picker |
datetime | TIMESTAMPTZ | DateTime<Utc> | string | Datetime picker |
uuid | UUID | Uuid | string | Text input |
json | JSONB | serde_json::Value | Record<string, unknown> | Textarea |
lookup | UUID → lookups | Uuid | string | Select dropdown |
belongs_to | UUID → related table | Uuid | string | Select dropdown |
Lookup Fields
A lookup field creates a foreign key to the centralized lookups table. The field name becomes the parent slug used to filter lookup values:
forge make:model Product --all status:lookup priority:lookupThis generates:
status_id UUID REFERENCES lookups(id)— filtered by slug"status"priority_id UUID REFERENCES lookups(id)— filtered by slug"priority"
BelongsTo Fields
A belongs_to field creates a foreign key to another model's table, derived from the field name:
forge make:model Product --all category:belongs_to user:belongs_toThis generates:
category_id UUID REFERENCES categories(id)user_id UUID REFERENCES users(id)
Interactive Mode
If you prefer not to type fields inline, use --interactive (-i) to launch a guided builder:
forge make:model Product --all --interactiveThe builder prompts for each field's name and type, validates input, and shows the resolved SQL type as confirmation.
Generated Output Example
Given this command:
forge make:model Article --all title:string body:text is_published:boolean author:belongs_toThe generated migration includes:
CREATE TABLE articles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title VARCHAR(255) NOT NULL,
body TEXT NOT NULL,
is_published BOOLEAN NOT NULL DEFAULT false,
author_id UUID REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);The generated Rust model includes:
pub struct Article {
pub id: Uuid,
pub title: String,
pub body: String,
pub is_published: bool,
pub author_id: Option<Uuid>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}And the admin create form renders appropriate inputs for each field type — text inputs for strings, a textarea for the body, a switch for the boolean, and a select dropdown for the author relation.
Command Reference
Here is the complete list of make commands and what each generates:
forge make:model <Name> Model file
forge make:model <Name> --all Full resource stack (model + all below)
forge make:migration <name> Database migration
forge make:handler <Name> API handler(s)
forge make:dto <Name> Request/response types
forge make:service <Name> Business logic service
forge make:seeder <Name> Database seed file
forge make:page <Name> Frontend page(s)
forge make:page <Name> --admin Admin panel pages
forge make:page <Name> --web Public web pagesTIP
Use forge make:model Product --all for new resources. It generates everything in one command and ensures all pieces are properly wired together -- routes registered, sidebar updated, permissions seeded.
How Context Variables Flow
When a make command runs, FORGE builds a context from multiple sources:
┌──────────────┐
│ forge.yaml │──→ app.name, auth.method, languages, providers
└──────────────┘
+
┌──────────────┐
│ CLI flags │──→ model name, --all, --admin, --web
└──────────────┘
+
┌──────────────┐
│ CLI fields │──→ fields[], has_fields, lookup_fields, relation_fields
│ (name:type) │
└──────────────┘
+
┌──────────────┐
│ Derived │──→ table name (pluralized, snake_case),
│ values │ struct name (PascalCase), route path (kebab-case),
└──────────────┘ next migration number, timestamps
│
▼
┌──────────────┐
│ Template │──→ Full context passed to Tera engine
│ Context │
└──────────────┘For example, forge make:model ProductCategory --all title:string status:lookup produces this context:
model:
name: "ProductCategory" # PascalCase
snake: "product_category" # snake_case
kebab: "product-category" # kebab-case
plural: "product_categories" # Pluralized
table: "product_categories" # Database table name
# From forge.yaml
app_name: "myapp"
auth_method: "email_password"
languages: [{ code: "en", ... }, { code: "ar", ... }]
# From CLI field definitions
has_fields: true
fields:
- name: "title"
field_type: "string"
column_name: "title"
sql_type: "VARCHAR(255)"
rust_type: "String"
ts_type: "string"
form_component: "Input"
zod_schema: "z.string().min(1)"
is_relation: false
- name: "status"
field_type: "lookup"
column_name: "status_id"
sql_type: "UUID"
rust_type: "Uuid"
ts_type: "string"
form_component: "Select"
is_relation: true
is_lookup: true
lookup_slug: "status"
foreign_table: "lookups"Generation vs. Runtime
It is important to understand that FORGE operates at generation time, not runtime. Once code is generated, FORGE's job is done. The generated application runs independently without any dependency on the FORGE CLI.
Generation Time (forge) Runtime (your app)
───────────────────── ──────────────────
forge.yaml + templates → Source code
forge make:model Product → product.rs, migration, handler, etc.
forge serve → Starts dev servers (convenience)
The generated code has no Your app runs with standard
dependency on FORGE at tooling: cargo, npm, docker
runtime.WARNING
If you modify generated files and then re-run a make command that targets the same file, FORGE will warn you about the conflict. It will not silently overwrite your changes.
Post-Generation Hooks
After generating files, FORGE can run verification commands defined in the backend or frontend manifest:
# manifest.yaml
hooks:
post_generate:
- "cd apps/api && cargo check"
- "cd apps/web && npm run lint"
- "cd apps/admin && npm run lint"These hooks catch generation errors immediately. If cargo check fails, FORGE reports the error so you can investigate before writing any custom code on top of a broken foundation.