MySQL adapter
The MySQL adapter works with:
- MySQL 5.7+, MySQL 8.x
- MariaDB 10.3+
- AWS Aurora MySQL, RDS MySQL
- Google Cloud SQL MySQL
- Azure Database for MySQL
- PlanetScale (use the
hostform with TLS) - Self-hosted MySQL
Config shape
URI form:
{ "type": "mysql", "config": { "uri": "mysql://spelo_readonly:pass@db.example.com:3306/production", "ssl": true }, "collections": { "products": { "source": "products", "searchable_fields": ["name", "description", "sku"], "filterable_fields": ["category_id", "price", "in_stock"], "display_fields": ["id", "name", "price", "sku"] } }}Discrete host/port form:
{ "type": "mysql", "config": { "host": "db.example.com", "port": 3306, "database": "production", "user": "spelo_readonly", "password": "${MYSQL_PASSWORD}", "ssl": true }}Setup
-
Create a read-only user
CREATE USER 'spelo_readonly'@'%' IDENTIFIED BY 'strong-random-password';GRANT SELECT ON your_database.* TO 'spelo_readonly'@'%';FLUSH PRIVILEGES;For tighter scope:
GRANT SELECT ON your_database.products TO 'spelo_readonly'@'%';GRANT SELECT ON your_database.categories TO 'spelo_readonly'@'%'; -
Whitelist Spelo’s IPs (if behind a firewall / security group)
AWS RDS / Azure / Cloud SQL: add Spelo’s egress IPs to the firewall rules. See spelo.ai/security/ips.
-
Enable TLS
MySQL 8 has TLS on by default. For RDS, set the connection string
?ssl-mode=REQUIRED. In the adapter, set"ssl": true. -
Paste the connection details in the dashboard
Dashboard → Data → MySQL → paste → Test connection.
-
Map collections — same as Postgres.
PlanetScale specifics
PlanetScale requires TLS and has a distinctive URI format. Use the “Connect with URL” option in the PlanetScale console, select “Node” → copy the URL. Then:
{ "type": "mysql", "config": { "uri": "mysql://user:pass@aws.connect.psdb.cloud/my-db", "ssl": true }}PlanetScale does not support FOREIGN KEY constraints, which does not affect the adapter. Joins across branches require you to pin a branch in the URI path.
Field type support
| MySQL type | Filter operators | Notes |
|---|---|---|
INT, BIGINT, DECIMAL, FLOAT, DOUBLE | eq, neq, gt, gte, lt, lte, in | |
VARCHAR, TEXT, CHAR | eq, neq, contains, in | contains is LIKE '%v%' |
TINYINT(1) / BOOLEAN | eq, neq | MySQL stores bool as 0/1 |
DATE, DATETIME, TIMESTAMP | eq, neq, gt, gte, lt, lte | Pass ISO 8601 |
JSON | contains | Uses JSON_CONTAINS on the column |
ENUM | eq, neq, in | String values |
Security notes
- Read-only enforced at three layers: user privilege, adapter code (only
SELECT), identifier whitelist. - Identifiers backticked (
`column`) to survive reserved words. - Values parameterized via
?placeholders (mysql2 handles array expansion forIN).
Connection pool
The adapter uses mysql2’s built-in connection pool with connectionLimit: 5. Each site gets its own pool, lazily initialized and cached for 5 minutes of inactivity, then closed.
Troubleshooting
ER_ACCESS_DENIED_ERROR→ wrong user/password, or the user isn’t allowed from the connecting host. Use'spelo_readonly'@'%'(any host) or scope to our egress IPs.ER_DBACCESS_DENIED_ERROR→ the user exists but has noSELECTon your database.GRANT SELECT ON db.* TO ....ETIMEDOUT→ firewall or security group is blocking us.HANDSHAKE_NO_SSL_SUPPORT→ you setssl: truebut your server doesn’t have TLS configured. Fix your server TLS or setssl: false(not recommended).
More: Database connection errors.