Install
$ agentstack add skill-param087-saas-starter-skills-database-schema ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Database Schema
Overview
Use Drizzle ORM + Postgres: schema is TypeScript, migrations are generated and version-controlled, and queries are fully typed. The single most important early decision for SaaS is the tenancy model — almost every table hangs off an organization. Model that now; retrofitting organization_id onto a live table is painful.
When to use
- Setting up the database layer of a new app.
- Adding any table — especially tenant-owned data.
- Migrating from an untyped query builder or raw SQL.
Core schema
// src/server/db/schema.ts
import { pgTable, uuid, text, timestamp, pgEnum, uniqueIndex, index } from "drizzle-orm/pg-core";
export const roleEnum = pgEnum("role", ["owner", "admin", "member"]);
export const users = pgTable("users", {
id: uuid("id").defaultRandom().primaryKey(),
email: text("email").notNull().unique(),
name: text("name"),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
});
export const organizations = pgTable("organizations", {
id: uuid("id").defaultRandom().primaryKey(),
name: text("name").notNull(),
slug: text("slug").notNull().unique(),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
});
export const memberships = pgTable("memberships", {
id: uuid("id").defaultRandom().primaryKey(),
userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
orgId: uuid("org_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
role: roleEnum("role").notNull().default("member"),
}, (t) => ({
userOrg: uniqueIndex("memberships_user_org").on(t.userId, t.orgId),
}));
// Tenant-owned table: ALWAYS carry org_id + index it.
export const projects = pgTable("projects", {
id: uuid("id").defaultRandom().primaryKey(),
orgId: uuid("org_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
name: text("name").notNull(),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
}, (t) => ({
byOrg: index("projects_org_idx").on(t.orgId),
}));
Rules of thumb
org_idon every tenant table, indexed, withonDelete: "cascade"from organizations.- UUID PKs (
defaultRandom) avoid enumeration and merge conflicts; usebigserialonly when you need ordered keys. timestamptzeverywhere withdefaultNow(). Store UTC; format in the UI.- Money as integer cents (or
numeric), never floats. - Index foreign keys and every column you filter/sort by. Postgres doesn't auto-index FKs.
Migrations
npx drizzle-kit generate # diff schema -> SQL migration
npx drizzle-kit migrate # apply to the database
Commit the generated SQL. Never hand-edit applied migrations; add a new one.
Pitfalls
- Forgetting
org_id— the table becomes a cross-tenant leak waiting to happen. pushto production — use generated, reviewedmigrate;pushis for local prototyping.- Floats for money — rounding errors in billing. Use cents.
- No FK indexes — joins and cascades get slow as data grows.
- Naive timestamps —
timestampwithout timezone causes off-by-hours bugs across regions.
Hand-off
A typed, migratable schema. data-access-layer wraps it in tenant-scoped queries; multi-tenancy enforces isolation on top.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: param087
- Source: param087/saas-starter-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.