feat(api-headless-cms): postgres#5402
Merged
Merged
Conversation
Covers how 9 CMS systems handle filtering/sorting on deeply nested complex fields (object[] -> dynamic zone -> ...). Confirms no SQL-based CMS supports efficient filtering inside dynamic zones at depth. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Distinguish what users can do through the CMS API vs internal implementation details. Only Sanity (GROQ) supports deep nested filtering via API. All SQL-based CMSes block it at API level or require client-side filtering. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only 4 of 9 surveyed CMSes support nested AND/OR conditions via their public API. Webiny is among them. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
EAV storage + Solr/Azure Search for queries. Confirms dual-store pattern. No dynamic zones, no nested AND/OR via API. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Webiny and Sanity are the only systems where filtering/sorting/search works at any depth with zero user configuration. All others require manual index setup, explicit populate params, or don't support it. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Demonstrates why nested JSONB array queries are fundamentally unindexable in Postgres — GIN covers equality only, B-tree needs fixed paths, jsonb_array_elements is CPU-bound. Linear degradation vs OpenSearch O(log n). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
System indexes created once per model table. No per-field index management. GIN on values JSONB not created (OS handles filtering) but documented as optional future addition. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dynamic zone filtering not yet exposed in GraphQL API, but data is already indexed in OpenSearch. Enabling it is a GraphQL layer change, not a storage change. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lder Not just GraphQL schema — also needs OpenSearch query generation code for dynamic zone field types. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All four docs now consistently note: dynamic zone data is indexed in OpenSearch but filtering not yet exposed in GraphQL API. Requires GraphQL schema + OS query builder work to enable. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Users can nest fields as deep as they want — can't offer a limited nesting mode. Pure Postgres is not viable for Webiny's requirements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SQLite uses database-level write locking (entire file), no change streaming, no network access, no multi-process support. Postgres required for production at scale. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add createPostgresConnection() with env var support from <Infra.Postgres> - Add <Infra.Postgres> config component with all pg connection options (host, port, user, password, database, ssl, timeouts, keep-alive, etc.) - SSL accepts boolean or object with certificate file paths - Rename createSqliteApiHandler to createSqlApiHandler (old name deprecated) - Add pg driver dependency Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract SslEnvVars and OptionalEnvVar components, remove inline conditionals - Add optionalString helper for undefined-safe stringification - Add parseInputDatesAsUTC and options props (last serializable pg options) - Use named interfaces instead of inline types Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split test setup into separate client modules (createSqliteClient.js, createPgClient.js) with a common interface (createKnex, dropAllTables, teardown). Base setup files delegate to the correct client based on WEBINY_SQL_CLIENT env var. Run with Postgres: WEBINY_SQL_CLIENT=pg yarn test:sql packages/api-headless-cms Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Split SQL test clients into sqlite/pg/pglite modules with common interface - WEBINY_SQL_CLIENT env var: "pg" for real Postgres, "pglite" for in-memory - Real Postgres auto-creates test database, defaults to OS user - Add test:pg and test:pglite scripts to root package.json - Add PGlite group to GitHub Actions vitest workflow - Update api-core-sql presets to support pg/pglite cleanup Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
brunozoric
force-pushed
the
bruno/feat/api-headless-cms-postgres
branch
from
July 14, 2026 15:51
6eb2a16 to
0e5970c
Compare
brunozoric
marked this pull request as ready for review
July 14, 2026 16:00
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Research and design documentation for adding Postgres + OpenSearch storage operations to Webiny Headless CMS.
This branch contains no code changes — only design docs exploring the feasibility and architecture of replacing DynamoDB with Postgres as the primary data store, while keeping OpenSearch for
search/filter/sort.
Documents
01-pure-postgres.md— Pure Postgres approach (evaluated and rejected for production use due to dynamic zone querying limitations at scale)02-postgres-plus-opensearch.md— Recommended architecture: Postgres as source of truth + OpenSearch for all search/filter/sort, synced via WAL logicalreplication
03-cms-comparison.md— Industry comparison of 9 CMS systems (Strapi, Payload, Contentful, TYPO3, WordPress, AEM, Sanity, Directus, Drupal) — storage patterns,nested field filtering capabilities, scaling limits
04-decisions.md— All architectural decisions: table structure mirroring CmsEntry, JSONB values column, WAL sync, upsert pattern, versioning, re-index strategyKey Decisions
valuesJSONB columnON CONFLICT DO UPDATE) for CRUD,FOR UPDATEtransactions for flag swaps