Core Concepts · Reference

Generated File Map

Exactly what grit generate resource writes, for every app in your project — the definitive index of paths, what each file is, and which are yours to edit versus regenerated. Filenames use the resource name (singular for Go, kebab-plural for routes/hooks).

One command, the whole stack

From a single field spec, Grit fans out a working, end-to-end feature. Here are the four files at the heart of it — switch tabs to see the same resource across Go and TypeScript:

grit generateGo backendSharedFrontendfans outresource Product--fields …models/product.goGORM structhandlers/product.goCRUD + importservices/product.gobusiness logicschemas/product.tsZodtypes/product.tsTS interfaceuse-products.tsReact Queryresources/products.tsadmin defproducts/page.tsxCRUD UI
One commandGo backendShared (Zod/TS)Frontend + admin
One command writes a working feature across every layer that exists in your project
type Product struct {
ID string `gorm:"primaryKey;size:36" json:"id"`
Name string `gorm:"size:255;not null" json:"name" binding:"required"`
Price float64 `gorm:"not null" json:"price"`
CategoryID string `gorm:"size:36;not null;index" json:"category_id"`
Category Category `gorm:"foreignKey:CategoryID" json:"category,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
func (p *Product) BeforeCreate(tx *gorm.DB) error {
if p.ID == "" {
p.ID = uuid.New().String()
}
return nil
}

Excerpts, abbreviated for clarity — the real files are complete and runnable.

Backend + shared (always)

These are written for every architecture that has an API and a shared package.

PathWhat it is
apps/api/internal/models/<name>.goGORM model (UUID PK, BeforeCreate, relations)
apps/api/internal/services/<name>.goService layer — your custom business logic goes here
apps/api/internal/handlers/<name>.goGin CRUD handler (list/get/create/update/delete)
apps/api/internal/handlers/<name>_import.goCSV/Excel bulk-import handler
packages/shared/schemas/<name>.tsZod Create/Update schemas
packages/shared/types/<name>.tsTypeScript types (regenerated by grit sync)

Web app (if present)

PathWhat it is
apps/web/hooks/use-<plural>.tsReact Query hooks (Next.js) — src/hooks/ for the Vite kit

Admin panel (if present)

PathWhat it is
apps/admin/resources/<name>.tsResource definition (table + form + widgets) — src/resources/ on Vite
apps/admin/app/(dashboard)/resources/<plural>/page.tsxAdmin CRUD page — src/routes/_dashboard/resources/ on Vite

Mobile — Expo (if present)

PathWhat it is
apps/expo/hooks/use-<plural>.tsTyped React Query hook
apps/expo/app/<plural>/index.tsxList screen
apps/expo/app/<plural>/[id].tsxDetail screen
apps/expo/app/<plural>/new.tsxCreate screen
apps/expo/app/<plural>/edit/[id].tsxEdit screen
apps/expo/components/resource-forms/<plural>-form.tsxShared form component

Desktop — Wails (if present)

PathWhat it is
apps/desktop/frontend/src/hooks/use-<plural>.tsReact Query hook (calls Wails bindings)
apps/desktop/frontend/src/routes/_app/<plural>.index.tsxList route
apps/desktop/frontend/src/routes/_app/<plural>.new.tsxCreate route (+ detail / edit routes)

Files it edits (marker injection)

Rather than overwrite shared files, Grit injects at // grit:* markers. Keep the markers in place — they're how generation and seeding stay idempotent.

Routes are the exception, and used to be the worst case: four edits per resource, hundreds of lines apart, in a routes.go that passed a thousand lines with a handful of resources. Each resource now gets its own internal/routes/<resource>_routes.go holding the handler and every path that reaches it, and routes.go no longer changes when you add or remove one.

PathWhat it is
apps/api/internal/models/user.goModel registered in Models() at // grit:models
packages/shared/schemas/index.tsSchema re-exported at // grit:schemas
apps/admin/resources/index.tsResource registered at // grit:resources
Adding a route to a resourceOpen internal/routes/<resource>_routes.go and add a line. The file registers itself from an init(), so creating one mounts a resource and deleting one unmounts it; nothing else refers to it by name. m.Public is outside the auth middleware and behind an API key, m.Protected takes a JWT or an API key, and m.Admin also requires the ADMIN role. Mount is defined in internal/routes/resources.go.
Yours vs regeneratedEverything above is your code once generated — edit models, add service methods, restyle screens freely. Put custom logic in the service layer, not in packages/shared/types/*, which grit sync rewrites from your Go structs every time it runs.
Adding a field to a resource you have already editedRe-running grit generate resource writes these files again from scratch. If you have edited them, Grit compares each one against what it last wrote, stops before touching anything, and names the files at risk. Nothing is lost, but nothing is generated either.

To add a field without that, add it in place with grit generate field <Name> <name:type>, then grit migrate. It reaches the model, the handler's request structs and PATCH allow-list, the CSV importer, the types, the Zod schemas and the admin, and leaves the rest of each file as you had it. It covers scalar, select and toggle fields.

Adding the field to the model by hand and running grit sync is not enough on its own. Sync updates the types, the schemas and the admin, but the API accepts only what the handler's request structs name, so the field is shown, submitted, and dropped.

--force regenerates anyway, overwriting your edits. Commit first: there is no backup.
Escape hatchNeed to pull a resource back out? grit generate remove resource <Name> deletes exactly these files and unwinds the marker injections — a clean inverse of generation.