Connect MySQL, PostgreSQL, SQL Server, or Redshift to Google Sheets automatically. Covers JDBC, Apps Script, Python, and no-code connectors with exact connection strings.
Google Sheets has no built-in database connector. That is not a bug or an oversight. It is the architecture, and 118,000 Stack Overflow views on that single question confirm it catches people off guard every time. Here is the direct answer: you can connect a database to Google Sheets automatically using Google Apps Script with JDBC (free, requires code), a Python script with the gspread library (free, requires code), or a third-party connector like brooked.io, Coefficient, or Skyvia (no code, paid). This guide covers all methods with exact connection strings for MySQL, PostgreSQL, SQL Server, Cloud SQL, RDS, Supabase, and PlanetScale.
The Problem: Google Sheets Has No Database Tab
"I spent three hours looking for a native Google Sheets database connector before I realized it just doesn't exist." (Developer, fintech startup (Stack Overflow, 118K views))
"Apps Script JDBC looked perfect until I realized our database was behind a VPC and whitelisting Google's IP ranges was 'not going to happen' according to our security team." (Data analyst, healthcare company)
"We used CSV exports for six months. Then someone changed a column name in the database and every formula in our Sheets broke, silently. We didn't notice for three weeks." (Operations lead, logistics company)
These three failure modes (discovery shock, infrastructure friction, and silent schema breakage) explain why connecting a database to Google Sheets is one of the most-searched data integration problems on the internet despite having a handful of documented solutions.
The underlying issue: Google Sheets was designed as a collaborative spreadsheet, not a database client. It has no concept of a connection string, a database driver, or a read replica. Every method for connecting a database to Sheets is a workaround. The question is which workaround fits your situation.
Method 1: CSV Import (Why It Doesn't Scale)
The first thing every developer tries: export a query result as CSV from your database client (DBeaver, pgAdmin, TablePlus), drag it into Google Sheets, and call it done.
Why it works for one-time pulls:
It is immediate, requires no credentials in Sheets, and produces a clean flat dataset. For a quarterly audit, an ad hoc analysis, or a one-off data hand-off, CSV export is completely appropriate.
Why it fails for ongoing use:
- Immediately stale. The moment you import the CSV, it starts diverging from the live database. There is no sync, no refresh, no connection.
- Manual every time. Someone has to run the query, export the file, and re-import it on whatever cadence the report requires. This is not automation. It is scheduled manual work.
- Breaks formulas. If the CSV has a different row count or column order than last time, every
VLOOKUP,INDEX/MATCH, and named range in your sheet breaks. - No query parameterization. You cannot filter by date range or dynamic parameters in a CSV export. You get the whole table or nothing.
The verdict: Use CSV import for one-off analysis. For anything that runs more than once, use one of the methods below.
Method 2: Google Apps Script + JDBC
Google Apps Script includes a JDBC service that can connect directly to MySQL, PostgreSQL, Microsoft SQL Server, and Cloud SQL databases. This is the only native, free method for live database connectivity in Google Sheets.
How it works: Apps Script runs on Google's servers and makes outbound TCP connections to your database on the standard port. It supports parameterized queries, result set iteration, and writing data back to Sheets.
Basic pattern:
function importFromDatabase() {
const conn = Jdbc.getConnection("YOUR_JDBC_URL", "db_user", "db_password");
const stmt = conn.prepareStatement(
"SELECT order_id, customer, revenue, created_at FROM orders WHERE created_at >= ? ORDER BY created_at DESC LIMIT 1000"
);
stmt.setString(1, getLastWeekDate()); // parameterized query
const rs = stmt.executeQuery();
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Orders");
sheet.clearContents();
let row = 1;
while (rs.next()) {
sheet.getRange(row, 1, 1, 4).setValues([[
rs.getString("order_id"),
rs.getString("customer"),
rs.getDouble("revenue"),
rs.getString("created_at")
]]);
row++;
}
rs.close();
stmt.close();
conn.close();
}MySQL Connection String
jdbc:mysql://YOUR_HOST:3306/YOUR_DATABASE
Standard MySQL (self-hosted or cloud VM):
const conn = Jdbc.getConnection( "jdbc:mysql://db.yourcompany.com:3306/production", "sheets_reader", "your_password" );
MySQL with SSL required:
const conn = Jdbc.getConnection( "jdbc:mysql://db.yourcompany.com:3306/production?useSSL=true&requireSSL=true", "sheets_reader", "your_password" );
Amazon RDS MySQL:
const conn = Jdbc.getConnection( "jdbc:mysql://your-instance.abc123.us-east-1.rds.amazonaws.com:3306/production", "sheets_reader", "your_password" );
PlanetScale (MySQL-compatible): PlanetScale requires SSL and uses a unique URL format from their dashboard:
const conn = Jdbc.getConnection( "jdbc:mysql://aws.connect.psdb.cloud/your-db?useSSL=true&requireSSL=true", "your_username", "your_planetscale_password" );
Note: PlanetScale does not support traditional TCP connections from Google's IP ranges in all configurations. Test with their connection string from the dashboard under Settings → Passwords.
PostgreSQL Connection String
jdbc:postgresql://YOUR_HOST:5432/YOUR_DATABASE
Standard PostgreSQL:
const conn = Jdbc.getConnection( "jdbc:postgresql://db.yourcompany.com:5432/production", "sheets_reader", "your_password" );
PostgreSQL with SSL:
const conn = Jdbc.getConnection( "jdbc:postgresql://db.yourcompany.com:5432/production?ssl=true&sslmode=require", "sheets_reader", "your_password" );
Amazon RDS PostgreSQL:
const conn = Jdbc.getConnection( "jdbc:postgresql://your-instance.abc123.us-east-1.rds.amazonaws.com:5432/production", "sheets_reader", "your_password" );
Google Cloud SQL (PostgreSQL): Cloud SQL requires the Cloud SQL Auth Proxy or a public IP with authorized networks. For Apps Script, use the public IP with SSL:
const conn = Jdbc.getConnection( "jdbc:postgresql://YOUR_CLOUD_SQL_PUBLIC_IP:5432/your_database?ssl=true", "sheets_reader", "your_password" );
Alternatively, use the Apps Script-specific Cloud SQL connection format:
const conn = Jdbc.getCloudSqlConnection( "jdbc:google:mysql://project:region:instance/database", "sheets_reader", "your_password" );
Supabase (PostgreSQL): Supabase exposes a direct PostgreSQL connection. Get your connection string from the Supabase dashboard under Settings → Database → Connection string:
const conn = Jdbc.getConnection( "jdbc:postgresql://db.yourprojectref.supabase.co:5432/postgres?ssl=true", "postgres", "your_supabase_db_password" );
Note: Use the direct connection string, not the connection pooler (port 6543), as Apps Script JDBC does not support PgBouncer's pooling protocol reliably.
Amazon Redshift: Redshift uses a PostgreSQL-compatible JDBC driver:
const conn = Jdbc.getConnection( "jdbc:redshift://your-cluster.abc123.us-east-1.redshift.amazonaws.com:5439/dev", "sheets_reader", "your_password" );
Ensure your Redshift cluster's VPC security group allows inbound traffic from Google's Apps Script IP ranges (see Google's published range list).
SQL Server Connection String
jdbc:sqlserver://YOUR_HOST:1433;databaseName=YOUR_DATABASE
Standard SQL Server:
const conn = Jdbc.getConnection( "jdbc:sqlserver://db.yourcompany.com:1433;databaseName=Production", "sheets_reader", "your_password" );
SQL Server with Windows Auth (not supported in Apps Script). Apps Script JDBC does not support Windows/Kerberos authentication. You must use SQL Server authentication (a username and password for a SQL login).
Azure SQL Database:
const conn = Jdbc.getConnection( "jdbc:sqlserver://yourserver.database.windows.net:1433;databaseName=YourDB;encrypt=true;trustServerCertificate=false;loginTimeout=30", "sheets_reader@yourserver", "your_password" );
Amazon RDS SQL Server:
const conn = Jdbc.getConnection( "jdbc:sqlserver://your-instance.abc123.us-east-1.rds.amazonaws.com:1433;databaseName=Production", "sheets_reader", "your_password" );
IP Whitelisting: The Unavoidable Problem
Every database method above requires your database to accept inbound connections from Google's Apps Script servers. Google publishes its IP ranges at https://www.gstatic.com/ipranges/goog.json, but the list is large (hundreds of CIDR blocks) and changes periodically.
If your database is behind a VPC, a private subnet, or a corporate firewall, Apps Script JDBC will fail to connect. Your options:
- Use a public IP with allowlisted CIDR blocks, workable, but ongoing maintenance as Google updates its ranges.
- Use a jump server or proxy. Apps Script calls the proxy, which relays to the database. Adds infrastructure complexity.
- Use Google Cloud SQL with the
Jdbc.getCloudSqlConnection()method, which connects through Google's internal network without public IP exposure. - Use a third-party connector (see Method 4) that runs an agent inside your VPC, eliminating the IP whitelist problem entirely.
Hard limits of Apps Script JDBC:
- 6-minute maximum execution time. Large queries or slow connections hit this wall. Workaround: paginate with LIMIT/OFFSET and use multiple trigger runs.
- Credentials in plaintext. Passwords stored in Script Properties are base64-accessible to anyone with editor access to the script. Use a dedicated read-only database user with minimal privileges.
- No connection pooling. Every script run opens and closes a connection. High-frequency runs can exhaust your database's connection limit.
- No schema change detection. If a column is renamed or dropped, your script fails silently on the next run. Build error notifications into every script.
Method 3: Python + gspread
For teams with Python infrastructure: a data engineering team, a backend with Python scripts, or an analyst who prefers Python to JavaScript. The gspread library provides a clean interface for writing data into Google Sheets from any Python environment.
Setup:
pip install gspread google-auth sqlalchemy psycopg2-binary pymysql pyodbc
Create a Google Cloud service account, download the JSON credentials file, and share your Google Sheet with the service account's email address.
Basic pattern:
import gspread
from google.oauth2.service_account import Credentials
import sqlalchemy
import pandas as pd
# Database connection (using SQLAlchemy)
engine = sqlalchemy.create_engine(
"postgresql+psycopg2://sheets_reader:[email protected]:5432/production"
)
# Run query
with engine.connect() as conn:
df = pd.read_sql(
"SELECT order_id, customer, revenue, created_at FROM orders WHERE created_at >= NOW() - INTERVAL '7 days' ORDER BY created_at DESC",
conn
)
# Write to Google Sheets
scopes = ["https://www.googleapis.com/auth/spreadsheets"]
creds = Credentials.from_service_account_file("service_account.json", scopes=scopes)
client = gspread.authorize(creds)
sheet = client.open("Weekly Orders Report").worksheet("Orders")
sheet.clear()
sheet.update([df.columns.tolist()] + df.values.tolist())
print(f"Wrote {len(df)} rows to Google Sheets.")SQLAlchemy connection strings for common databases:
# MySQL "mysql+pymysql://user:password@host:3306/database" # PostgreSQL "postgresql+psycopg2://user:password@host:5432/database" # SQL Server "mssql+pyodbc://user:password@host:1433/database?driver=ODBC+Driver+17+for+SQL+Server" # Redshift (PostgreSQL-compatible) "postgresql+psycopg2://user:[email protected]:5439/dev" # Supabase "postgresql+psycopg2://postgres:[email protected]:5432/postgres"
Scheduling the Python script:
Run this script on a cron job (Linux/Mac), Windows Task Scheduler, or a managed scheduler:
# Run every Monday at 6 AM UTC 0 6 * * 1 /usr/bin/python3 /home/user/scripts/db_to_sheets.py >> /var/log/sheets_sync.log 2>&1
Or deploy it as a cloud function (AWS Lambda, Google Cloud Functions, Azure Functions) with a time-based trigger.
Advantages over Apps Script: No 6-minute execution limit, no Google IP range whitelisting (runs from your own server/VPC), full Python ecosystem (pandas, complex transformations), and proper secrets management via environment variables or a secrets manager.
Disadvantages: Requires infrastructure to run the script (a server, a cloud function, or a CI/CD system), and someone needs to maintain the Python environment and dependencies.
Method 4: Third-Party Connectors
Third-party database-to-Sheets connectors handle the authentication, driver management, IP whitelisting, and scheduling. You configure the connection in a UI and it runs on their infrastructure.
brooked.io, connects to MySQL, PostgreSQL, SQL Server, Redshift, BigQuery, Snowflake, and others. You write a SQL query in brooked.io's interface, map the columns to your Sheet, and set a refresh schedule. brooked.io also offers an agent you can run inside your VPC for databases that are not publicly accessible, solving the IP whitelist problem entirely.
Coefficient, strong Salesforce and HubSpot support, also connects to Snowflake and BigQuery. Good for sales and CRM data. Less strong for custom queries on transactional databases.
Skyvia, specifically built for database-to-cloud-app data integration. Supports MySQL, PostgreSQL, SQL Server, Oracle, and others. Offers both scheduled sync and bi-directional write-back.
Supermetrics, excellent for marketing data sources (GA4, Meta, Google Ads), limited support for raw databases.
Hevo Data, Fivetran, Airbyte, full ETL/ELT pipelines, primarily designed for data warehouse loading. Can write to Sheets but this is not their primary use case. Generally overkill and over-priced for a Google Sheets sync.
Method 5: Reverse ETL Tools
If your data already lives in a data warehouse (Snowflake, BigQuery, Redshift, Databricks), Reverse ETL tools like Hightouch and Census can sync query results from the warehouse directly into Google Sheets on a schedule.
This approach is worth considering if:
- Your analytics team has already centralized data in a warehouse.
- You want consistent, governed data with defined models (dbt, LookML) rather than ad hoc queries.
- You need the Sheets view to stay in sync with the canonical warehouse data, not a production database.
Hightouch and Census both have native Google Sheets destinations. You define a model (SQL query or dbt model) in the tool, configure the Sheets destination, and set a sync schedule.
Trade-off: These tools are designed for operational data sync at scale: syncing CRM data back to Salesforce, or customer segments to advertising platforms. Using them solely for Google Sheets sync is functional but may be more infrastructure than the use case warrants.
Method Comparison Table
| Method | Cost | Technical Skill | DB Types | Scheduling | VPC Support | Schema Change Alerts |
|---|---|---|---|---|---|---|
| CSV Import | Free | None | Any | Manual only | N/A | None |
| Apps Script JDBC | Free | JavaScript | MySQL, PG, SQL Server, Cloud SQL | Yes (triggers) | No (IP whitelist only) | None |
| Python + gspread | Free | Python + infra | Any (via SQLAlchemy) | Yes (cron/cloud fn) | Yes (runs in your VPC) | Custom only |
| brooked.io | Paid | None | MySQL, PG, SQL Server, Redshift, more | Yes | Yes (VPC agent) | Yes |
| Coefficient | Paid | Low | Snowflake, BigQuery, some RDBMS | Yes | Limited | Limited |
| Skyvia | From $19/mo | Low | MySQL, PG, SQL Server, Oracle | Yes | Yes | Limited |
| Hightouch / Census | Paid | Medium | Warehouse-first | Yes | Yes | Yes |
Common Pitfalls and Troubleshooting
"Connection refused" or timeout on JDBC. Your database is not accepting connections from Google's IP ranges. Options: add Google's CIDR ranges to your database's security group/firewall rules, use a proxy server, switch to Google Cloud SQL with the native connector, or use a third-party tool with a VPC agent.
Apps Script JDBC hits the 6-minute execution limit. Add a LIMIT clause to your query and implement pagination: run the script multiple times with OFFSET, writing batches of 500–1,000 rows per execution. Use a trigger chain or store the current offset in Script Properties between runs.
Credentials exposed in Apps Script. Store passwords in Script Properties (File → Project Properties → Script Properties), not hardcoded in the script body. Grant only SELECT privileges to the database user. Consider IP-restricting the database user to Google's published ranges.
Schema change breaks the script silently. This is the most dangerous failure mode. A column renamed in the database causes your INSERT statement or your rs.getString() call to return null or throw an exception, and unless you have alerting, nobody knows. Mitigation: wrap your main function in a try/catch and send an email alert on any exception. Also version-pin your SQL queries so schema changes are visible.
function importFromDatabase() {
try {
// ... your import logic
} catch (e) {
GmailApp.sendEmail(
"[email protected]",
"DB to Sheets sync FAILED - " + new Date().toDateString(),
"Error: " + e.message + "\n\nStack: " + e.stack
);
throw e; // re-throw so it appears in the execution log
}
}Supabase connection pooler (port 6543) fails with JDBC. Use the direct connection string on port 5432, not the pooler on 6543. Supabase's pooler uses PgBouncer in transaction mode, which does not support all PostgreSQL features that JDBC relies on.
PlanetScale returns "This database has been branched" errors. PlanetScale uses a branching model. Ensure you are connecting to the production branch endpoint, not a development branch. The connection string from the dashboard under Settings → Passwords → Production will show the correct branch URL.
Google Sheets runs slowly with large datasets. JDBC writes data one row at a time by default. Batch your writes using sheet.getRange(1, 1, data.length, data[0].length).setValues(data) where data is a 2D array. This single call is 10–100x faster than row-by-row writes for large result sets.
// Slow (row by row)
while (rs.next()) {
sheet.getRange(row, 1, 1, 4).setValues([[...]]);
row++;
}
// Fast (batch write)
const data = [];
while (rs.next()) {
data.push([rs.getString(1), rs.getString(2), rs.getDouble(3), rs.getString(4)]);
}
sheet.getRange(1, 1, data.length, data[0].length).setValues(data);Azure SQL requires "encrypt=true" in the connection string. Azure SQL Database enforces SSL by default and will reject connections that do not include encrypt=true;trustServerCertificate=false in the JDBC connection string. If you see SSL handshake errors, add these parameters.
The Bottom Line
Connecting a database to Google Sheets automatically is entirely possible, but every method involves a trade-off between cost, technical complexity, and operational reliability:
- One-time analysis: Export as CSV. It is the right tool for a non-recurring pull.
- Recurring sync, technical team, database is publicly accessible: Apps Script JDBC is free and functional. Invest in proper error alerting and batch writes. Be prepared for maintenance as APIs and schemas evolve.
- Recurring sync, Python team, database in private network: Python + gspread running inside your infrastructure solves the IP whitelist problem and removes the 6-minute execution limit.
- Non-technical team, or database behind a VPC, or multiple source types: A third-party connector is the right investment. brooked.io handles the connection, scheduling, and schema change detection without requiring you to maintain code or manage IP whitelists.
- Data already in a warehouse: Hightouch or Census for governed, model-driven sync.
The 118,000 Stack Overflow views on this question are not evidence that the problem is unsolvable. They are evidence that the default assumption ("Sheets will have this built in") is wrong, and that once teams discover the gap, they want a real solution rather than a workaround they will have to maintain forever.
Stop Maintaining Connection Strings, Start Getting Data
brooked.io connects MySQL, PostgreSQL, SQL Server, Redshift, BigQuery, Snowflake, and 100+ other sources directly to Google Sheets. Write your SQL query once, set your refresh schedule, and your Sheet stays current, no IP whitelisting, no 6-minute limits, no silent failures when someone renames a column.
Connect your database to Google Sheets with brooked.io →
Frequently asked questions
Does Google Sheets have a built-in database connector?
No. Google Sheets has no native connector to MySQL, PostgreSQL, SQL Server, or any other RDBMS. The only built-in data import functions are IMPORTRANGE (Google Sheets to Google Sheets), IMPORTDATA (public CSV URLs), IMPORTHTML (public web tables), and IMPORTFEED (RSS). For real database connectivity you need Apps Script JDBC, a Python script, or a third-party connector.
What is the easiest way to connect MySQL to Google Sheets?
For a no-code approach, use brooked.io or Skyvia: connect your MySQL database in their UI, write or select your query, and set a refresh schedule. For a code-based free approach, use Google Apps Script with the JDBC connection string jdbc:mysql://your_host:3306/your_database and a time-based trigger.
Can I write data from Google Sheets back to a database?
Yes, with caveats. Apps Script JDBC supports INSERT and UPDATE statements, not just SELECT. Python with gspread and SQLAlchemy can read from Sheets and write to a database. Third-party tools like Skyvia support bi-directional sync. Be careful with write-back: without conflict detection, concurrent edits in Sheets and the database will overwrite each other.
Why does Google Apps Script JDBC fail to connect to my database?
The most common causes are: (1) your database's firewall or security group does not allow inbound connections from Google's IP ranges, (2) the database port (3306 for MySQL, 5432 for PostgreSQL, 1433 for SQL Server) is not open, (3) SSL is required but not specified in the connection string, (4) the database user does not have SELECT privileges on the target table, or (5) the connection string has a syntax error. Check the Apps Script execution log for the exact exception message.
How do I connect a PostgreSQL database that is inside a private VPC?
Options: (1) Add a bastion/proxy server with a public IP that forwards connections to the private database. Apps Script connects to the proxy. (2) Use Google Cloud SQL with the Jdbc.getCloudSqlConnection() method, which bypasses public IP requirements for Cloud SQL instances. (3) Use a third-party connector like brooked.io that offers a VPC agent you install inside your private network. The agent makes outbound connections to brooked.io, so no inbound firewall rules are needed.
Is it safe to put database passwords in Google Apps Script?
Somewhat. Passwords stored in Script Properties are not visible in the script code, but they are accessible to anyone with editor access to the Google Apps Script project. Best practices: use a dedicated read-only database user with minimal privileges (SELECT on specific tables only), restrict that user's connections to Google's published IP ranges at the database level, and rotate the password periodically. For higher security requirements, use a Python script with proper secrets management or
How do I prevent schema changes from breaking my Google Sheets sync?
Add error alerting to your sync script so you are notified immediately when it fails. Use explicit column aliases in your SQL queries (SELECT order_id, customer_name AS customer) rather than SELECT. This way if a column is added or reordered, your query still returns the expected columns. For third-party connectors, check whether the tool offers schema change notifications; brooked.io alerts you when query results change structure unexpectedly.
What is the difference between Redshift and PostgreSQL for JDBC connections?
Amazon Redshift is wire-compatible with PostgreSQL for most SQL operations, and Apps Script's JDBC service connects to Redshift using a PostgreSQL-style JDBC URL with the jdbc:redshift:// prefix and port 5439. Performance differences: Redshift is a columnar warehouse optimized for analytical queries on large datasets; for Google Sheets sync (which typically pulls thousands to low millions of rows), the practical difference is minimal. Ensure your Redshift cluster allows inbound connections from
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 →

