Project Structure
Understand the frontend, gencow backend, config, and deploy files in a Gencow project
Overview
A Gencow project follows a simple convention: your backend lives in gencow/, and everything else (the recommended Vite + React frontend, configs) lives alongside it.
my-app/
├── gencow/ ← Backend code
│ ├── schema.ts ← Database tables (Drizzle ORM)
│ ├── schema-auth.ts ← Auth table boundary (re-export or fork)
│ ├── generated/ ← Server codegen (do not edit by hand)
│ │ ├── schema-auth.gen.ts ← Generated Better Auth tables (codegen)
│ │ └── db-schema.gen.ts ← Aggregated schema for typed ctx.db (codegen)
│ ├── auth.ts ← Auth configuration (defineAuth)
│ ├── runtime.ts ← Typed `procedure`, `httpRoute`, and `createCrud` bound to your schema + auth
│ ├── index.ts ← Registers procedures, routes, and cron schedules (`defineApi`)
│ ├── tasks.ts ← Your queries & mutations
│ ├── seed.ts ← Seed data (optional)
│ ├── README.md ← ⚡ Auto-generated AI guide
│ └── SECURITY.md ← Security checklist
├── .gencow/ ← Runtime data (gitignored)
│ ├── uploads/ ← File storage
│ ├── server.js ← Bundled server copy
│ └── dashboard/ ← Admin dashboard assets
├── migrations/ ← SQL migration files
├── src/ ← Vite + React frontend (recommended)
│ ├── main.tsx ← React entry point
│ ├── App.tsx ← App shell
│ └── gencow/ ← Generated frontend API client
├── gencow.config.js ← Project configuration
├── gencow.json ← Cloud app ID (created on deploy)
├── drizzle.config.ts ← Database config
├── package.json
├── tsconfig.json
├── .env ← Environment variables (local only)
└── .gitignoreConfiguration Files
gencow.config.js
The main project configuration file:
/** @type {import('@gencow/core').GencowConfig} */
export default {
/** Backend module root (`index.ts`, `schema.ts`, …). */
rootDir: "./gencow",
/** Drizzle schema entry files. */
schema: ["./gencow/schema-auth.ts", "./gencow/schema.ts"],
codegen: {
/** Client codegen: `api.ts`, `db.d.ts`, `operations.d.ts`. */
clientOutDir: "./src/gencow",
/**
* Server codegen: `db-schema.gen.ts`, `schema-auth.gen.ts`.
* Default: `{rootDir}/generated` (e.g. `./gencow/generated`).
*/
// serverOutDir: "./gencow/generated",
/** Experimental. Set `false` if you fully own auth schema files. */
authSchema: { emitRelations: true },
},
/** Local file upload directory. */
storage: "./.gencow/uploads",
/** Local dev database path (PGlite). */
db: { url: "./.gencow/data" },
/** Dev server port. */
port: 5456,
};The JSDoc @type annotation gives editors full autocomplete and inline docs without adding a runtime import. Because the file is plain JavaScript, you can use variables, process.env, or any other dynamic expression. The CLI loads gencow.config.js with a dynamic import(). Projects from gencow init ship export default together with "type": "module" in package.json. In a CommonJS-only package you can still use module.exports = { ... }; Node interop exposes that as the default export. Legacy gencow.config.ts files (using defineConfig({ ... }) from @gencow/core) are still loaded for backward compatibility, but support only literal values; migrate to gencow.config.js when you need dynamic configuration.
gencow codegen writes client artifacts to codegen.clientOutDir and server artifacts to codegen.serverOutDir (defaults to {rootDir}/generated). Better Auth tables from gencow/auth.ts are emitted to schema-auth.gen.ts; gencow/schema-auth.ts is your boundary file (re-export by default, fork when you need manual edits).
| Option | Default | Description |
|---|---|---|
rootDir |
"./gencow" |
Backend module root containing index.ts, schema.ts, and related modules |
functionsDir |
— | Deprecated. Alias for rootDir when rootDir is omitted |
schema |
["./gencow/schema-auth.ts", "./gencow/schema.ts"] |
Path to Drizzle schema files |
codegen.clientOutDir |
"./src/gencow" |
Client codegen: api.ts, db.d.ts, operations.d.ts |
codegen.outDir |
— | Deprecated. Same as clientOutDir; still accepted for backwards compatibility |
codegen.serverOutDir |
{rootDir}/generated |
Server codegen: db-schema.gen.ts, schema-auth.gen.ts |
codegen.authSchema |
{ emitRelations: true } |
Experimental. Emits {serverOutDir}/schema-auth.gen.ts; set false only if you fully own auth schema files |
storage |
"./.gencow/uploads" |
File storage directory |
db.url |
"./.gencow/data" |
Database data directory |
port |
5456 |
Dev server port |
Hover
GencowConfigingencow.config.jsto see supported keys, comments, and examples directly from@gencow/core.
gencow.json
Auto-created when you run gencow dev or gencow deploy. Contains your cloud app identity:
{
"appId": "null-mint-9625",
"displayName": "my-app",
"platformUrl": "https://gencow.app"
}Do not edit
appId— it's a unique identifier assigned by the platform. Changing it will create a new app on next deploy.
drizzle.config.ts
Database configuration for Drizzle Kit (migrations):
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "postgresql",
schema: ["./gencow/schema.ts", "./gencow/schema-auth.ts"],
out: "./migrations",
// Database connection
...(process.env.DATABASE_URL
? { dbCredentials: { url: process.env.DATABASE_URL } }
: {}),
});Note — which schema files to list: Drizzle walks the import graph from every path in
schema. The correct entries depend on howschema.tsandschema-auth.tsare wired — the same rules apply togencow.config.jsschema(andgencow dev/gencow db:generateuse that list, not necessarily this file).
- Default (import layout):
schema.tsimports auth from./schema-authfor FKs anddefineRelations, but does not re-export auth tables. List both files ingencow.config(putschema-auth.tsfirst). For standalonenpx drizzle-kit,"./gencow/schema.ts"alone is enough — auth tables are included transitively via imports.- Re-export layout:
schema.tsre-exports auth (export { user, … } from "./schema-auth"). List only"./gencow/schema.ts". Listingschema-auth.tsas well registers the samepgTabledefinitions twice and causes duplicate table errors.- Missing auth tables: If
schema-auth.tsis omitted fromgencow.configschemawhileschema.tsonly imports (no re-export), migrations may try to drop auth tables (user,session, …) and the Better Auth runtime adapter may fail to load.Do not list
gencow/generated/schema-auth.gen.tsor legacygenerated/auth-schema.ts— auth should enter the graph only throughgencow/schema-auth.ts.
The gencow/ Folder
schema.ts — Database Tables
Define your tables using Drizzle's pgTable with ownerRls() for automatic Row-Level Security:
import { pgTable, serial, text, boolean, timestamp } from "drizzle-orm/pg-core";
import { ownerRls } from "@gencow/core";
import { user } from "./schema-auth";
export const tasks = pgTable("tasks", {
id: serial("id").primaryKey(),
title: text("title").notNull(),
done: boolean("done").default(false).notNull(),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
createdAt: timestamp("created_at").defaultNow().notNull(),
}, (t) => ownerRls(t.userId));Note:
ownerRls()generates PostgreSQL RLS policies for automatic per-user data isolation. Combined withcreateCrud(tasks), all CRUD operations enforce authentication and ownership — no manual filtering needed.
gencow/schema-auth.ts — Auth Tables
Scaffolded by gencow init as a thin boundary over codegen. By default it only re-exports gencow/generated/schema-auth.gen.ts — do not edit the re-export; run gencow codegen after changing gencow/auth.ts so the generated file stays in sync.
A minimal email/password setup typically includes user, session, account, and verification, but the exact tables and columns are not fixed — enabling OAuth, organizations, two-factor, or other plugins can add more tables and fields.
To customize auth tables manually (for example RLS policies or extra Drizzle-only columns), copy gencow/generated/schema-auth.gen.ts into gencow/schema-auth.ts, edit in place, and remove the re-export line. Codegen never overwrites schema-auth.ts once it exists.
gencow/generated/db-schema.gen.ts — Typed Schema Map
Auto-generated aggregate of your Drizzle schema files (including auth tables). Used by gencow/runtime.ts for typed ctx.db. Do not edit manually.
auth.ts — Auth Configuration
You own this file. Changes here drive schema-auth.gen.ts on the next gencow codegen — extra user columns, OAuth providers, and Better Auth plugins all affect the generated tables.
import { defineAuth } from "@gencow/core";
export default defineAuth({
user: {
/** App-owned columns on `user` — types, defaults, and signup `input` flags. */
additionalFields: {
role: { type: "text", default: "user" },
plan: { type: "text", default: "free", input: true },
},
},
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
},
// oauth: {
// callbackURL: "https://your-frontend.com/auth/callback",
// allowedCallbackURLs: ["http://localhost:5173/auth/callback"],
// },
// emailVerification: {
// sendVerificationEmail: async ({ user, url }) => { /* send mail */ },
// },
});When your browser frontend is hosted outside the Gencow app domain, declare that exact origin in gencow.config.js so CORS, Better Auth trusted origins, and OAuth callback validation share the same allowlist:
export default {
frontendOrigins: ["https://your-frontend.com"],
};Run gencow codegen (or gencow dev) after editing auth settings so generated auth tables stay in sync. For Google SSO setup, see the Authentication guide or gencow add sso.
index.ts — API Entry Point
Critical file — the server loads this module and registers only what you list in defineApi. Import CRUD definitions, procedures, routes, and cron schedules from feature modules (for example ./tasks) and register them here:
import "./runtime";
import { cron, defineApi } from "@gencow/core";
import { tasksCrud, searchTasks, cleanupTasks } from "./tasks";
const cleanupTasksCron = cron.name("cleanupTasks").daily({ hour: 3 }).handler(cleanupTasks);
export default defineApi({
crud: {
tasks: tasksCrud,
},
procedures: {
searchTasks,
cleanupTasks,
},
crons: { cleanupTasksCron },
});Pattern: Every
createCrudresult, custom procedure,httpRoute, and cron schedule must appear underdefineApi. The server does not auto-discover files ingencow/— if it is not imported and registered here, it is not exposed or scheduled.
Cron Jobs
Define scheduled work as procedure.internal handlers, then reference those definitions from independent cron defs in defineApi({ crons }):
import { cron, defineApi } from "@gencow/core";
import { procedure } from "./runtime";
export const syncData = procedure.internal
.name("data.sync")
.handler(async ({ context }) => {
// scheduled server-only work
});
export const syncDataCron = cron.name("syncData").interval({ minutes: 30 }).handler(syncData);
export default defineApi({
procedures: { syncData },
crons: { syncDataCron },
});Legacy gencow/crons.ts and cronJobs() definitions are still supported for existing projects, but new projects should keep cron registration in the API entry point with cron.
seed.ts — Seed Data (Optional)
Populate the database with test data:
import { tasks } from "./schema";
export default async function seed(ctx) {
await ctx.db.insert(tasks).values([
{ title: "First task", userId: "test-user" },
]);
}Run with gencow db:seed.
src/gencow/api.ts — Auto-Generated Client (⚡ Do Not Edit)
Generated by gencow dev / gencow codegen into codegen.clientOutDir (default ./src/gencow). Provides type-safe API references for your React frontend:
// src/gencow/api.ts — generated; do not edit manually
import { defineProcedureQuery, defineProcedureMutation } from "@gencow/client";
import type * as Operations from "./operations.d.ts";
export const api = {
tasks: {
list: defineProcedureQuery<Operations.TasksListOutput>("tasks.list"),
get: defineProcedureQuery<Operations.TasksGetOutput>("tasks.get"),
create: defineProcedureMutation<Operations.TasksCreateOutput>("tasks.create"),
update: defineProcedureMutation<Operations.TasksUpdateOutput>("tasks.update"),
remove: defineProcedureMutation<Operations.TasksRemoveOutput>("tasks.remove"),
},
} as const;Companion files in the same folder: db.d.ts, operations.d.ts.
gencow/README.md — Auto-Generated AI Guide (⚡ Do Not Edit)
Generated alongside client codegen. Contains a comprehensive vibe-coding guide that you can paste into AI assistants. Includes API reference, React hook usage, auth setup, cron patterns, and deployment commands.
The .gencow/ Folder (Gitignored)
| Path | Purpose |
|---|---|
.gencow/uploads/ |
Uploaded files (via ctx.storage.store()) |
.gencow/server.js |
Bundled server copy (for standalone projects) |
.gencow/dashboard/ |
Admin dashboard static assets |
Environment Variables
.env (local development)
The .env file at your project root is for local development only and is gitignored. gencow dev loads it automatically on start.
# Frontend → local API (gencow dev sets this if missing)
VITE_API_URL=http://localhost:5456
# Secrets for local dev:
OPENAI_API_KEY=sk-your-key-here
DATABASE_URL=postgres://user:pass@host/db # optional — omit to use PGliteCloud apps: Do not rely on
.envin production. Set secrets on the deployed app withgencow env(below). You can usegencow env pushto copy local.envvalues to the cloud when bootstrapping.
Cloud environment (gencow env)
Manage environment variables for your deployed app via CLI:
gencow env set OPENAI_API_KEY=sk-... # → cloud
gencow env set DATABASE_URL=postgres://... # → cloud
gencow env list # → cloud env vars
gencow env push # Push local .env vars to cloudSecurity:
.envis gitignored. Cloud environment variables are encrypted at rest. Never expose secrets in frontend code.
Next Steps
- Schema Guide — Advanced table patterns
- Queries & Mutations — Read and write data
- Authentication — Auth setup in detail