doc/DATABASE.md
Paperclip uses PostgreSQL via Drizzle ORM. There are three ways to run the database, from simplest to most production-ready.
If you don't set DATABASE_URL, the server automatically starts an embedded PostgreSQL instance and manages a local data directory.
pnpm dev
That's it. On first start the server:
~/.paperclip/instances/default/db/ directory for storagepaperclip database existsData persists across restarts in ~/.paperclip/instances/default/db/. To reset local dev data, delete that directory.
If you need to apply pending migrations manually, run:
pnpm db:migrate
When DATABASE_URL is unset, this command targets the current embedded PostgreSQL instance for your active Paperclip config/instance.
Issue reference mentions follow the normal migration path: the schema migration creates the tracking table, but it does not backfill historical issue titles, descriptions, comments, or documents automatically.
To backfill existing content manually after migrating, run:
pnpm issue-references:backfill
# optional: limit to one company
pnpm issue-references:backfill -- --company <company-id>
Future issue, comment, and document writes sync references automatically without running the backfill command.
This mode is ideal for local development and one-command installs.
Docker note: the Docker quickstart image also uses embedded PostgreSQL by default. Persist /paperclip to keep DB state across container restarts (see doc/DOCKER.md).
For a full PostgreSQL server locally, use the included Docker Compose setup:
docker compose up -d
This starts PostgreSQL 17 on localhost:5432. Then set the connection string:
cp .env.example .env
# .env already contains:
# DATABASE_URL=postgres://paperclip:paperclip@localhost:5432/paperclip
Run migrations:
DATABASE_URL=postgres://paperclip:paperclip@localhost:5432/paperclip \
pnpm db:migrate
Start the server:
pnpm dev
For production, use a hosted PostgreSQL provider. Supabase is a good option with a free tier.
Supabase offers two connection modes:
Direct connection (port 5432) — use for migrations and one-off scripts:
postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:5432/postgres
Connection pooling via Supavisor (port 6543) — use for the application:
postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres
For the application runtime, use a direct PostgreSQL connection unless the database client has explicit prepared-statement configuration for your pooling mode:
DATABASE_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:5432/postgres
If you later run the app with a pooled runtime URL, set DATABASE_MIGRATION_URL to the direct connection URL. Paperclip uses it for startup schema checks/migrations and plugin namespace migrations, while the app continues to use DATABASE_URL for runtime queries:
DATABASE_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:6543/postgres
DATABASE_MIGRATION_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-[REGION].pooler.supabase.com:5432/postgres
If your hosted database requires transaction-pooling-only connections, use a direct or session-pooled connection for Paperclip until runtime pooling support is documented in this guide. Do not edit database client source files as part of deployment setup.
# Use the direct connection (port 5432) for schema changes
DATABASE_URL=postgres://postgres.[PROJECT-REF]:[PASSWORD]@...5432/postgres \
pnpm db:migrate
See Supabase pricing for current details.
The database mode is controlled by DATABASE_URL:
DATABASE_URL | Mode |
|---|---|
| Not set | Embedded PostgreSQL (~/.paperclip/instances/default/db/) |
postgres://...localhost... | Local Docker PostgreSQL |
postgres://...supabase.com... | Hosted Supabase |
Your Drizzle schema (packages/db/src/schema/) stays the same regardless of mode.
The plugin runtime tracks plugin-owned database namespaces and migrations in plugin_database_namespaces and plugin_migrations. Hosted deployments that separate runtime and migration connections should set DATABASE_MIGRATION_URL; plugin namespace migration work uses the migration connection when present.
Paperclip supports automatic and manual database backups. See doc/DEVELOPING.md for the current paperclipai db:backup / pnpm db:backup commands and backup retention configuration.
Paperclip stores secret metadata and versions in:
company_secretscompany_secret_versionsFor local/default installs, the active provider is local_encrypted:
~/.paperclip/instances/default/secrets/master.key (auto-created if missing).~/.paperclip/instances/default/config.json under secrets.localEncrypted.keyFilePath.Optional overrides:
PAPERCLIP_SECRETS_MASTER_KEY (32-byte key as base64, hex, or raw 32-char string)PAPERCLIP_SECRETS_MASTER_KEY_FILE (custom key file path)Strict mode to block new inline sensitive env values:
PAPERCLIP_SECRETS_STRICT_MODE=true
You can set strict mode and provider defaults via:
pnpm paperclipai configure --section secrets
Inline secret migration command:
pnpm secrets:migrate-inline-env --apply