You can connect WordPress and WooCommerce to PostgreSQL, but not by replacing MySQL. WordPress only supports MySQL and MariaDB, and WooCommerce and most plugins write SQL that assumes MySQL. What works is leaving WordPress on MySQL and giving your plugin its own connection to a separate Postgres database, for the data that has to obey real rules.
That is how WeCoza 3.0 is built. Its business data lives in a 43-table Postgres schema beside the WordPress install. The plugin talks to it through PDO, not $wpdb, and plpgsql trigger functions enforce the invariants. WordPress handles the admin screens, the front end, logins and media. It does not own the operational data.
Can WordPress run on PostgreSQL instead of MySQL?
Not in a way I would put a business on. WordPress reaches its database through $wpdb, which wraps PHP’s mysqli driver, and the official requirements name MySQL and MariaDB only.
The community workaround is PG4WP, a db.php drop-in that intercepts those mysqli calls and rewrites each query for PostgreSQL as it runs. When I checked in October 2026, its tested list stopped at WordPress 6.5 and named two plugins. WooCommerce was not one of them. A translation layer is only as good as the SQL it receives, and a store sends plenty of its own, from WooCommerce and from every extension installed on it. Any query that does not translate cleanly is a bug you meet in production.
How do you connect WordPress to a PostgreSQL database?
From a plugin, through its own PDO connection. $wpdb only speaks MySQL, so the plugin opens a second connection with PHP’s pdo_pgsql driver and uses it for the operational data. WordPress keeps MySQL for posts, users, options and media.
What that needs:
pdo_pgsqlenabled on the server. Confirm it with the host before you quote. If it is missing, the hosting is wrong for this design.- Credentials outside the code, in
wp-config.phpconstants or environment variables. - One class that owns the connection and hands it out, so the rest of the plugin never opens its own.
- PDO set to throw exceptions, and prepared statements for every query, so a failed write stops loudly instead of passing silently.
How do you get WooCommerce data into PostgreSQL?
Leave the store on MySQL and copy across what you need. The right method depends on how fresh the copy has to be.
| Method | How it works | Use it when |
|---|---|---|
| Webhooks | WooCommerce posts each order, product or customer change to an endpoint you run, which writes it to Postgres | Orders must reach Postgres within seconds |
| REST API on a schedule | A job pulls the records changed since its last run through the WooCommerce REST API | Reporting, where an hour’s delay is fine |
| pgloader | Reads the MySQL tables and builds matching tables in Postgres in one pass | You are leaving WooCommerce, or need a one-off snapshot |
| mysql_fdw | A Postgres extension that queries the MySQL tables in place, without copying them | Occasional reads, where Postgres can reach the MySQL server |
For most stores, webhooks are where to start. An order sends an update every time its status changes, so write by order ID: a repeat delivery should update the row, not add a second one.
Why keep the operational data out of WordPress at all?
The default answer, for a WordPress project, is to store everything in the WordPress database. Custom post types, post meta, term meta, maybe a few custom tables behind $wpdb. It works. It’s standard. It keeps one DBA surface.
WeCoza needed something different. The client is a South African adult-education training provider running real operations, learners, agents, classes, clients, sites with a parent-child hierarchy, exams, placements, progressions, attendance, deliveries, QA visits, collections. The objects in their business aren’t “posts”; they’re business entities with foreign-key relationships between them, strict state transitions, and an audit trail that needs to survive plugin uninstalls and WordPress upgrades.
Three things pushed it toward a separate Postgres schema:
Invariants belong in the database. A class can only move from “Draft” to “Active” when an agent order is marked “Complete”. A notification fires when specific state transitions happen. Encoding rules like that in PHP means re-implementing things Postgres already does well, and doing them in a place where an uninstalled plugin can break the invariant. plpgsql triggers are harder to accidentally route around.
The schema needs to be readable independently of WordPress. There’s a hand-maintained DBML file in the repo so the client can read the model without SQL. When requirements change, and they do every couple of weeks, we talk about tables and relations, not about post types. Anchoring the conversation there has saved a lot of back-and-forth.
Operational data should survive the CMS. WordPress is the interface today. It may not be the interface in two years. The business logic shouldn’t move when the front-end does. Keeping Postgres as the system of record means a future rewrite is a UI project, not a data migration.
The cost is one extra connection, one extra ops surface, and the discipline of not accidentally falling back to $wpdb for anything operational. The settings UI in WeCoza has fields for database host / user / password that includes/db.php never consults, that’s debt I know about, not a decision. The PDO layer is where the real wiring happens.
Where this shows up
- wecoza_development, 43-table Postgres schema, PDO layer, plpgsql triggers, hand-maintained DBML, two-phase notification engine in
wecoza_events. - WordPress & PHP Craft, this is the specific shape that skill takes when the problem outgrows
$wpdb.