Create an n8n workflow that monitors Google Sheets for new rows and syncs them to a PostgreSQL database in real time.
Nodes used: Google Sheets Trigger, PostgreSQL
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.
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.
Make sure your spreadsheet has a header row. For this guide, assume the following columns:
| A | B | C | D |
|---|---|---|---|
| full_name | signup_date | plan |
The column headers should match or map clearly to your database columns to simplify the mapping step later.
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.
Create a new workflow in n8n and add the Google Sheets Trigger as the first step. Configure:
The trigger polls automatically on the interval set in the node (default is every minute). No additional infrastructure is needed.
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.
Click + on the trigger output and add a PostgreSQL node. Configure:
signupsfull_name, email, signup_date, planMap 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.
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 NULLconstraint, 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.
SELECT * FROM signups ORDER BY id DESC LIMIT 5;
Toggle the Active switch. The workflow now runs continuously, syncing every new row from the sheet to the database.
Production workflows should handle failures gracefully. n8n provides several mechanisms:
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.
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.
email) to update existing records instead of creating duplicates.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.
I build production n8n and Cloudflare automation for teams — the same engineering behind HarperFlow. Fixed-price, escrow-protected, US-based.