Important notice: This addon is in an alpha state. Expect bugs. Do not use in production without rigorous testing. Please raise issues.
A PostgreSQL-native content engine for Statamic 6. Replaces flat-file Stache storage with PostgreSQL while preserving full compatibility with Statamic's Query Builder, GraphQL API, Antlers tags, and Control Panel.
No Eloquent models. No statamic/eloquent-driver. Direct PostgreSQL storage through Statamic's repository contracts.
- Stores all Statamic content types in PostgreSQL: collections, entries, taxonomies, terms, globals, navigations, asset containers, forms, submissions, and revisions
- Uses PostgreSQL-native features: JSONB for flexible data, UUID primary keys, citext for case-insensitive handles, tsvector for full-text search, pg_trgm for fuzzy search
- Provides bidirectional import/export between flat files and PostgreSQL
- Includes observability tooling: slow query logging, query profiling, health checks
- Does not use Eloquent models for content persistence
- Does not require
statamic/eloquent-driver - Does not introduce a custom query API — everything works through standard Statamic methods
- Does not store blueprints, fieldsets, or users — those stay in their default locations
- PHP 8.5+
- PostgreSQL 15+
- Statamic 6
- Laravel 13
composer require stokoe/postgres-engineYour .env should have a PostgreSQL connection:
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=your_database
DB_USERNAME=your_user
DB_PASSWORD=your_passwordAdd this to your .env:
POSTGRES_ENGINE_CONNECTION=pgsqlWithout this line, the addon stays dormant and Statamic uses Stache as normal.
php artisan migratephp artisan pg-engine:doctorAll checks should pass.
php artisan vendor:publish --tag=postgres-engine-configSee INSTALL.md for the full installation guide including troubleshooting.
Once activated, the addon is transparent. All standard Statamic code works without modification:
// Query Builder — works identically
Entry::query()->where('collection', 'blog')->orderBy('date', 'desc')->get();
// Facades — work identically
Collection::findByHandle('blog');
Taxonomy::findByHandle('tags');
GlobalSet::findByHandle('site_settings');
// Repository contracts — resolve to PostgreSQL implementations
app(EntryRepository::class)->find($id);
app(CollectionRepository::class)->all();Antlers templates, GraphQL queries, and the Control Panel all work without changes.
# Dry-run first (validates without writing)
php artisan pg-engine:import content --dry-run
# Full import
php artisan pg-engine:import contentphp artisan pg-engine:export storage/app/export --overwritephp artisan pg-engine:import content --type=collections
php artisan pg-engine:import content --type=entries --collection=blog
php artisan pg-engine:import content --type=taxonomies
php artisan pg-engine:import content --type=terms --taxonomy=tags
php artisan pg-engine:import content --type=globals
php artisan pg-engine:import content --type=navigations
php artisan pg-engine:import content --type=assets
php artisan pg-engine:import content --type=forms
php artisan pg-engine:import content --type=submissions
php artisan pg-engine:import content --type=revisionsphp artisan pg-engine:export path/to/export --type=collections --overwrite
php artisan pg-engine:export path/to/export --type=globals --overwrite
php artisan pg-engine:export path/to/export --type=navigations --overwriteImport is idempotent — running it twice updates existing records without creating duplicates. Export supports --dry-run and --overwrite flags. Sensitive metadata (IP hashes, user agent hashes) is excluded from exports.
See docs/IMPORT_EXPORT.md for the full import/export architecture.
| Mode | .env setting |
Content source |
|---|---|---|
| PostgreSQL | POSTGRES_ENGINE_CONNECTION=pgsql |
pg_* tables |
| Flat files | Line removed or commented | content/ YAML/MD files |
To switch back to flat files:
# Comment out the activation line in .env
# POSTGRES_ENGINE_CONNECTION=pgsql
# Clear caches
php artisan cache:clear
php artisan statamic:stache:clear
php artisan statamic:stache:warmTo switch back to PostgreSQL, uncomment the line. No reimport needed.
| Command | Description |
|---|---|
pg-engine:doctor |
Health check: connection, extensions, tables, indexes, bindings |
pg-engine:import {path} |
Import flat files into PostgreSQL |
pg-engine:export {path} |
Export PostgreSQL content to flat files |
pg-engine:reindex |
Rebuild full-text search indexes |
pg-engine:search:test {query} |
Test search queries interactively |
pg-engine:stats |
Query statistics and slow query report |
All commands support --help for full option details.
The addon provides PostgreSQL-native search with four modes:
- Full-text search — tsvector-based, language-aware, ranked by relevance
- Fuzzy search — pg_trgm trigram matching for typo tolerance
- Hybrid search — combines full-text and fuzzy results
- Accent-insensitive search — uses the unaccent extension
Search weights are configurable per field (A/B/C/D ranking). Rebuild indexes after import:
php artisan pg-engine:reindexSee docs/ARCHITECTURE.md for the full technical architecture.
The addon is structured in layers:
ServiceProvider — Registers all bindings, validates config
├── Repositories — Implement Statamic contracts (Entry, Collection, etc.)
│ ├── Hydrators — Convert DB rows ↔ Statamic objects
│ └── Storage — Raw PostgreSQL operations (no Eloquent)
├── Query — EntryQueryBuilder, TermQueryBuilder, SQL compilation
├── Search — Full-text, fuzzy, hybrid search engine
├── Import/Export — Bidirectional flat-file ↔ PostgreSQL
├── Observability — Query profiling, slow query logging
├── Support — Identity map, cycle detector, bulk loader
└── Commands — Artisan commands (doctor, import, export, reindex, stats)
composer testThe test suite enforces strict guards — any incomplete, skipped, or noticed test fails the run:
- 976 tests, 5114 assertions
- 0 incomplete, 0 skipped, 0 PHPUnit notices
failOnIncomplete,failOnSkipped,failOnNotice,failOnRiskyall enabled
This addon maintains full compatibility with:
- Statamic Query Builder —
Entry::query(),where(),orderBy(),paginate(), JSONB dot-notation - Statamic GraphQL API — collections, entries, globals, filtering, sorting, pagination
- Antlers tags —
{{ collection }},{{ nav }},{{ global }}, all standard tags - Control Panel — listings, filtering, sorting, pagination, publish/unpublish
- Native Statamic objects —
Entry,Collection,Term,GlobalSet,Nav, etc.
Existing Statamic code works without modification.
You can use it for free while in development, but requires a license to use on a live site. Learn more or buy a license on The Statamic Marketplace!