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)
└── .gitignore

Configuration 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 GencowConfig in gencow.config.js to 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 how schema.ts and schema-auth.ts are wired — the same rules apply to gencow.config.js schema (and gencow dev / gencow db:generate use that list, not necessarily this file).

  • Default (import layout): schema.ts imports auth from ./schema-auth for FKs and defineRelations, but does not re-export auth tables. List both files in gencow.config (put schema-auth.ts first). For standalone npx drizzle-kit, "./gencow/schema.ts" alone is enough — auth tables are included transitively via imports.
  • Re-export layout: schema.ts re-exports auth (export { user, … } from "./schema-auth"). List only "./gencow/schema.ts". Listing schema-auth.ts as well registers the same pgTable definitions twice and causes duplicate table errors.
  • Missing auth tables: If schema-auth.ts is omitted from gencow.config schema while schema.ts only 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.ts or legacy generated/auth-schema.ts — auth should enter the graph only through gencow/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 with createCrud(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.tsdo 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 createCrud result, custom procedure, httpRoute, and cron schedule must appear under defineApi. The server does not auto-discover files in gencow/ — 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 PGlite

Cloud apps: Do not rely on .env in production. Set secrets on the deployed app with gencow env (below). You can use gencow env push to copy local .env values 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 cloud

Security: .env is gitignored. Cloud environment variables are encrypted at rest. Never expose secrets in frontend code.

Next Steps