Skip to main content
Version: 1.2.0
Last Updated: 2026-04-24
Status: Stable
Target Audience: Developers and AI agents (GitHub Copilot, Cursor, general AI assistants)
Comprehensive guide for database development in the Encore OS Platform, covering RLS patterns, migrations, custom fields, multi-tenant queries, and debugging.

AI Agent Context

Key Patterns for AI:
  • Always include organization_id and custom_fields JSONB DEFAULT '{}' NOT NULL in business entity tables
  • Always use SECURITY DEFINER functions for RLS policies to avoid recursion
  • Always follow naming conventions: snake_case for tables/columns, {core}_{action}_{entity}() for functions
  • Always ship schema changes as versioned supabase/migrations/*.sql — never manual SQL in production Dashboard
  • Prefer declarative DDL under supabase/schemas/ plus supabase db diff (see DECLARATIVE_SCHEMA_GUIDE.md); use hand-written migrations for documented diff caveats (DML, some policy/view/grant cases)
  • Always test RLS policies with multi-tenant isolation scenarios
Common Mistakes to Avoid:
  • Creating recursive RLS policies (querying RLS-protected tables in RLS policies)
  • Forgetting organization_id in business tables (breaks multi-tenancy)
  • Using CHECK constraints for time-based validations (use triggers or application logic)
  • Creating schema changes outside migrations / declarative workflow (no orphan Dashboard DDL)
  • Not including custom_fields JSONB column in business entities

Quick Reference


Database Development Workflow

1. Plan the Schema

Before writing SQL, document:
  • Table purpose and ownership (which core/module)
  • Required columns (standard + module-specific)
  • Relationships (foreign keys)
  • RLS requirements
  • Custom fields use cases

2. Author DDL (declarative) and generate a migration

Preferred (DDL):
  1. Edit or add .sql under supabase/schemas/ so it reflects the desired final state (see DECLARATIVE_SCHEMA_GUIDE.md).
  2. Optional: npm run db:schemas:lint (@supabase/pg-topo ordering / parse diagnostics; skips if no .sql files yet).
  3. Stop local Supabase, then generate a migration from drift:
    Shortcut: npm run db:schemas:diff -- -f {core}_{entity}_{change}
  4. Review the new file under supabase/migrations/ (watch for unintended drops).
Legacy / caveat-only (DML, certain policy/view/grant cases):
Follow MIGRATION_LANES.md for schema vs system-defaults lanes.

3. Write or verify SQL

Follow naming conventions and include:
  • Table creation with standard columns
  • RLS enablement
  • RLS policies using SECURITY DEFINER functions
  • Indexes for performance
  • Comments for documentation

4. Test Locally

5. Generate TypeScript Types

6. Update Code

  • Create TypeScript types/interfaces
  • Implement queries with proper RLS
  • Add tests for RLS policies
  • Document in implementation log

Creating Tables with RLS

Standard Table Structure

All business entity tables MUST include:

When to Include custom_fields

✅ Include for:
  • Business entities (employees, residents, forms, transactions)
  • User-facing data that may need org-specific metadata
❌ Skip for:
  • Junction/mapping tables
  • Audit/log tables
  • System/configuration tables

Example: Employee Table


RLS Policy Patterns

Critical Rule: Use SECURITY DEFINER Functions

❌ NEVER query RLS-protected tables directly in policies (causes infinite recursion):
✅ ALWAYS use SECURITY DEFINER helper functions:

Standard RLS Policies

SELECT Policy (View)

INSERT Policy (Create)

UPDATE Policy (CRITICAL: Must Include WITH CHECK)

Why WITH CHECK is Critical: Without WITH CHECK, a user can change organization_id to move data between tenants, breaking multi-tenant isolation.

DELETE Policy

Creator-Only Policies

For entities where only the creator can modify:

Role-Based Policies

For policies that require specific roles:

Migration Creation and Testing

Migration Naming

Format (new migrations): {timestamp}_{snake_case_description}.sql — e.g. 20260428000143_pf15_picklist_defaults_data_driven.sql. The Lovable-generated {timestamp}_{uuid}.sql pattern is grandfathered for existing files but blocked for new ones by scripts/database/lint-migration-filename.ts in pre-commit.

Migration Requirements

  1. Monotonic and Ordered: Migrations run in timestamp order
  2. Re-runnable: Can run multiple times safely (use IF NOT EXISTS)
  3. Backward-Compatible: When possible, avoid breaking changes

Migration Template

Testing Migrations

Migration Best Practices

  1. One logical change per migration - Easier to review and rollback
  2. Use IF NOT EXISTS - Makes migrations re-runnable
  3. Add comments - Document purpose and changes
  4. Test locally first - Always test before committing
  5. Review RLS policies - Ensure tenant isolation

Custom Fields Implementation

When to Use Custom Fields

Use custom_fields JSONB for:
  • Organization-specific metadata on records
  • Fields that vary by organization
  • Extensibility without schema changes
Examples:
  • Employee badge numbers
  • External system IDs
  • Custom flags or preferences
  • Organization-specific classifications

Custom Fields Pattern

Querying Custom Fields

Custom Fields Indexing

For frequently queried custom fields, add GIN index:
When to add index:
  • Custom field is queried frequently
  • Organization has many records
  • Query performance is slow

Multi-Tenant Query Patterns

Always Filter by organization_id

✅ CORRECT: Always include organization_id filter

❌ WRONG: Missing organization_id filter

Getting Current Organization

Multi-Organization Queries

For users with access to multiple organizations:
Note: RLS policies will automatically filter to organizations the user has access to.

Database Debugging

Check RLS Policies

Test RLS Policies

Debug Query Performance

Common RLS Issues

Issue: “Infinite recursion” error

Cause: Querying RLS-protected table in policy Solution: Use SECURITY DEFINER function

Issue: “No rows returned” when rows exist

Cause: RLS policy too restrictive or missing Solution: Check policy conditions, verify user has org access

Issue: “Cross-organization data leakage”

Cause: Missing WITH CHECK in UPDATE policy Solution: Add WITH CHECK clause to UPDATE policies

Constitution playbook: PF DDL templates

Canonical SQL and JSON examples for module settings, picklists (PF-15), and custom field definitions (PF-16). The constitution §5.2 states the MUST rules; this section is the extended implementation reference. UI patterns for settings pages: settings-pattern-guide.md.

Module settings table ({core}_module_settings)

Picklists: pf_picklists and pf_picklist_items

Example RLS (use current pf_has_org_access / pf_is_org_admin from your schema):

Custom field definitions: pf_custom_field_definitions

Example validation_rules JSON


Core Standards

  • Constitution §5 - Database rules and RLS requirements
  • AGENTS.md - Database naming conventions and patterns

Development Guides

Architecture


Maintained By: Platform Foundation Team