Pricing

Neon Postgres to Google Sheets: The Guide Nobody Has Written Yet

psql — brooked_prodbrooked_prod=#SELECT region, revenue, ordersbrooked_prod-#FROM regional_summarybrooked_prod-#ORDER BY revenue DESC LIMIT 4;regionrevenueordersUS East$4,218,00012,841EU West$2,901,0008,204APAC$1,712,0005,112US West$1,440,0004,390(4 rows)brooked_prod=#
JW
James Whitfield

Connect Neon's serverless Postgres to Google Sheets, cold-start behavior, connection strings, branching, PgBouncer pooling, working Apps Script JDBC code, and a Python alternative. The first complete guide on this topic.

Search for "Neon Postgres Google Sheets" and you will find nothing useful. A few forum threads asking if it is possible, no complete answers. This guide fills that gap. It covers every practical method for getting data from a Neon database into Google Sheets, including the behaviors that are specific to Neon and absent from generic Postgres-to-Sheets tutorials.

What Makes Neon Different from Standard Postgres

Neon is a serverless Postgres platform built on a storage-compute separation architecture. Unlike a traditional Postgres instance (or even Supabase, which runs on dedicated VMs), Neon computes can scale to zero. The database engine shuts down completely when there is no activity.

This has three practical consequences for anyone building a Sheets integration:

  1. Cold starts: The first query after an idle period takes longer than usual (typically 500ms to a few seconds) while Neon boots the compute.
  2. Connection limits: Because Neon uses ephemeral compute, raw Postgres connection limits are lower than you might expect on small plans. PgBouncer pooling is essential for any integration that opens multiple connections.
  3. Connection string format: Neon's connection strings look like standard Postgres URIs but include a specific hostname format and optional pooler subdomain.

Everything else about Neon is standard Postgres 16. Any Postgres driver, library, or tool that works with standard Postgres will work with Neon.

Neon's Cold-Start Behavior and What It Means for Sheets Syncs

When Neon's compute is suspended (after roughly 5 minutes of inactivity on the free tier), the next connection triggers a cold start. During cold start:

  • The compute instance boots from a snapshot.
  • Your connection waits in a queue.
  • The first query executes once the compute is ready.

For an interactive application, this can manifest as a slow first page load. For a scheduled Google Sheets sync, it means your Apps Script function may take 3–5 seconds longer on runs that follow an idle period. This is not an error. It is expected behavior.

Practical impact on Apps Script: Apps Script has a maximum execution time of 6 minutes. For most Sheets syncs, cold-start latency is not a problem. If you are syncing very large datasets and already near the 6-minute limit, consider warming the connection by running a lightweight SELECT 1 query before your main data pull.

On paid Neon plans, you can configure the compute to never suspend, eliminating cold starts entirely.

The Neon Connection String Format

Neon provides a standard Postgres URI. You can find it in the Neon console under your project's Connection Details panel.

Direct (non-pooled) connection string

Code
postgresql://[user]:[password]@[project-id].us-east-2.aws.neon.tech/[database]?sslmode=require

Breaking this down:

  • [user]: Your Neon role name. The default role for new projects is the same as your project name.
  • [password]: Generated by Neon. Visible once in the console; store it securely.
  • [project-id]: A string like ep-wispy-forest-123456. This is your endpoint ID.
  • [database]: Defaults to neondb.
  • sslmode=require: Always include this. Neon requires TLS.

Pooled connection string (via PgBouncer)

Code
postgresql://[user]:[password]@[project-id]-pooler.us-east-2.aws.neon.tech/[database]?sslmode=require

The only difference is -pooler appended to the hostname. This routes your connection through Neon's managed PgBouncer instance in transaction pooling mode.

Connection Pooling via PgBouncer

Neon's managed PgBouncer runs in transaction pooling mode, which is the right choice for stateless clients like Apps Script. Connection behavior is the same as described for Supabase's transaction pooler: a server connection is held only for the duration of a transaction, then returned to the pool.

Use the pooled endpoint (-pooler hostname) whenever:

  • You are connecting from Apps Script, a serverless function, or any client that opens and closes connections frequently.
  • You expect more than a handful of concurrent connections.
  • You are on the Neon free tier (which has a lower raw connection limit).

Use the direct endpoint (no -pooler) whenever:

  • You need session-level features: SET statements that persist, LISTEN/NOTIFY, or COPY streaming.
  • You are running a migration tool.
  • You need logical replication for CDC.

PgBouncer in transaction mode does not support SET statements that persist across transactions, advisory locks held between transactions, or the replication protocol. For everything else, the pooler is preferable.

Neon Branching: Dev vs. Prod

One of Neon's standout features is instant database branching. A branch is a full copy-on-write fork of your database at a point in time. It shares storage with the parent branch up to the branch point, so creating a branch is nearly instantaneous regardless of database size.

Why this matters for Sheets integrations

If you are testing a new sync query or restructuring how your data lands in Sheets, run your tests against a dev branch, not production. This gives you:

  • Isolation: Your test runs cannot affect production data.
  • A realistic dataset: The branch is a full copy of prod at branch time, not a subset.
  • Easy cleanup: Delete the branch when done; no traces left in production.

Connecting to a specific branch

Each branch gets its own endpoint (and its own connection string). In the Neon console, select the branch from the branch dropdown before copying the connection details. The endpoint ID in the hostname changes per branch:

Code
# Production branch
postgresql://user:[email protected]/neondb?sslmode=require

# Dev branch
postgresql://user:[email protected]/neondb?sslmode=require

In Apps Script, you can store the branch connection string in Script Properties and swap it as needed without editing the code.

Method 1: Apps Script JDBC

Apps Script's JDBC service supports Postgres. The connection string must use the jdbc:postgresql:// prefix. Apps Script does not accept the bare postgresql:// URI format.

Step 1: Get your pooled connection string from Neon

Copy the pooled connection string from the Neon console (the one with -pooler in the hostname).

Step 2: Convert to JDBC format

Code
# Neon gives you:
postgresql://alice:[email protected]/neondb?sslmode=require

# Convert to JDBC (move credentials to parameters, change scheme):
jdbc:postgresql://ep-wispy-forest-123456-pooler.us-east-2.aws.neon.tech/neondb?sslmode=require

Pass the username and password as separate arguments to Jdbc.getConnection() rather than embedding them in the URL.

Step 3: The complete Apps Script

javascript
function syncNeonToSheets() {
  const NEON_HOST = 'ep-wispy-forest-123456-pooler.us-east-2.aws.neon.tech';
  const NEON_DB = 'neondb';
  const NEON_USER = 'alice';
  const NEON_PASSWORD = PropertiesService.getScriptProperties().getProperty('NEON_PASSWORD');
  const SHEET_NAME = 'Orders';

  const connString = `jdbc:postgresql://${NEON_HOST}/${NEON_DB}?sslmode=require`;

  let conn, stmt, results;

  try {
    conn = Jdbc.getConnection(connString, NEON_USER, NEON_PASSWORD);
    stmt = conn.createStatement();
    stmt.setMaxRows(10000);

    results = stmt.executeQuery(`
      SELECT
        o.id,
        o.created_at,
        o.status,
        o.total_amount,
        c.email AS customer_email
      FROM orders o
      JOIN customers c ON c.id = o.customer_id
      ORDER BY o.created_at DESC
      LIMIT 10000
    `);

    const meta = results.getMetaData();
    const numCols = meta.getColumnCount();

    // Build headers row
    const headers = [];
    for (let i = 1; i <= numCols; i++) {
      headers.push(meta.getColumnName(i));
    }

    // Build data rows
    const data = [headers];
    while (results.next()) {
      const row = [];
      for (let i = 1; i <= numCols; i++) {
        row.push(results.getString(i));
      }
      data.push(row);
    }

    // Write to sheet in one batch
    const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
    sheet.clearContents();
    sheet.getRange(1, 1, data.length, numCols).setValues(data);

    Logger.log(`Synced ${data.length - 1} rows to ${SHEET_NAME}`);

  } catch (e) {
    Logger.log('Error: ' + e.toString());
    throw e;
  } finally {
    if (results) results.close();
    if (stmt) stmt.close();
    if (conn) conn.close();
  }
}

Storing the password: Never hardcode credentials in Apps Script. Use PropertiesService.getScriptProperties(). Set the property once via Project Settings > Script Properties in the editor.

Setting a trigger: Go to Triggers in the Apps Script editor, click Add Trigger, and schedule syncNeonToSheets to run on a time-based schedule (hourly, daily, etc.).

Method 2: Python Alternative

If you need more flexibility, parameterized queries, complex transformations, custom scheduling via cron or GitHub Actions. Python gives you more control.

Dependencies

bash
pip install psycopg2-binary gspread google-auth

Use psycopg2 for a direct Postgres connection. Since Neon is standard Postgres, no Neon-specific library is needed.

The Script

python
import psycopg2
import gspread
from google.oauth2.service_account import Credentials

NEON_CONN = "postgresql://alice:[email protected]/neondb?sslmode=require"
SHEET_ID = "your_google_sheet_id"
SERVICE_ACCOUNT_FILE = "/path/to/service-account.json"

def sync():
    # Connect to Neon
    conn = psycopg2.connect(NEON_CONN)
    cur = conn.cursor()

    cur.execute("""
        SELECT id, created_at, status, total_amount
        FROM orders
        ORDER BY created_at DESC
        LIMIT 10000
    """)

    rows = cur.fetchall()
    col_names = [desc[0] for desc in cur.description]

    cur.close()
    conn.close()

    # Connect to Google Sheets
    scopes = [
        "https://www.googleapis.com/auth/spreadsheets",
        "https://www.googleapis.com/auth/drive"
    ]
    creds = Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=scopes)
    gc = gspread.authorize(creds)
    worksheet = gc.open_by_key(SHEET_ID).worksheet("Orders")

    # Write in one batch
    values = [col_names] + [list(map(str, row)) for row in rows]
    worksheet.clear()
    worksheet.update("A1", values)

    print(f"Synced {len(rows)} rows")

if __name__ == "__main__":
    sync()

This script uses the pooled endpoint. If you need to use psycopg2 with Neon and encounter issues with prepared statements, add options=-c default_transaction_isolation=read\ committed or use cursor_factory=psycopg2.extras.RealDictCursor for named result columns.

Scheduling with GitHub Actions

A simple and free way to schedule this script without a server:

yaml
# .github/workflows/sync.yml
name: Neon to Sheets Sync
on:
  schedule:
    - cron: '0 * * * *'  # Every hour
jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      - run: pip install psycopg2-binary gspread google-auth
      - run: python sync.py
        env:
          NEON_CONN: ${{ secrets.NEON_CONN }}

Store the connection string in GitHub Actions Secrets.

Method 3: Third-Party Connectors

Because Neon is standard Postgres, any connector that supports Postgres will work with Neon. Provide the pooled connection string and ensure SSL is required.

  • Airbyte: Has a dedicated Neon connector as of 2024, which handles cold-start retries automatically.
  • Fivetran: Use the Postgres source connector. Tested with Neon.
  • Zapier / Make.com: Use the Postgres action. Provide the pooled connection string.
  • brooked.io: Native support for Neon with automatic cold-start handling and pooler selection.

If a connector prompts for SSL settings, choose Require or Verify-Full depending on what the tool supports.

Comparison Table

MethodCold-Start HandlingSetup EffortSchedulingBest For
Apps Script JDBCAutomatic (waits)LowGoogle triggersSimple periodic syncs
Python + psycopg2Configurable timeoutMediumCron / GitHub ActionsComplex queries, transforms
AirbyteBuilt-in retry logicHighBuilt-inELT pipelines
Zapier / MakeWaits automaticallyLowEvent-drivenTrigger-based syncs
brooked.ioAutomaticVery LowUI schedulerNo-code scheduled syncs

Troubleshooting

"Connection refused" or timeout on first query This is a cold start. Neon's compute is booting. Wait a few seconds and retry. On free tier, this happens after 5 minutes of inactivity. Upgrade to a paid plan to disable auto-suspension.

"SSL connection is required" error Always append ?sslmode=require to your connection string. Neon does not allow unencrypted connections.

Apps Script "Invalid argument" on connection string You must use jdbc:postgresql:// not postgresql://. Also, do not embed credentials in the URL string itself: pass username and password as the second and third arguments to Jdbc.getConnection().

"prepared statement X does not exist" error You are connecting through PgBouncer (the pooler) with a library that uses server-side prepared statements. Disable prepared statements in your driver. For psycopg2, this is not an issue by default. For other drivers, look for a prepareThreshold=0 parameter.

Data in Sheets is stale Check that your trigger is actually firing. Apps Script triggers can silently fail if there is an uncaught exception. Check Executions in the Apps Script editor for error logs.

Cannot connect to a specific branch Each branch has its own endpoint ID and its own connection string. Copy the connection details from the Neon console with the correct branch selected.

Bottom Line

Neon is standard Postgres with one meaningful behavioral difference for Sheets integrations: cold starts. Plan for them by using the pooler endpoint (the -pooler hostname) and building basic retry logic or cold-start tolerance into your sync. For everything else, the same JDBC and Python patterns that work with any Postgres database work with Neon.

Neon's branching feature is genuinely useful for this use case: test your sync queries against a branch before running them against production, especially if you are transforming or filtering data in ways that are hard to validate without a real dataset.

This guide will be updated as Neon's feature set evolves.

Start Syncing

brooked.io supports Neon Postgres natively. Paste your connection string, pick your table, schedule your sync, no code required.

Related guides on brooked.io:

Troubleshooting quick reference

Frequently asked questions

Is Neon compatible with standard Postgres drivers?

Yes. Neon runs Postgres 16 and is compatible with any driver, library, or tool that supports standard Postgres. There is no Neon-specific driver required. Use psycopg2, pg (Node), JDBC, or any other standard Postgres driver.

How long do Neon cold starts take?

On the free tier, cold starts typically take 500ms to 3 seconds. The variance depends on database size and compute region. On paid plans, you can disable auto-suspension to eliminate cold starts entirely.

Can I connect to a Neon branch from Apps Script?

Yes. Each branch has its own connection string. Copy the connection string for the desired branch from the Neon console and use it in your Apps Script. This is useful for testing sync queries against a copy of production data.

Does Neon support logical replication for CDC?

Yes, on paid plans. Use the direct (non-pooler) endpoint. Logical replication is not supported through PgBouncer's transaction pooling mode, which is the same constraint as Supabase.

What is the row limit for a practical Sheets sync?

Google Sheets supports up to 10 million cells per spreadsheet. Practically, performance degrades noticeably above 500,000 rows in a single sheet. For most analytics use cases, limit your sync to the most recent N rows or use date-range filtering in your query.

How do I rotate a Neon password?

In the Neon console, go to your project's Roles settings and reset the password for the role. Then update the stored credential in Apps Script (Script Properties) or your secrets manager. The connection string itself does not change: only the password component.

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 →

Also in Database

More Database guides

Database

How to Connect MySQL to Google Sheets

Apps Script with JDBC, CSV exports, IMPORTDATA, and no-code add-ons compared, with the exact cloud-provider connection strings (RDS, PlanetScale, Cloud SQL, Azure), scheduled refresh, two-way write-back, and an AI agent that writes the SQL for you.

JW
James Whitfield
Read
Database

How to Connect SQL Server to Google Sheets

Import SQL Server query results into Google Sheets, schedule auto-refresh, and write data back, works with Azure SQL, Amazon RDS SQL Server, and self-hosted instances.

JW
James Whitfield
Read

Get your spreadsheet hours back

Brooked sets up in seconds. Your team is querying live data before lunch.

Get started free