Skip to content
GitHub
Get started →

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 host form 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

  1. 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'@'%';
  2. 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.

  3. Enable TLS

    MySQL 8 has TLS on by default. For RDS, set the connection string ?ssl-mode=REQUIRED. In the adapter, set "ssl": true.

  4. Paste the connection details in the dashboard

    Dashboard → DataMySQL → paste → Test connection.

  5. 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 typeFilter operatorsNotes
INT, BIGINT, DECIMAL, FLOAT, DOUBLEeq, neq, gt, gte, lt, lte, in
VARCHAR, TEXT, CHAReq, neq, contains, incontains is LIKE '%v%'
TINYINT(1) / BOOLEANeq, neqMySQL stores bool as 0/1
DATE, DATETIME, TIMESTAMPeq, neq, gt, gte, lt, ltePass ISO 8601
JSONcontainsUses JSON_CONTAINS on the column
ENUMeq, neq, inString 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 for IN).

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 no SELECT on your database. GRANT SELECT ON db.* TO ....
  • ETIMEDOUT → firewall or security group is blocking us.
  • HANDSHAKE_NO_SSL_SUPPORT → you set ssl: true but your server doesn’t have TLS configured. Fix your server TLS or set ssl: false (not recommended).

More: Database connection errors.