Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.refactkit.com/llms.txt

Use this file to discover all available pages before exploring further.

RefactKit uses Drizzle ORM with a PostgreSQL database hosted on Supabase. All table definitions live in a single file — db/schema.ts — which acts as the single source of truth for your entire data model. Drizzle generates fully-typed query builders from this schema, so TypeScript catches invalid field references at compile time rather than at runtime.

Schema file overview

Open db/schema.ts to see the existing tables. The core schema ships with the tables required by Better Auth: user, session, account, verification, organization, member, and invitation. The galleryImage table is an example of a product-specific table:
export const galleryImage = pgTable(
  'gallery_image',
  {
    id: text('id').primaryKey(),
    name: text('name').notNull(),
    url: text('url').notNull(),
    size: text('size').default('0').notNull(),
    organizationId: text('organization_id')
      .notNull()
      .references(() => organization.id, { onDelete: 'cascade' }),
    createdAt: timestamp('created_at').defaultNow().notNull(),
  },
  (table) => [index('gallery_image_organizationId_idx').on(table.organizationId)],
)

Add a new table

1

Define the table in db/schema.ts

Import the column builders you need and call pgTable. Always include organizationId to scope data to a tenant:
import { pgTable, text, timestamp, index } from 'drizzle-orm/pg-core'
import { organization } from './schema'

export const myTable = pgTable('my_table', {
  id: text('id').primaryKey(),
  name: text('name').notNull(),
  organizationId: text('organization_id')
    .notNull()
    .references(() => organization.id, { onDelete: 'cascade' }),
  createdAt: timestamp('created_at').defaultNow().notNull(),
})
2

Add a relation (optional)

Define Drizzle relations to enable typed joins:
import { relations } from 'drizzle-orm'

export const myTableRelations = relations(myTable, ({ one }) => ({
  organization: one(organization, {
    fields: [myTable.organizationId],
    references: [organization.id],
  }),
}))
3

Push the schema to your database

Run this command to synchronize your schema file with the live database. Drizzle inspects the current database structure, diffs it against db/schema.ts, and applies only the changes needed:
npx drizzle-kit push
If you update the better-auth package, always check their changelog before running npx drizzle-kit push. Better Auth updates often add new columns to the core authentication tables. You must manually verify db/schema.ts matches their required schema before pushing.

Always include organizationId for tenant isolation

Every table that stores product data must include an organizationId foreign key. This is the cornerstone of RefactKit’s multi-tenancy model — it guarantees that no query can accidentally return data belonging to a different organization.
Add a database index on organizationId for every table. RefactKit’s pattern is to pass an (table) => [index('my_table_organizationId_idx').on(table.organizationId)] argument as the third parameter to pgTable. Without this index, queries that filter by organization will perform a full table scan as your data grows.

Explore data with Drizzle Studio

Drizzle Studio is a local browser-based UI for browsing and editing your database records directly:
npx drizzle-kit studio
This opens a web interface at https://local.drizzle.studio where you can inspect tables, run queries, and edit rows — useful during development without needing a separate database GUI tool.

Query data in server functions

Backend logic in RefactKit lives in server functions created with createServerFn from @tanstack/react-start. These run exclusively on the Nitro server and have direct access to the db instance. Use Zod to validate inputs before touching the database:
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
import { db } from '@/db'
import { myTable } from '@/db/schema'

export const createItem = createServerFn({ method: 'POST' })
  .validator((data: unknown) =>
    z.object({ name: z.string().min(1) }).parse(data)
  )
  .handler(async ({ data }) => {
    const newItem = await db
      .insert(myTable)
      .values({ name: data.name })
      .returning()
    return { item: newItem[0] }
  })
For read queries, scope every query to the current organization. Never return rows without filtering on organizationId:
import { eq } from 'drizzle-orm'

export const getItems = createServerFn({ method: 'GET' })
  .validator((data: unknown) =>
    z.object({ organizationId: z.string() }).parse(data)
  )
  .handler(async ({ data }) => {
    const items = await db
      .select()
      .from(myTable)
      .where(eq(myTable.organizationId, data.organizationId))
    return { items }
  })

Drizzle config reference

The drizzle.config.ts at the project root tells Drizzle where to find your schema and which database to connect to:
import 'dotenv/config'
import { defineConfig } from 'drizzle-kit'

export default defineConfig({
  out: './drizzle',
  schema: './db/schema.ts',
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL as string,
  },
})
Set DATABASE_URL in your .env file to your Supabase connection string with pooling enabled (port 6543).

Available column types

Drizzle supports the full PostgreSQL type system. The most common types used in this codebase are:
import { text, boolean } from 'drizzle-orm/pg-core'

text('column_name')
text('column_name').notNull()
text('column_name').default('')
boolean('is_active').default(false).notNull()