HOW-TO · DATA SYNC

Move Postgres rows into MySQL on a schedule.

By Chris Davidson, founder of yForest · Updated September 26, 2026

Not every sync flows toward a warehouse. A read-heavy internal tool built on MySQL, or a legacy app that only speaks MySQL, sometimes needs a live copy of data that actually lives in Postgres. Data Sync handles that direction just as well as the warehouse cases.

Start 14-day free trial Download on theMac App Store

No credit card. 14 days. Cancel in one click.

macOS 15+ · Apple Silicon native · 14-day free trial · No credit card

Quick answer: Add a PostgreSQL connection and a MySQL connection, open Pipelines, Build, New Sync, pick Postgres as source and MySQL as target, map fields, choose Insert, Update or Upsert, Dry Run, then save as a scheduled job. Pipelines tier.

A sync that goes the less common direction

Most content about Postgres and MySQL together assumes a migration off MySQL onto Postgres, since that's the direction most teams choose when starting fresh. The reverse need is real too: an internal WordPress-adjacent tool, a support ticketing system, or a reporting plugin that only speaks MySQL, while the actual application of record runs on Postgres. Rebuilding that tool isn't worth it if a scheduled sync solves the actual problem.

Before you start

Steps

  1. Add both connections under Databases.
  2. Open Pipelines → Build and click New Sync.
  3. On the left, pick the Postgres connection and table, or write SQL.
  4. On the right, pick the MySQL connection and target table, or create one.
  5. Drag fields across, or click AI Map.
  6. Pick a MODE: Insert, Update or Upsert.
  7. For Update or Upsert, choose a MATCH ON field that's unique per row.
  8. Click Dry Run, then Save as a job.

A worked example

Keeping a MySQL-backed support tool's customer table current from the Postgres application database:

SELECT customer_id, full_name, plan, mrr_cents, updated_at
FROM public.customers
WHERE updated_at >= now() - interval '1 day';

Target support_tool.customers in MySQL, MODE Upsert, MATCH ON customer_id. QueryFlow handles the upsert into MySQL on that key, so a plan change updates the existing row in the support tool instead of creating a duplicate customer record. Schedule it hourly so support agents aren't looking at a stale plan tier.

A type gotcha worth knowing

Postgres's BOOLEAN is a real boolean type, but a MySQL target column often ends up as TINYINT(1) under the hood, which is fine for storage but can confuse a downstream tool expecting a true/false string. If the MySQL-based tool reads the column oddly, check whether it expects a literal 'true'/'false' string rather than 1/0, and adjust the mapping or a Flow Books transform accordingly.

What this doesn't do

This doesn't make MySQL a live mirror of Postgres, it's a scheduled batch sync, so the support tool is only ever as current as the last run. If the tool genuinely needs sub-minute consistency with the Postgres source, that's a case for logical replication or a CDC tool, not a scheduled query. For a support or internal tool where an hourly lag is invisible to the people using it, the scheduled approach is simpler to operate and doesn't require replication slots on the Postgres side.

Naming things so this doesn't get mysterious in six months

A sync named "customer sync" tells nobody why it exists or what depends on it. Name it for what it feeds, "Postgres customers to support tool," so whoever's debugging the support tool's stale data six months from now can find the right job in the Pipelines list without guessing.

Handling the case where MySQL is the one being retired

Sometimes this sync exists specifically to keep an old MySQL-backed tool alive just long enough to migrate its users to something else, rather than as a permanent architecture. If that's the plan, it's worth documenting the sync's expected end date somewhere outside QueryFlow itself, since a working sync has a way of quietly becoming permanent infrastructure nobody planned for.

A note on which side owns the truth

It's worth being explicit, even just in a comment on the sync job itself, that Postgres remains the source of truth and MySQL is a read-oriented copy for the support tool. Without that written down somewhere, it's easy for someone to eventually edit a record directly in the MySQL table thinking it's authoritative, only to have the next sync run overwrite their change with whatever Postgres still says.

Sunsetting the sync eventually

If the plan is to retire the MySQL-backed tool at some point rather than run this indefinitely, it's worth revisiting the sync's continued necessity on a regular cadence, quarterly is reasonable, rather than letting a working integration become permanent infrastructure by default. A sync nobody remembers the purpose of is harder to safely remove than one with a clear expected lifetime.

A gotcha with case sensitivity between the two databases

Postgres string comparisons are case-sensitive by default; MySQL's default collation on many installs is case-insensitive. A MATCH ON column holding text, an email address or a code, that differs only by case between two Postgres rows would be treated as two different keys there but might collide as one in MySQL. It's worth confirming the MySQL target table's collation matches the assumption the sync is making before trusting Upsert's behavior on a text-based key.

Monitoring for a growing lag

An hourly sync that starts taking longer than an hour to run is a sign worth catching early, not after the support tool starts showing data that's a full sync cycle behind. Checking the job's run duration in its history periodically, not just whether it succeeded, catches a slow creep in table size or query complexity before it turns into a user-visible staleness problem.

A rollback plan if the sync ever needs to pause

If the sync needs to be disabled temporarily, a maintenance window on the Postgres side, say, the MySQL table simply stops updating rather than losing data, and support agents keep working off whatever the last successful run left behind. It's worth telling the team ahead of a planned pause that the data will be stale for its duration, rather than letting them discover it mid-shift when a customer's plan tier looks wrong.

What this costs against Fivetran and Airbyte

Fivetran's pricing page (September 26, 2026) meters by monthly active rows, 500,000 free, then a $5 base charge per connection between 1 and 1,000,000 MAR, usage pricing above that behind a quote. Airbyte's open-source option avoids the metered cost but needs hosting, Docker or a server, plus the maintenance that comes with running it yourself. QueryFlow Pipelines is $199.99 a year flat, with nothing to host beyond your own Mac.

Comparison

QueryFlow Data SyncFivetranAirbyte (self-hosted)
Pricing$199.99/yr flatFree <500k MAR, then $5 base + usage quoteFree software, you pay for hosting
InfrastructureNone, runs on your MacNone, fully managedDocker or a server you maintain
Setup for one pairTwo connections, one mapped syncConnector wizardSource and destination connector config
Best fitOne legacy tool, small-to-medium volumeMany managed sources at onceTeams already running Airbyte infrastructure

Check it worked

Run Dry Run and compare against Postgres. After a real run, check row counts and the job's history for any listed errors.

Troubleshooting

If you seeFix
Choose at least one key fieldPick a MATCH ON column for Update or Upsert.
Boolean field reads oddly in the MySQL toolCheck whether the tool expects a string, not MySQL's native TINYINT(1).
JSONB column looks like raw text in MySQLExpected. Parse it in Flow Books first if the tool needs structured fields.

Sources

Sync Postgres to Snowflake MySQL Mac client Every warehouse, one client Integrations Load a CSV into MySQL. Sync Google Sheets to MySQL.
QueryFlow Studio $9.99/mo · $99/yr
QueryFlow Pipelines $29.99/mo · $199.99/yr

Frequently asked

Why would data move from Postgres to MySQL instead of the more common other direction?

Usually an existing tool, a legacy internal app, a plugin ecosystem, or a vendor product, only supports MySQL, while the source of truth for the data itself is a Postgres application database.

Does this handle Postgres-specific types like JSONB or arrays?

JSONB reads as its text representation and maps to a MySQL JSON or TEXT column. A native Postgres array doesn't have a direct MySQL equivalent, so it also comes across as text unless you restructure it in Flow Books first.

How does Upsert write into MySQL?

It runs an INSERT with ON DUPLICATE KEY UPDATE against the MATCH ON column you set, MySQL's native upsert mechanism, achieving the same result as a MERGE.

Which tier includes this?

Pipelines. Studio covers querying both databases, not building a scheduled sync between them.

Keep the old tool honest.

14-day free trial, no card. Map it once, run it hourly.

Start 14-day free trial

No credit card. 14 days. Cancel in one click.