Skip to main content
RapidDev - Software Development Agency
bubble-integrationsBubble API Connector

MySQL

Bubble cannot open a TCP connection to MySQL port 3306 — all database calls must go through HTTPS. The two main paths are: PlanetScale's HTTP API (the cleanest modern approach, no server to maintain) and a custom REST proxy in front of your existing MySQL database. Both use Bubble's API Connector with credentials in Private headers. Write operations should go through Backend Workflows on paid plans.

What you'll learn

  • Why Bubble cannot connect directly to MySQL and what REST proxy patterns solve this
  • How to use PlanetScale's HTTP API as a managed MySQL REST layer with no server to maintain
  • How to configure Bubble's API Connector with Private headers for MySQL credentials
  • How to write SQL queries safely using parameterized query syntax (not string concatenation)
  • How to set up a custom REST proxy for existing MySQL databases
  • How to handle write operations securely through Bubble Backend Workflows
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate19 min read1.5–3 hoursDatabase & BackendLast updated July 2026RapidDev Engineering Team
TL;DR

Bubble cannot open a TCP connection to MySQL port 3306 — all database calls must go through HTTPS. The two main paths are: PlanetScale's HTTP API (the cleanest modern approach, no server to maintain) and a custom REST proxy in front of your existing MySQL database. Both use Bubble's API Connector with credentials in Private headers. Write operations should go through Backend Workflows on paid plans.

Quick facts about this guide
FactValue
ToolMySQL
CategoryDatabase & Backend
MethodBubble API Connector
DifficultyIntermediate
Time required1.5–3 hours
Last updatedJuly 2026

Bubble + MySQL: No Direct Connection — Two Patterns That Work

MySQL is the database behind millions of LAMP stack applications, WordPress installs, and legacy business systems. When Bubble founders first try to connect to MySQL, they often expect something like Retool's direct database connection panel — enter host, user, password, and you're in. That approach is impossible in Bubble because Bubble applications run in users' browsers, which cannot open TCP connections to external servers on port 3306. The browser's security model simply doesn't allow it.

The solution is a REST API layer sitting between Bubble and MySQL. This layer accepts HTTP requests from Bubble's API Connector, executes MySQL queries server-side (where the connection credentials live safely), and returns JSON responses. Two patterns dominate:

**Pattern 1: PlanetScale HTTP API.** PlanetScale is a MySQL-compatible database platform that provides an HTTP API endpoint — you send it a SQL query as a JSON POST body and receive a JSON result set. This is the cleanest modern path: no server to maintain, no deployment, and the HTTP API works natively with Bubble's API Connector. PlanetScale deprecated its MySQL binary protocol for new connections, so the HTTP API is now the standard PlanetScale path.

**Pattern 2: Custom REST Proxy.** If you have an existing MySQL database (a shared hosting environment, an AWS RDS instance, a DigitalOcean Droplet running MySQL), you need to deploy a small REST API that exposes your data over HTTPS. This could be a simple Express.js app on Railway or Render, a PHP REST endpoint on your shared hosting server, or an AWS Lambda with the `mysql2` npm package. The proxy stores MySQL credentials in environment variables and accepts requests from Bubble with a private API key.

The critical security requirement for both patterns is parameterized queries. If the proxy concatenates Bubble-supplied values directly into SQL strings (`SELECT * FROM users WHERE id = '` + userId + `'`), your database is vulnerable to SQL injection even from your own Bubble app — a malicious user can craft an input that escapes the string and executes arbitrary SQL. The proxy must use parameterized queries (`?` placeholders in mysql2, `PDO::prepare` in PHP) where the driver handles escaping, not the application code.

Write operations (INSERT, UPDATE, DELETE) should be routed through Bubble Backend Workflows rather than client-side API Connector calls — not because client-side calls are inherently less secure (the Private header protects the API key either way), but because server-side workflows don't appear in browser DevTools network logs, reducing the attack surface for manipulation.

Integration method

Bubble API Connector

Bubble API Connector calls either the PlanetScale HTTP API (POST /api/v1/execute with SQL query body) or a custom REST proxy endpoint sitting in front of a MySQL database, with credentials in Private shared headers.

Prerequisites

  • A Bubble app (free or paid; client-side reads work on any plan; Backend Workflows for secure write operations require a paid plan)
  • The Bubble API Connector plugin installed (Plugins tab → Add plugins → search 'API Connector' by Bubble)
  • Either a PlanetScale account (planetscale.com) or an existing MySQL database with a REST proxy you can deploy
  • For PlanetScale: a service token created in PlanetScale console with read+write access to your database
  • For a custom REST proxy: basic familiarity with Node.js/Express or PHP, and a hosting environment (Railway, Render, or shared hosting)

Step-by-step guide

1

Choose your MySQL integration path

Before configuring Bubble, decide which pattern fits your situation: **Choose PlanetScale HTTP API if:** - You're starting a new project with no existing MySQL database - You want managed MySQL without maintaining a server - You're comfortable writing raw SQL in Bubble's API Connector call bodies **Choose a Custom REST Proxy if:** - You have an existing MySQL database (shared hosting, RDS, DigitalOcean, etc.) that you cannot or do not want to migrate to PlanetScale - You have an existing LAMP/PHP application with REST endpoints already in place - You want to define business logic in the proxy (access control, data transformation) rather than in Bubble workflows If you're choosing PlanetScale: go to planetscale.com → Create a new database → choose a region → create it (takes about 30 seconds). In the PlanetScale console, go to your database → Settings → Passwords → create a service token with `read_write` access. Note the **token ID** and **token value** — you'll need both for the Authorization header. If you're using a custom proxy: verify your MySQL database is accessible from the internet (or from your proxy deployment platform). Most shared hosts and cloud databases are publicly accessible on a specific host + port. Note your MySQL host, port, database name, username, and password.

Pro tip: PlanetScale's HTTP API endpoint URL includes the region in the hostname: for example `https://us-east.connect.psdb.cloud/api/v1/execute`. Check PlanetScale's current documentation for the exact regional endpoint URL for your database, as region codes can change.

Expected result: You've chosen your integration path. For PlanetScale: you have a database, a service token ID, and a token value. For a custom proxy: your MySQL database credentials are noted and the database is reachable from your deployment platform.

2

(PlanetScale path) Configure Bubble API Connector for PlanetScale HTTP API

In Bubble, go to Plugins tab → Add plugins → search 'API Connector' → Install. Open the API Connector and click 'Add another API'. Name it 'PlanetScale MySQL'. Add a shared header: - Name: `Authorization` - Value: `Bearer {token_id}:{token_value}` — concatenate your service token ID, a colon, and your service token value - Check the **Private** checkbox Add another shared header: - Name: `Content-Type` - Value: `application/json` Click 'Add another call'. Configure: - **Name**: `Execute Query` - **Method**: POST - **URL**: `https://us-east.connect.psdb.cloud/api/v1/execute` (use your actual region endpoint) - **Body**: JSON with two fields: - `query`: a dynamic text field (the SQL query string) - `args`: a dynamic text field (the query arguments as a comma-separated list, or an array) The call configuration: ```json { "method": "POST", "url": "https://us-east.connect.psdb.cloud/api/v1/execute", "headers": { "Authorization": "Bearer <private>", "Content-Type": "application/json" }, "body": { "query": "<dynamic>", "args": ["<dynamic>"] } } ``` For the Initialize call, test with a simple SELECT: `query = 'SELECT 1 as test'`, `args = []`. If PlanetScale is connected, you'll see the response `{"rows": [{"test": "1"}], "rowsAffected": 0, "insertId": "", "fields": [...]}`. Click Save.

planetscale-api-call.json
1{
2 "method": "POST",
3 "url": "https://us-east.connect.psdb.cloud/api/v1/execute",
4 "headers": {
5 "Authorization": "Bearer <token_id>:<token_value>",
6 "Content-Type": "application/json"
7 },
8 "body": {
9 "query": "<dynamic-sql>",
10 "args": ["<dynamic-args>"]
11 }
12}

Pro tip: PlanetScale's HTTP API requires parameterized queries — use `?` placeholders in the SQL string and pass the actual values in the `args` array. For example: `{"query": "SELECT * FROM users WHERE id = ?", "args": ["123"]}`. Never concatenate values directly into the query string.

Expected result: The PlanetScale API Connector group is configured with the Authorization shared header marked Private. The 'Execute Query' call is initialized and returns a valid PlanetScale JSON response structure.

3

(Custom proxy path) Deploy a REST proxy for existing MySQL

If you're using an existing MySQL database, deploy a simple REST API that Bubble can call. Here's a minimal Node.js/Express proxy that you can deploy to Railway or Render for free: ```javascript import express from 'express'; import mysql2 from 'mysql2/promise'; const app = express(); app.use(express.json()); // Validate API key from Bubble const API_KEY = process.env.BUBBLE_API_KEY; app.use((req, res, next) => { if (req.headers['x-api-key'] !== API_KEY) { return res.status(401).json({ error: 'Unauthorized' }); } next(); }); const pool = mysql2.createPool({ host: process.env.MYSQL_HOST, user: process.env.MYSQL_USER, password: process.env.MYSQL_PASSWORD, database: process.env.MYSQL_DATABASE, waitForConnections: true, connectionLimit: 10 }); // Example: GET users with optional status filter app.get('/users', async (req, res) => { const { status } = req.query; const [rows] = await pool.execute( status ? 'SELECT id, name, email, status FROM users WHERE status = ?' : 'SELECT id, name, email, status FROM users', status ? [status] : [] ); res.json({ rows }); }); // Example: POST to create a user app.post('/users', async (req, res) => { const { name, email } = req.body; const [result] = await pool.execute( 'INSERT INTO users (name, email, created_at) VALUES (?, ?, NOW())', [name, email] ); res.json({ insertId: result.insertId }); }); app.listen(process.env.PORT || 3000); ``` Store environment variables in your deployment platform (Railway: Variables tab, Render: Environment tab): `MYSQL_HOST`, `MYSQL_USER`, `MYSQL_PASSWORD`, `MYSQL_DATABASE`, and `BUBBLE_API_KEY` (a random string you generate and use in Bubble's Private header). After deployment, your proxy URL will be something like `https://your-app.up.railway.app`. Test it with a REST client (curl or Postman) before connecting Bubble.

mysql-proxy.mjs
1import express from 'express';
2import mysql2 from 'mysql2/promise';
3
4const app = express();
5app.use(express.json());
6
7const API_KEY = process.env.BUBBLE_API_KEY;
8app.use((req, res, next) => {
9 if (req.headers['x-api-key'] !== API_KEY) {
10 return res.status(401).json({ error: 'Unauthorized' });
11 }
12 next();
13});
14
15const pool = mysql2.createPool({
16 host: process.env.MYSQL_HOST,
17 user: process.env.MYSQL_USER,
18 password: process.env.MYSQL_PASSWORD,
19 database: process.env.MYSQL_DATABASE,
20 waitForConnections: true,
21 connectionLimit: 10
22});
23
24app.get('/users', async (req, res) => {
25 const { status } = req.query;
26 const [rows] = await pool.execute(
27 status ? 'SELECT id, name, email, status FROM users WHERE status = ?' : 'SELECT id, name, email, status FROM users',
28 status ? [status] : []
29 );
30 res.json({ rows });
31});
32
33app.post('/users', async (req, res) => {
34 const { name, email } = req.body;
35 const [result] = await pool.execute(
36 'INSERT INTO users (name, email, created_at) VALUES (?, ?, NOW())',
37 [name, email]
38 );
39 res.json({ insertId: result.insertId });
40});
41
42app.listen(process.env.PORT || 3000);

Pro tip: Always use `pool.execute()` with parameterized `?` placeholders instead of `pool.query()` with string concatenation. `execute()` uses prepared statements and prevents SQL injection. Design one endpoint per business operation (GET /users, POST /orders) rather than a generic /query endpoint that accepts raw SQL from Bubble — generic SQL endpoints are a serious security risk.

Expected result: The proxy is deployed and accessible via HTTPS. Testing with curl returns JSON rows from your MySQL database. The API key header is required and returns 401 without it.

4

Configure Bubble API Connector for the custom REST proxy

In Bubble's API Connector, click 'Add another API'. Name it 'MySQL API'. Set the base URL to your deployed proxy URL (e.g. `https://your-app.up.railway.app`). Add a shared header: - Name: `x-api-key` - Value: your BUBBLE_API_KEY value - Check the **Private** checkbox Add another shared header: - Name: `Content-Type` - Value: `application/json` Add your first call for reads: - **Name**: `Get Users` - **Method**: GET - **URL**: `/users` - **Parameters**: add a query parameter `status` (text, dynamic, optional) - **Use as**: Data (for repeating groups) Add a call for writes: - **Name**: `Create User` - **Method**: POST - **URL**: `/users` - **Body**: JSON `{"name": "{{name}}", "email": "{{email}}"}` - **Use as**: Action For the Initialize call on 'Get Users': click Initialize call → Run (no dynamic params needed for a basic GET). If your MySQL table has data, Bubble will detect the field schema from the `rows` array in the response. If the table is empty, insert a test row first. For the Initialize call on 'Create User': provide test values `name = 'Test'` and `email = 'test@test.com'`. The proxy should insert the row and return `{"insertId": 1}`. Click Save. This API call configuration looks like: ```json { "method": "GET", "url": "https://your-app.up.railway.app/users", "headers": { "x-api-key": "<private>", "Content-Type": "application/json" }, "params": { "status": "<dynamic-optional>" } } ```

mysql-proxy-get-call.json
1{
2 "method": "GET",
3 "url": "https://your-app.up.railway.app/users",
4 "headers": {
5 "x-api-key": "<private>",
6 "Content-Type": "application/json"
7 },
8 "params": {
9 "status": "<dynamic-optional>"
10 }
11}

Pro tip: If the Initialize call returns 'There was an issue setting up your call', check Bubble's Logs tab → API Connector logs for the full response. A 401 means the x-api-key header is wrong. A 500 from your proxy often means a MySQL connection error — check your proxy's deployment logs for the specific MySQL error message.

Expected result: Both 'Get Users' and 'Create User' API Connector calls are initialized and return expected data. Bubble has detected the field schema from the MySQL rows in the response. The x-api-key header is marked Private.

5

Build Bubble workflows for reads and writes

With initialized API calls, connect them to your Bubble UI. **Reads (Repeating Group)**: Set a Repeating Group's Data source to 'Get Users' (or your MySQL GET call). The data type should match the detected API response fields. Each row in the repeating group can display `current cell's Get Users result's name`, `current cell's Get Users result's email`, etc. For filtered reads, pass a dynamic parameter. For example, a Dropdown filter for user status: set the `status` parameter in the Data source to `Dropdown Filter Status's value`. Bubble re-fetches the API call whenever the parameter value changes. **Writes (Backend Workflow for paid plan)**: For INSERT, UPDATE, DELETE operations, use a Bubble Backend Workflow rather than a direct client-side API call. In the Backend Workflows section, create a new workflow called 'Create MySQL User'. Add parameters: `name` (text), `email` (text). Add action: Plugins → API Connector → Create User. Map `name = Parameter: name`, `email = Parameter: email`. On the client-side, trigger this Backend Workflow from a button click: Workflow → Schedule API Workflow → 'Create MySQL User' → pass `name = Input Name's value`, `email = Input Email's value`. **For client-side writes on free plan**: If you're on Bubble's free plan and cannot use Backend Workflows, you can make client-side POST calls with the Private header. The key stays hidden in transit. However, Backend Workflows are still preferred for writes because they run server-side and don't appear in browser DevTools. RapidDev's team has connected Bubble to dozens of MySQL databases across LAMP stacks, PlanetScale, and custom REST proxies — book a free scoping call at rapidevelopers.com/contact if you'd like help designing your specific integration.

Pro tip: Design your proxy endpoints around business operations, not raw tables. Instead of a generic GET /users that returns all columns, create GET /user-dashboard-data that returns only the columns your Bubble repeating group needs. This reduces data transfer, improves response speed, and limits what Bubble workflows can accidentally expose.

Expected result: A Bubble Repeating Group displays data from the MySQL database via the API Connector. A button workflow triggers a Backend Workflow that creates a new row in MySQL. The MySQL database is updated as confirmed in a database viewer or via a GET call.

6

Add error handling and test write security

MySQL write operations can fail in ways that Bubble won't surface as visible errors by default. Add explicit error handling to your Bubble workflows. For Backend Workflows that call MySQL write endpoints: after the API Connector action, add a conditional branch: if the API response status is not 200 (or if the `insertId` field is empty for POST calls), show an error message to the user and log the failure to a Bubble data type for debugging. For PlanetScale specifically: the HTTP API returns errors as HTTP 400 with a JSON body containing an error message. Check Bubble's 'API response error' event in the workflow to catch these. Test SQL injection protection: 1. In your Bubble app's input field, enter a value like `' OR '1'='1` and submit the form 2. Check your MySQL database — the submitted value should appear literally as a row field value, NOT cause additional rows to be returned 3. If using PlanetScale's HTTP API with parameterized queries, this is handled automatically 4. If using a custom proxy, verify your proxy uses `pool.execute()` with `?` placeholders — if it uses string concatenation, the injection test will return unexpected results Finally, add Bubble privacy rules on any data types that store MySQL-sourced data cached in Bubble's native database. Go to Data tab → Data types → [Your Type] → Privacy → add a rule restricting reads to the current user.

Pro tip: Monitor WU consumption in your Bubble app's Logs tab → Workload tab. Every API Connector call consumes WUs. If your app makes many reads per page load (e.g. multiple repeating groups each with their own API call), consider combining them into one endpoint on the proxy side to reduce the number of API Connector calls and WU cost.

Expected result: Error handling is added to write workflows. SQL injection test confirms parameterized queries are working correctly — injected values are stored literally, not executed as SQL. Bubble privacy rules are configured for any native data types that cache MySQL data.

Common use cases

Bubble frontend for an existing LAMP/PHP application

Your company has an existing PHP application with a MySQL database containing years of production data. Build a Bubble frontend or admin panel on top of it by pointing Bubble's API Connector at the existing PHP API endpoints — many LAMP apps already have REST-ish endpoints that return JSON. No new database, no data migration.

Bubble Prompt

Build a Bubble admin dashboard that calls existing PHP REST endpoints at api.yourapp.com/users and api.yourapp.com/orders, displays the data in repeating groups, and allows status updates via PATCH calls authorized with an internal API key in a Private header.

Copy this prompt to try it in Bubble

New SaaS application on PlanetScale

Start a new Bubble SaaS project using PlanetScale as the scalable MySQL backend. Use PlanetScale's branching feature (like Git branches for your database schema) during development, then promote the schema to production. Bubble reads and writes via the HTTP API, and PlanetScale handles scaling, backups, and high availability automatically.

Bubble Prompt

Create a PlanetScale database with a 'subscriptions' table and an 'api_usage' table. Configure Bubble's API Connector to POST SQL queries to PlanetScale's HTTP API endpoint and display subscription data in a user dashboard repeating group.

Copy this prompt to try it in Bubble

Data sync between WordPress and Bubble

If you run a WordPress site on MySQL and want to display or manage WordPress content in a Bubble app (orders from WooCommerce, posts, user accounts), deploy a simple REST endpoint that queries the WordPress database and returns JSON. Bubble reads this data without touching WordPress's native PHP — useful for building custom admin panels or mobile-app APIs on top of WordPress data.

Bubble Prompt

Deploy a small Node.js REST endpoint that connects to the WordPress MySQL database and exposes GET /wp-posts and GET /wc-orders endpoints with an API key. Connect Bubble's API Connector to these endpoints to build a custom content management dashboard.

Copy this prompt to try it in Bubble

Troubleshooting

API Connector call returns 'There was an issue setting up your call' during Initialize

Cause: The API endpoint returned a non-200 status code during initialization, or the response body was not valid JSON. For PlanetScale, this commonly means the Authorization header is formatted incorrectly. For custom proxies, it usually means a connection error to MySQL.

Solution: Check Bubble's Logs tab → API Connector for the raw response status and body. For PlanetScale: verify the Authorization header is exactly `Bearer {token_id}:{token_value}` with no spaces around the colon. For custom proxies: check your deployment logs for MySQL connection errors (wrong host, wrong credentials, MySQL not accepting connections from your proxy's IP).

PlanetScale HTTP API returns HTTP 400 with a JSON error body

Cause: The SQL query syntax is invalid, a referenced table or column doesn't exist, or the args array length doesn't match the number of ? placeholders in the query.

Solution: Check the error message in the PlanetScale 400 response body (visible in Bubble's Logs tab). Common issues: table name misspelled, missing column in INSERT (NOT NULL column with no value provided), or args array has fewer items than ? placeholders. Test your query directly in PlanetScale Console's SQL editor before adding it to Bubble.

Initialize call succeeds but Bubble shows no fields detected from the response

Cause: The response structure nests the data inside a key (e.g. `{"rows": [...]}`) but Bubble is looking for a top-level array. Or the `rows` array is empty because the table has no data.

Solution: In the API Connector Initialize call response, check how Bubble displays the data structure. If the response is `{"rows": [{...}]}`, Bubble should detect `rows` as a list — access fields as `result's rows[0]'s fieldname`. If the rows array is empty, insert a test row first, then re-initialize. For PlanetScale, the response always has a `rows` key — use that in your Bubble data bindings.

Write operations from client-side Bubble workflows appear in browser DevTools network requests

Cause: Client-side Bubble API Connector calls (those made directly in page workflows or button click workflows, not Backend Workflows) are visible in the browser's network panel even with Private headers — Private hides the header VALUE, but the request itself is visible.

Solution: Move INSERT, UPDATE, and DELETE operations to Bubble Backend Workflows (paid plan). Backend Workflows run on Bubble's servers, not in the user's browser, so they don't appear in the user's DevTools. From client-side, trigger them using 'Schedule API Workflow' action. This also prevents users from potentially manipulating request bodies through browser tools.

PlanetScale deprecated MySQL binary protocol message appears in documentation

Cause: PlanetScale deprecated the MySQL binary protocol (port 3306 connections) for new connections in favor of its HTTP API. Any tutorial or connection string format using host:port MySQL connections is outdated for PlanetScale.

Solution: Use only PlanetScale's HTTP API endpoint (`https://{region}.connect.psdb.cloud/api/v1/execute`) for all Bubble integrations. If you have an existing PlanetScale connection using the MySQL binary protocol that you want to maintain, check PlanetScale's current documentation for your options — the HTTP API is the supported path going forward.

Best practices

  • Always use parameterized queries with ? placeholders — never concatenate Bubble input values directly into SQL strings. SQL injection is a real risk even in internal apps, and parameterized queries are the only reliable protection.
  • Keep MySQL credentials (password, connection string) exclusively in server-side environment variables — in your custom proxy's deployment platform settings or PlanetScale's managed service, never in Bubble's API Connector fields without the Private checkbox.
  • Route write operations (INSERT, UPDATE, DELETE) through Bubble Backend Workflows (paid plan) rather than client-side API calls. This keeps write calls off the browser's network panel and reduces the risk of request manipulation.
  • Design REST proxy endpoints around business operations, not raw SQL pass-through. A generic `/query` endpoint that accepts any SQL from Bubble is a security risk — even with authentication, it allows any authenticated user to query any table. Use specific endpoints like GET /orders, POST /orders, PATCH /orders/{id}.
  • Add explicit error handling to MySQL write workflows in Bubble. API Connector calls that fail return an error state — add conditional branches after write actions to detect failures and show users meaningful error messages rather than silent failures.
  • For PlanetScale, test SQL queries in PlanetScale's Console SQL editor before adding them to Bubble's API Connector. The Console shows exact error messages and query execution plans, which are much easier to debug than API Connector initialization failures.
  • Cache frequently-read MySQL data in Bubble's native database when data doesn't change in real time — use a scheduled Backend Workflow to sync MySQL data to Bubble's database every few minutes. This reduces the number of API calls per page load and lowers WU consumption.
  • Monitor WU consumption for MySQL-heavy pages in Bubble's Logs tab → Workload section. Each API Connector call consumes WUs. Combine multiple data needs into one REST endpoint call rather than making five separate API calls per page.

Alternatives

Frequently asked questions

Can I connect Bubble directly to MySQL without any proxy or external service?

No. Bubble runs in the browser, and browsers cannot open TCP connections to MySQL's port 3306. Every Bubble-MySQL integration requires an HTTPS REST layer in between. Your two main options are PlanetScale's HTTP API (managed, no server required) or a custom REST proxy you deploy to a cloud platform.

Is PlanetScale still a good option after they deprecated MySQL binary protocol connections?

Yes — PlanetScale deprecated the MySQL binary protocol for NEW connections but provides the HTTP API as the modern replacement, which is actually easier to use with Bubble's API Connector than direct MySQL connections ever were. Existing binary protocol connections are grandfathered in, but all new Bubble integrations with PlanetScale should use the HTTP API.

Do I need a paid Bubble plan to connect to MySQL?

For read operations (GET calls), the free plan works — client-side API Connector calls work on any Bubble plan. For write operations (INSERT, UPDATE, DELETE), you technically CAN make those from client-side workflows on the free plan, but it's strongly recommended to use Backend Workflows (paid plan) to keep write operations off the browser's network panel. There's no hard technical requirement for a paid plan, but the security posture is better with Backend Workflows.

How do I prevent SQL injection in my custom REST proxy?

Use parameterized queries exclusively. In Node.js with mysql2, use `pool.execute('SELECT * FROM users WHERE id = ?', [userId])` — the `?` placeholder is replaced by the driver with a properly escaped value, not by string concatenation. In PHP with PDO, use `$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?')` followed by `$stmt->execute([$userId])`. Never build SQL strings by concatenating values from Bubble: `'SELECT * FROM users WHERE id = ' + bubbleParam` is a SQL injection vulnerability.

How do I handle MySQL errors in Bubble workflows?

API Connector calls that fail return an error state accessible in Bubble's workflow conditional logic. After a write action (Create User, etc.), add a conditional 'only when result's insertId is empty' branch to show an error popup. For PlanetScale, HTTP 400 errors include a JSON body with an error message — check Bubble's Logs tab → API Connector calls to see the exact error message during testing. For custom proxies, ensure your proxy returns structured JSON error responses (e.g. {"error": "Duplicate email"}) rather than plain text errors, so Bubble can parse them.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Integrations are where projects stall

We wire Bubble integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.