Skip to content

Schema Overview

The Booking Kit’s database schema consists of 26 tables managed by Drizzle ORM with full TypeScript type inference.

TableDescription
providersService providers (doctors, stylists, consultants). Includes metadata JSONB for settings.
event_typesBookable services. Duration, price, questions, limits, confirmation mode.
availability_rulesRecurring schedules using RRULE. Linked to providers.
availability_overridesDate-specific schedule modifications (block/extend hours).
bookingsCustomer appointments. starts_at/ends_at in UTC with EXCLUDE USING gist. Includes optional resource_id FK.
booking_eventsAppend-only audit trail for every booking status change.
out_of_officeExtended unavailability periods (vacations, leave).
TableDescription
resourcesBookable physical/virtual units (restaurant tables, hotel rooms, coworking desks, tennis courts). capacity in units, type for filtering.
resource_availability_rulesRecurring schedules for resources using RRULE (mirrors availability_rules).
resource_availability_overridesDate-specific resource schedule modifications (mirrors availability_overrides).
TableDescription
teamsGroups of providers with assignment strategy.
team_membersProvider membership in teams with role.
team_event_typesEvent types associated with teams.
TableDescription
paymentsPayment records linked to bookings. Amount, status, Stripe IDs.
TableDescription
workflowsWorkflow definitions (trigger, conditions, actions).
workflow_logsExecution history for workflows.
webhook_subscriptionsRegistered webhook endpoints with triggers and secrets.
webhook_deliveriesDelivery attempts with response status and retry count.
TableDescription
recurring_bookingsRecurring booking series metadata (frequency, count).
booking_seatsIndividual attendees for group/seat bookings.
TableDescription
email_delivery_logLog of email delivery attempts with status.
customer_preferencesCustomer email preferences and opt-out status.
TableDescription
walk_in_queueWalk-in queue entries with status and wait time estimates.
TableDescription
organizationsTenant organizations with branding and settings.
organization_membersUser membership in organizations with roles.

All tables include:

ColumnTypeDescription
iduuidPrimary key (auto-generated)
created_attimestampRow creation time
updated_attimestampLast modification (auto-updated via trigger)

Most tables include an optional organization_id column for multi-tenancy scoping.

EXCLUDE USING gist (
provider_id WITH =,
tstzrange(starts_at, ends_at) WITH &&
) WHERE (status NOT IN ('cancelled', 'rejected', 'rescheduled'))

This constraint prevents overlapping bookings for the same provider, except for cancelled, rejected, and rescheduled bookings. Requires the btree_gist extension.

EnumValues
booking_statuspending, confirmed, completed, cancelled, rescheduled, no_show, rejected
payment_statuspending, authorized, succeeded, failed, refunded, partially_refunded, released
payment_typeprepayment, no_show_hold, cancellation_fee
assignment_strategyround_robin, collective, managed, fixed
recurring_frequencyweekly, biweekly, monthly
seat_statusconfirmed, cancelled
workflow_triggerbooking_created, booking_confirmed, booking_cancelled, booking_rescheduled, before_event, after_event, payment_received, payment_failed, no_show_confirmed, form_submitted
booking_sourceonline, walk_in, phone, admin
walk_in_statusqueued, in_service, completed, no_show, cancelled

Insert a booking:

import { db } from "@/db";
import { bookings } from "@thebookingkit/db";
import { v4 as uuid } from "uuid";
const newBooking = await db
.insert(bookings)
.values({
id: uuid(),
providerId: "provider-uuid",
startsAt: new Date("2026-03-10T14:00:00.000Z"),
endsAt: new Date("2026-03-10T14:30:00.000Z"),
customerEmail: "customer@example.com",
customerName: "John Doe",
customerPhone: "+1-555-0123",
status: "pending",
createdAt: new Date(),
updatedAt: new Date(),
})
.returning();
console.log("Booking created:", newBooking[0].id);

Query bookings with related data:

import { db } from "@/db";
import { bookings } from "@thebookingkit/db";
import { eq } from "drizzle-orm";
const booking = await db.query.bookings.findFirst({
where: eq(bookings.id, "booking-uuid"),
with: {
provider: true,
eventType: true,
payments: true,
bookingEvents: true,
},
});

Find available providers with their schedules:

import { db } from "@/db";
import { providers, availabilityRules } from "@thebookingkit/db";
import { eq } from "drizzle-orm";
const provider = await db.query.providers.findFirst({
where: eq(providers.userId, "user-123"),
with: {
availabilityRules: true,
availabilityOverrides: true,
bookings: {
where: eq(bookings.status, "confirmed"),
},
},
});

Create an availability rule:

import { db } from "@/db";
import { availabilityRules } from "@thebookingkit/db";
import { v4 as uuid } from "uuid";
const rule = await db
.insert(availabilityRules)
.values({
id: uuid(),
providerId: "provider-uuid",
rrule: "RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR",
startTime: "09:00",
endTime: "17:00",
timezone: "America/New_York",
createdAt: new Date(),
updatedAt: new Date(),
})
.returning();

Find bookings on a specific date:

import { db } from "@/db";
import { bookings } from "@thebookingkit/db";
import { eq, and, gte, lt } from "drizzle-orm";
const providerId = "provider-uuid";
const dayStart = new Date(Date.UTC(2026, 2, 10, 0, 0, 0));
const dayEnd = new Date(Date.UTC(2026, 2, 10, 23, 59, 59, 999));
const dayBookings = await db.query.bookings.findMany({
where: and(
eq(bookings.providerId, providerId),
gte(bookings.startsAt, dayStart),
lt(bookings.endsAt, dayEnd),
eq(bookings.status, "confirmed")
),
});
console.log(`${dayBookings.length} confirmed bookings on March 10`);
packages/db/src/schema/
├── tables.ts # All 26 table definitions
└── enums.ts # All enum definitions