Most MySQL connectors are read-only. Learn how to push edits from Google Sheets back to MySQL, covering Apps Script write-back code, conflict resolution, and no-code connectors that handle it automatically.
Most tools that connect Google Sheets to MySQL only go one direction: they pull data into Sheets so you can read it. The moment an operations manager edits a cell and expects that change to reflect in the database, they hit a wall. This guide covers every realistic path to true write-back: from DIY Apps Script to connectors that handle it without code.
Why Write-Back Is Harder Than Read-Only
Read-only sync is conceptually simple: run a SELECT query, dump the rows into a spreadsheet. Write-back inverts every assumption that makes that easy.
When you read data, the database is the single source of truth. When you write back from Sheets, you now have two potential sources of truth that can diverge at any moment. Someone edits a cell in Sheets at 2:03 PM. A backend process updates the same row in MySQL at 2:04 PM. Which value wins?
Beyond conflict resolution, you face three structural challenges:
Primary key identification. An INSERT statement without a primary key creates a new row. An UPDATE statement without a primary key updates nothing, or everything, if you forget a WHERE clause. Sheets has no concept of primary keys. That mapping has to be imposed externally.
Data type coercion. Google Sheets stores everything as strings, numbers, or booleans. MySQL has DATETIME, DECIMAL, TINYINT(1), ENUM, JSON, and dozens of other types. The number 1 in a Sheets cell could map to an integer, a boolean, a foreign key, or a price. Without explicit type mapping, INSERT statements fail silently or corrupt data.
Change detection. To avoid overwriting the entire table on every sync, your write-back logic needs to know which rows changed. Sheets has no built-in change log. You either compare every cell on every run (slow), use onEdit triggers (unreliable for bulk edits), or maintain a separate "dirty" column that users toggle manually (error-prone).
The Core Problems You Have to Solve
Before writing a single line of code or installing a connector, decide how you will handle each of these:
Upsert vs. insert vs. update. An upsert (INSERT ... ON DUPLICATE KEY UPDATE in MySQL) is usually the right answer for operational data. It inserts new rows and updates existing ones in a single statement. But it requires a unique key in both Sheets and MySQL.
Row deletion. If a user deletes a row in Sheets, should that row be deleted in MySQL? Soft delete (set a deleted_at column) or hard delete? Most teams do not want hard deletes triggered from a spreadsheet.
Conflict resolution strategy. Common approaches: last-write-wins (simplest, most dangerous), version column (compare a updated_at timestamp before writing), manual review queue (write to a staging table first, approve before applying to production).
Audit trail. For compliance and debugging, you need a record of who changed what and when. MySQL's binary log captures this at the database level, but you also want a Sheets-side log.
Apps Script Write-Back: What the Code Actually Looks Like
Apps Script can write to MySQL using the JDBC service. Here is the minimum viable implementation for an upsert:
function writeBackToMySQL() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Orders');
var data = sheet.getDataRange().getValues();
var headers = data[0];
// Column index map - fragile if column order changes
var idCol = headers.indexOf('order_id');
var statusCol = headers.indexOf('status');
var updatedCol = headers.indexOf('updated_at');
var conn = Jdbc.getConnection(
'jdbc:mysql://your-host:3306/your_db',
'username',
'password'
);
var stmt = conn.prepareStatement(
'INSERT INTO orders (order_id, status, updated_at) ' +
'VALUES (?, ?, ?) ' +
'ON DUPLICATE KEY UPDATE status = VALUES(status), updated_at = VALUES(updated_at)'
);
conn.setAutoCommit(false);
try {
for (var i = 1; i < data.length; i++) {
var row = data[i];
if (!row[idCol]) continue; // skip rows without a primary key
stmt.setInt(1, row[idCol]);
stmt.setString(2, row[statusCol]);
// Convert Sheets date serial to MySQL DATETIME - this is where most bugs live
var dateVal = row[updatedCol];
stmt.setString(3, dateVal instanceof Date
? Utilities.formatDate(dateVal, 'UTC', 'yyyy-MM-dd HH:mm:ss')
: dateVal
);
stmt.addBatch();
}
stmt.executeBatch();
conn.commit();
Logger.log('Write-back complete.');
} catch (e) {
conn.rollback();
Logger.log('Error: ' + e.message);
} finally {
stmt.close();
conn.close();
}
}This code does work. But notice what it does not handle: it has no change detection (it re-upserts every row on every run), no conflict resolution (last write wins, silently), no audit trail, no column order safety beyond a single indexOf call, and no row deletion logic. A production-grade version of this script is several hundred lines. Every edge case you add increases the surface area for bugs.
The 6-minute execution limit (or 30 minutes on Google Workspace) is also a hard constraint. On tables with more than a few thousand rows, batch upserts time out.
Connectors That Support Write-Back
Several third-party tools handle the write-back problem without requiring you to maintain custom scripts.
brooked.io is built specifically for MySQL-to-Sheets and back. It handles upserts via a configurable primary key column, type mapping between Sheets and MySQL data types, and provides an audit log of every change synced. You configure which columns are writable and which direction data flows. The interface is a no-code form rather than a code editor. Scheduling is available for automatic two-way sync on a set interval.
Coefficient supports write-back through what it calls "two-way sync." The setup requires mapping a primary key column and configuring conflict behavior. It works well for CRM-style data where a salesperson edits a record in Sheets and the change needs to propagate to a backend database. The product is heavier (it supports many data sources) so the MySQL-specific configuration is less streamlined.
Byteline provides triggered write-back: when a row in Sheets changes, it fires a workflow that updates the connected database. This works well for event-driven updates (one row at a time) but is slower for bulk updates because each row triggers a separate workflow execution.
Comparison Table
| Tool | Write-Back | Upsert Support | Conflict Resolution | Audit Trail | Scheduling | No-Code |
|---|---|---|---|---|---|---|
| Apps Script (DIY) | Yes | Manual | Manual | Manual | Yes (trigger) | No |
| brooked.io | Yes | Yes | Configurable | Yes | Yes | Yes |
| Coefficient | Yes | Yes | Last-write-wins | Limited | Yes | Yes |
| Byteline | Yes (row-level) | Partial | None | Limited | Event-driven | Yes |
| Zapier | Insert only | No | None | No | No | Yes |
| Most read-only connectors | No | N/A | N/A | N/A | N/A | Yes |
Best Practices for Safe Write-Back
Always expose the primary key as a locked column. In Sheets, lock the column that holds the primary key so users cannot accidentally edit or delete it. A missing or modified primary key turns an UPDATE into an INSERT, creating duplicate rows in the database.
**Add a sheets_modified boolean column.** Ask users to check a box in a dedicated column when they have edited a row. Your write-back logic reads only rows where this column is TRUE, then resets it after a successful sync. This is manual but dramatically reduces write volume and makes it obvious which rows are pending sync.
Use a staging table for sensitive data. Instead of writing directly to your production table, write to an identical staging table. A scheduled SQL job reviews and applies the changes during a maintenance window. This adds latency but prevents a typo in Sheets from corrupting live data.
Log every write operation. Write a separate log row (timestamp, user email via Session.getActiveUser().getEmail(), row ID, old value, new value) for every upsert. Store this log either in a separate Sheets tab or in a MySQL audit table.
Test with a non-production database first. JDBC connections from Apps Script are real database connections with real write permissions. Use a staging or development database until your write-back logic is thoroughly tested.
Troubleshooting Common Write-Back Errors
"Duplicate entry for key PRIMARY" This occurs when your upsert logic treats a row as an INSERT when the primary key already exists, and the statement is not using ON DUPLICATE KEY UPDATE. Check that your SQL statement uses the correct upsert syntax for MySQL.
"Data truncated for column X at row Y" A Sheets value is too long or the wrong type for the MySQL column definition. Check the column's maximum length (VARCHAR) or precision (DECIMAL). This often happens with date values formatted as strings that MySQL cannot parse.
"Communications link failure" mid-batch The JDBC connection timed out during a long batch operation. Reduce batch size and commit more frequently. For large datasets, consider splitting into multiple script runs using Script Properties to track progress.
Write-back succeeds but changes do not appear in MySQL Verify that conn.setAutoCommit(false) is paired with conn.commit(). If an exception is caught and conn.rollback() is called, the transaction is discarded. Add explicit logging before and after the commit.
Changes in Sheets overwrite newer database values You have a conflict where the database was updated after the last Sheets sync. Add a version or updated_at column to your SQL table. In your upsert statement, add a WHERE condition: only update if the database timestamp is older than the Sheets timestamp.
Bottom Line
Writing data from Google Sheets back to MySQL is solvable but requires deliberate choices about primary keys, conflict resolution, change detection, and audit trails. Apps Script gives you maximum control at the cost of significant code complexity. Third-party connectors like brooked.io eliminate the code but require trust in a third-party service. For most operational teams, the no-code connector path delivers working two-way sync faster and more reliably than maintaining custom scripts.
Get Started with Two-Way Sync
Connect your MySQL database to Google Sheets with brooked.io, configure write-back in minutes without writing a line of code. Set your primary key column, choose your conflict behavior, and schedule automatic two-way sync.
Related articles:
- Does Google Sheets Have a MySQL Connector?
- How to Fix 'Exceeded Maximum Execution Time' When Pulling MySQL Data into Google Sheets
- Zapier vs Direct Connector for MySQL to Google Sheets
Frequently asked questions
Can I use Google Sheets as a frontend for a MySQL database?
Yes, with the right tooling. Sheets can function as a lightweight data entry interface for MySQL. You pull records into Sheets, users edit them, and write-back pushes changes to the database. This works best for operational tables (order status, contact records, inventory) where data volumes are manageable and the edit workflow is straightforward. It is not a replacement for a proper application UI when you need row-level permissions, complex validation, or high write volumes.
What happens if two people edit the same row at the same time?
Without explicit conflict resolution, last write wins. The write-back that runs last overwrites all previous changes. To prevent this, add an updated_at column to your MySQL table and include it in the Sheets sync. Before writing back, compare the current database timestamp with the value stored in Sheets. If the database is newer, abort the update and flag the row for manual review.
Do I need direct MySQL access to set up write-back?
Yes, for any method that uses JDBC (Apps Script or most direct connectors). You need a database user with INSERT and UPDATE privileges, and the MySQL host must be accessible from the internet or via an SSH tunnel. If your database is behind a firewall, you may need to whitelist the IP addresses of the connector service.
Is write-back safe to use on production databases?
With the right safeguards, yes. Use a database user with only the permissions needed (no DROP, no DELETE unless required). Enable MySQL's binary log for a full audit trail at the database level. Test on a staging environment first. Consider staging table architecture for sensitive data. The risk is not the write-back mechanism itself. It is the lack of validation on what Sheets users can enter.
How do I handle rows that are deleted in Sheets?
The safest approach is soft delete: add a deleted boolean column to the Sheets sync. When a row is removed in Sheets, the connector sets deleted = true in MySQL rather than running a DELETE statement. Hard deletes from Sheets require explicit confirmation logic to prevent accidental data loss.
Ready to connect your data to Google Sheets?
Brooked's free tier covers 100 imports per month with AI Analyst included, no credit card required.
Install Brooked free →

