Guides

Sync New Google Sheets Rows to a PostgreSQL Database

Create an n8n workflow that monitors Google Sheets for new rows and syncs them to a PostgreSQL database in real time.

GuideIntermediate6 min read

Nodes used: Google Sheets Trigger, PostgreSQL


What you'll build

A workflow that detects new rows added to a Google Sheets spreadsheet and inserts them into a PostgreSQL table automatically. Every time a team member adds a row to the sheet, the data appears in your database within minutes, keeping both systems in sync without manual imports.

Why this is useful

Google Sheets is widely used for data entry because it is accessible and collaborative, but spreadsheets are not a reliable long-term data store. By syncing rows to PostgreSQL, you get the convenience of spreadsheet-based input with the durability, query performance, and integration capabilities of a relational database.

Prerequisites

  • An n8n instance (Cloud or self-hosted, v2.x)
  • A Google account with access to the target spreadsheet
  • A Google OAuth2 credential configured in n8n (with Sheets and Drive scopes)
  • A PostgreSQL database with network access from your n8n instance
  • A PostgreSQL credential configured in n8n
  • A target table already created in PostgreSQL (see step 2)

Steps

1. Prepare your Google Sheet

Make sure your spreadsheet has a header row. For this guide, assume the following columns:

A B C D
full_name email signup_date plan

The column headers should match or map clearly to your database columns to simplify the mapping step later.

2. Create the PostgreSQL table

Connect to your PostgreSQL database and run the following:

CREATE TABLE IF NOT EXISTS signups (
    id SERIAL PRIMARY KEY,
    full_name TEXT NOT NULL,
    email TEXT NOT NULL,
    signup_date DATE,
    plan TEXT,
    synced_at TIMESTAMP DEFAULT NOW()
);

The synced_at column is populated automatically by PostgreSQL, so you do not need to map it in n8n.

3. Add the Google Sheets Trigger node

Create a new workflow in n8n and add the Google Sheets Trigger as the first step. Configure:

  • Credential: Select your Google OAuth2 credential.
  • Document: Choose the spreadsheet from the dropdown.
  • Sheet: Select the specific sheet tab (e.g., "Sheet1").
  • Event: Watch for New Rows.

n8n Cloud

The trigger polls automatically on the interval set in the node (default is every minute). No additional infrastructure is needed.

Self-hosted

The polling interval works the same way, but your instance must be running continuously. If your instance restarts, n8n resumes polling and picks up rows added while it was down, provided the sheet data is still available.

Info: Polling behavior

The Google Sheets Trigger tracks which rows it has already seen. It will only process rows added after the workflow is activated. Existing rows at activation time are ignored.

4. Add the PostgreSQL node

Click + on the trigger output and add a PostgreSQL node. Configure:

  • Credential: Select your PostgreSQL credential.
  • Operation: Insert
  • Table: signups
  • Columns: full_name, email, signup_date, plan

Map each column to the corresponding value from the sheet using expressions:

Column Expression
full_name {{ $json.full_name }}
email {{ $json.email }}
signup_date {{ $json.signup_date }}
plan {{ $json.plan }}

Tip: Column name matching

If your Google Sheets header names match your PostgreSQL column names exactly, n8n can auto-map them. Select Map Automatically in the PostgreSQL node's column mapping mode to save time.

5. Handle data types

Spreadsheet data arrives as strings. PostgreSQL will cast compatible values automatically for most types, but dates can cause problems if the format is unexpected.

If your signup_date column uses a format like MM/DD/YYYY, convert it explicitly with an expression:

{{ DateTime.fromFormat($json.signup_date, 'MM/dd/yyyy').toISODate() }}

This produces a YYYY-MM-DD string that PostgreSQL accepts without ambiguity.

Warning: Null and empty values

If a cell in the sheet is empty, n8n sends an empty string. If your PostgreSQL column has a NOT NULL constraint, the insert will fail. Either make columns nullable, set default values in your table definition, or add an IF node before the PostgreSQL node to filter out incomplete rows.

6. Test the workflow

  1. Click Test workflow in the top bar.
  2. Add a new row to your Google Sheet with sample data.
  3. Wait for the trigger to pick up the new row.
  4. Check the PostgreSQL node output on the canvas to confirm the insert succeeded.
  5. Query your database to verify:
SELECT * FROM signups ORDER BY id DESC LIMIT 5;

7. Activate the workflow

Toggle the Active switch. The workflow now runs continuously, syncing every new row from the sheet to the database.


Error handling basics

Production workflows should handle failures gracefully. n8n provides several mechanisms:

  • Retry on fail. On the PostgreSQL node, open Settings and enable Retry on Fail. Set it to retry 2-3 times with a short wait. This handles transient database connection issues.
  • Error workflow. In your workflow settings (the gear icon), set an Error Workflow that triggers when this workflow fails. The error workflow can send a Slack message or email so you know about the problem promptly.
  • Error output. On the PostgreSQL node, enable the Error Output in node settings. This creates a second output branch that captures failed items, allowing you to log them or route them to a fallback destination without stopping the entire workflow.

Note: Duplicate prevention

If the workflow fails after the trigger processes a row but before the insert completes, the trigger will not re-send that row on the next poll. To guard against data loss, consider writing failed rows to a dead-letter sheet or table for manual review.


Test it

Add five rows to your sheet in quick succession and confirm all five appear in PostgreSQL. Then test an error scenario: temporarily change the table name in the PostgreSQL node to a nonexistent table, add a row, and verify that the error handling you configured (retry, error workflow, or error output) catches the failure.


Take it further

  • Upsert instead of insert. Use the PostgreSQL node's Upsert operation with a conflict column (e.g., email) to update existing records instead of creating duplicates.
  • Add data validation. Insert a Code node between the trigger and the database to validate email format, trim whitespace, or normalize text before inserting.
  • Sync deletions. Add a second workflow that periodically compares sheet rows against database rows and soft-deletes records that were removed from the sheet.
  • Scale to multiple sheets. Duplicate the workflow for other sheets, or use a single workflow with a Switch node that routes data to different tables based on the sheet name.

Troubleshooting

Issue: PostgreSQL node fails with "relation does not exist". The table name in the PostgreSQL node does not match an existing table in the database. Verify that you ran the CREATE TABLE statement from step 2 and that you are connecting to the correct database. Table names are case-sensitive if created with double quotes in PostgreSQL.

Issue: Insert fails with "null value in column violates not-null constraint". An empty cell in the Google Sheet sends an empty string, but if the corresponding PostgreSQL column has a NOT NULL constraint, the insert may fail depending on the column type. Either make the column nullable, set a DEFAULT value in the table definition, or add an IF node before the PostgreSQL node to filter out rows with missing required fields.

Issue: Date values cause a "date/time field value out of range" error. The date format in your spreadsheet does not match what PostgreSQL expects. If your sheet uses MM/DD/YYYY, convert it explicitly using the expression {{ DateTime.fromFormat($json.signup_date, 'MM/dd/yyyy').toISODate() }} to produce a YYYY-MM-DD string that PostgreSQL accepts.

Want this running in your stack?

I build production n8n and Cloudflare automation for teams — the same engineering behind HarperFlow. Fixed-price, escrow-protected, US-based.