Enums
PostgreSQL enum types used across the schema, their values, and conventions for adding new enums.
The schema uses PostgreSQL enum types for columns with a fixed set of valid values. Enums are preferred over plain text columns with check constraints because they provide type safety in both PostgreSQL and TypeScript, appear in Drizzle's generated types, and document valid values in the schema itself.
Enum Registry
Cross-Cutting Enums (enums.ts)
| Enum | Values | Used by |
|---|---|---|
tenantTypeEnum | personal, family, company | organization.type (via text column, not enum -- Better Auth compatibility) |
Note: tenantTypeEnum is defined as a PostgreSQL enum but the organization.type column uses text().default("personal") instead of the enum type. This is because Better Auth manages the organization table schema and expects a text column. The enum exists for reference data validation in application code.
AI Domain Enums (ai.ts)
| Enum | Values | Used by |
|---|---|---|
aiProviderEnum | anthropic, openai, google | aiModel.provider |
aiPricingTypeEnum | input, output, cache_write, cache_read, batch_input, batch_output, batch_cache_write, batch_cache_read, long_context_input, long_context_output, tool_code_execution, tool_web_search, tool_web_fetch | aiModelPricing.pricingType |
aiPricingUnitEnum | per_1m_tokens, per_call, per_hour | aiModelPricing.unit |
aiBatchStatusEnum | pending, processing, ended, canceling, canceled, expired | aiBatch.status |
aiStopReasonEnum | end_turn, max_tokens, stop_sequence, tool_use, pause_turn, refusal, error | aiUsage.stopReason |
Research Domain Enums (research.ts)
| Enum | Values | Used by |
|---|---|---|
researchPlanStatusEnum | planning, executing, awaiting_review, stalled, completed, failed | researchPlan.status |
planStepStatusEnum | pending, in_progress, awaiting_input, completed, skipped, failed | planStep.status |
planStepTypeEnum | search, extract, analyze, critique, synthesize, checkpoint, custom | planStep.stepType |
planAuditEventTypeEnum | step_started, step_completed, step_failed, plan_modified, user_reviewed, session_resumed, skill_started, skill_completed | planAuditLog.eventType |
extractionResultStatusEnum | pending, completed, failed, needs_review | extractionResult.status |
skillExecutionStatusEnum | started, executing, completed, failed | skillExecution.status |
artifactTypeEnum | analysis, synthesis, comparison, summary, source_list, finding | researchArtifact.artifactType |
Search Domain Enums (search.ts)
| Enum | Values | Used by |
|---|---|---|
chunkSourceTableEnum | research_artifact, research_output, extraction_result | documentChunk.sourceTable |
Defining a New Enum
Enums are defined using Drizzle's pgEnum(), typically at the top of the schema file for the domain they belong to:
import { pgEnum } from "drizzle-orm/pg-core";
export const myStatusEnum = pgEnum("my_status", ["pending", "active", "completed", "failed"]);
Then use it in a table definition:
export const myTable = pgTable("my_table", {
id: text("id").primaryKey(),
status: myStatusEnum("status").notNull().default("pending"),
});
Naming Convention
- Enum type names use
snake_casein PostgreSQL:research_plan_status,ai_provider - Drizzle variable names use
camelCasewithEnumsuffix:researchPlanStatusEnum,aiProviderEnum - Values are
snake_casestrings:"in_progress","cache_write","per_1m_tokens"
Where to Put New Enums
- Domain-specific enums: In the domain's schema file (e.g., enums for a billing feature go in
billing.ts) - Cross-cutting enums: In
enums.ts(currently onlytenantTypeEnum)
Do not create a new file for a single enum. The convention is to co-locate enums with the tables that use them.
Enum Migration Considerations
Adding a new enum type generates a CREATE TYPE statement in the migration. Adding a new value to an existing enum generates an ALTER TYPE ... ADD VALUE statement.
PostgreSQL limitations to be aware of:
- Adding values is safe --
ALTER TYPE ... ADD VALUEis non-blocking - Removing values is not supported -- PostgreSQL does not allow removing values from an existing enum without recreating the type and all dependent columns
- Renaming values is not supported natively -- requires creating a new enum, migrating data, and dropping the old one
Because of these limitations, be thoughtful about initial enum values. It is easy to add values later but expensive to remove or rename them.
When to Use Enums vs Text
Use a PostgreSQL enum when:
- The value set is small and relatively stable (status codes, types, categories)
- The values are used in
WHEREclauses orCASEexpressions - You want database-level validation that prevents invalid values
Use a plain text column when:
- The value set is large or user-defined
- The values change frequently
- The column is managed by an external library (like Better Auth's
roleandtypecolumns) - The values are free-form identifiers (like
featureinaiUsage)
The organization.type and member.role columns are text instead of enums because Better Auth manages them and may add values in future versions.