Connecting MySQL to n8n: the MySQL node, its operations and its pitfalls
Published 2 August 2026 · 7 min read
MySQL quietly powers a huge share of the web without anyone thinking about it: every WordPress install (so every WooCommerce store), a large chunk of legacy PHP applications, plenty of homegrown ERPs and CRMs. When an automation need touches one of these databases, two instincts are possible: look for a clean REST API on the application side, or connect n8n directly to MySQL. n8n's MySQL node covers exactly that second case — a native SQL client, a close cousin of the Postgres node we cover in our Postgres node guide, but with its own parameter syntax and its own pitfalls. This guide covers the connection, the six available operations, protection against SQL injection, and the — important — line between direct database access and going through the application's API.
When to connect MySQL rather than an API or Airtable
Three cases clearly justify the MySQL node:
- An application database you own, with no third-party API to respect: an in-house CRM, a configuration table, an internal tool. Here, direct SQL access is the shortest path.
- Cross-cutting reporting on an existing database: aggregations, joins across several tables, queries the application's admin interface or API doesn't offer. A
GROUP BYover the last three months of WooCommerce orders, for instance, is often simpler to write in SQL than to rebuild through several API calls and a Merge node. - A one-off migration or sync, where reading the source tables directly is faster than going through an application layer designed for other purposes.
Conversely, as soon as it's about writing to the database of an application with its own business logic — creating a WooCommerce order, changing a PrestaShop status, updating a WordPress post — the application's REST API remains the right path, not the MySQL node. Our connecting WooCommerce and connecting PrestaShop guides cover those APIs in detail: they trigger internal hooks, invalidate caches and apply business rules (stock recalculation, transactional emails) that a plain UPDATE ignores completely. A direct UPDATE on the wp_posts table works, but leaves the application in an inconsistent state its own code never anticipated.
Connecting: credentials and common environments
n8n's MySQL credential asks for the usual fields: host, port (3306 by default), database, user, password, and an SSL setting.
MySQL in local Docker. If n8n and MySQL run in the same docker-compose, the host to enter isn't localhost (which would point to the n8n container itself) but the Docker service name — mysql or db, depending on your file. This is the number-one self-hosted mistake, the same one that trips people up with the Postgres node.
Shared hosting or a VPS (OVH, Infomaniak, Hostinger…). Most hosts running WordPress or PrestaShop expose MySQL on an internal host, sometimes reachable only from the same network. If your n8n instance lives elsewhere (Vercel, a separate VPS, n8n Cloud), you'll need to either allow n8n's IP through the host's MySQL firewall or go through an SSH tunnel — many shared hosts only expose MySQL locally and refuse any remote connection at all, in which case only the application's API (or a plugin that creates one) remains usable.
A managed database (RDS MySQL, Cloud SQL, PlanetScale…): same parameters, SSL enabled, and n8n's IP allowed in the security group. An ETIMEDOUT or connection refused error almost always points to this network filtering rather than a wrong credential.
The MySQL node's six operations
The node offers two levels, just like the Postgres node:
- Select — reads rows with simple filters, no SQL required.
- Insert — adds a row by mapping columns from the incoming fields.
- Update — modifies existing rows based on a matching column.
- Delete — removes filtered rows.
- Insert or Update (upsert) — inserts if the row doesn't exist, updates it otherwise, based on a key column you designate.
- Execute Query — a free-form SQL field for everything the built-in operations don't cover: joins, aggregations, subqueries, stored procedure calls.
The rule stays the same as for Postgres: built-in operations are enough for row-by-row CRUD and rule out any typo in a column name; Execute Query takes over once the logic turns relational.
Query Parameters: ? instead of $1
This is the main departure from the Postgres node, and the most common source of errors for anyone switching between the two. The Postgres node uses numbered placeholders $1, $2… The MySQL node uses positional question marks ?, in the order the values are supplied:
SELECT id, total, status
FROM wp_woocommerce_orders
WHERE status = ? AND date_created >= ?
with, in the Query Parameters field, the ordered list of matching values: {{ $json.status }}, {{ $json.startDate }}. As with Postgres, this mechanism separates the query from the data sent to the server: a value containing an apostrophe, a semicolon, or a SQL fragment forged by a third party is always treated as plain text, never as executable code. It's the reference countermeasure long documented in the SQL injection literature, notably the taxonomy by Halfond, Viegas and Orso (A Classification of SQL Injection Attacks and Countermeasures, IEEE Symposium on Secure Software Engineering, 2006 — see on Google Scholar), which identifies parameterized queries as the most reliable protection against this class of attack, on MySQL just as on Postgres.
Two MySQL-specific nuances are worth knowing before trusting this blindly:
- Order matters, not naming. Unlike
$1/$2, which can be reused at several points in the same query, each?consumes one value from the list in order of appearance. A reordered query without a reordered parameter list produces a silently wrong result, not an error. - Values containing a comma have had parsing bugs in some versions of the node (the Query Parameters field expects a comma-separated list). If a business value can legitimately contain a comma — an address, a company name — test it explicitly before going to production, or isolate it in a Code node that builds the query more carefully.
A concrete use case: weekly reporting on a WordPress/WooCommerce database
A pipeline representative of the "cross-cutting reporting" case mentioned above:
- Schedule Trigger, every Monday morning.
- MySQL node (Execute Query) aggregates the past week's orders directly on the WooCommerce tables, with a join the REST API doesn't allow in a single call:
SELECT DATE(o.date_created_gmt) AS day,
COUNT(*) AS orders,
SUM(o.total_amount) AS revenue
FROM wp_wc_orders o
WHERE o.status = ? AND o.date_created_gmt >= ?
GROUP BY day
ORDER BY day
- A Set node formats the result into a readable table.
- A Slack or Gmail node sends the summary to the team, on the same principle as our automated Google Analytics report, applied here directly to the database rather than to a third-party analytics API.
This pipeline stays strictly read-only: there's no risk of inconsistency on the WooCommerce side, since no write ever goes through the MySQL node.
MySQL or PostgreSQL for a new project
When the database doesn't exist yet and the choice is open, recent academic benchmarks lean clearly toward PostgreSQL for complex queries and large volumes. Salunke and Ouda's study, A Performance Benchmark for the PostgreSQL and MySQL Databases (Future Internet, MDPI, 2024 — DOI), measures clear gaps on large-scale select queries and a sharper degradation of MySQL as tables grow, while MySQL keeps the edge on simple write workloads at moderate concurrency. For a fresh project with no existing constraint, our Postgres node guide and our Supabase connection guide remain the recommended starting point.
But in the vast majority of cases where n8n connects to MySQL, the question doesn't come up in those terms: the MySQL database is already there, put in place by WordPress, a PHP application, or an ERP that's been running for years. The right move then isn't to migrate the database to satisfy n8n, but to connect to it cleanly — which is what this guide covers.
Common pitfalls
- Writing directly to the database of an application with its own logic (WooCommerce, PrestaShop, WordPress) instead of going through its API: hooks, caches and business rules get bypassed, leaving inconsistencies that are hard to diagnose afterward.
- Concatenating
{{ }}expressions into the SQL text instead of using Query Parameters: the reflex that opens the door to SQL injection, on MySQL just like anywhere else. - Using
localhostas the host in Docker: from the n8n container, that name points to the container itself, not to MySQL. - Trusting a value that contains a comma in Query Parameters without testing it first — a pitfall specific to MySQL, absent from the Postgres node.
- Forgetting the node runs per item: an Execute Query fed by hundreds of items fires just as many queries; aggregate upstream or tune Query Batching.
- Connecting with a MySQL user that has full privileges instead of a dedicated role limited to the tables actually needed — the same hygiene detailed in our securing API credentials guide.
In summary
n8n's MySQL node follows the same logic as the Postgres node — built-in operations for simple CRUD, Execute Query for the rest — with one syntax difference to remember (positional ? rather than $1/$2) and a comma-specific pitfall in the parameters. The real decision isn't technical but architectural: direct reads are legitimate for reporting or for a database you own, while the application's API is mandatory as soon as you're writing to a database whose logic belongs to a WordPress, a WooCommerce, or a PrestaShop. To go further on the e-commerce side, our automating orders guide covers the full pipeline on the API side — and for a first profitable AI project built on data that's already clean (the kind this sort of MySQL query is precisely meant to extract), the Inbox AI Pack (€79) applies the same sorting and prioritization logic to your inbox.
FAQ
Frequently asked questions
Should I use the MySQL node or the REST API to automate a WooCommerce or PrestaShop store whose database runs on MySQL?
To write data (creating an order, changing a status), go through the application's REST API (WooCommerce, PrestaShop) rather than direct MySQL writes: the API triggers hooks, invalidates caches and respects internal business rules that a raw SQL query bypasses entirely. The MySQL node becomes relevant again for read-only access — reporting queries the API doesn't easily support (joins across several tables, aggregations) — or for writes on an application database you own that has no such constraint.
How do I prevent SQL injection with n8n's MySQL node?
Never insert a {{ }} expression directly into the query text. Use ? placeholders for the values instead, then list those values in the Query Parameters field: n8n passes them separately to the MySQL server, which can never interpret them as SQL code, regardless of their origin (a form, a webhook, an LLM's response).
Does the MySQL node run one query per item or a single batched query?
By default, one query per incoming item: ten items trigger ten executions. The Query Batching option lets you group the queries into a single call, run them independently of one another, or wrap them in a transaction that rolls back the whole batch on failure — useful when a set of writes must be applied entirely or not at all.
MySQL or PostgreSQL: which should I pick for a new project driven by n8n?
With no existing constraint, PostgreSQL has the edge on complex queries (joins, aggregations) and large volumes, as recent academic benchmarks show. But the question often doesn't arise in those terms: most MySQL connections from n8n target a database that's already in place (WordPress, a legacy PHP app, an ERP), not a choice made from scratch. In that case, n8n's MySQL node is more than enough to connect to it cleanly.
Bundle FlowKit Complet
€269