Streamline your data management process by automating updates between PostgreSQL and Google Sheets using n8n. This integration allows you to automatically sync data, ensuring your spreadsheets are always up-to-date without manual entry. Features real-time data synchronization, scheduled triggers for automation, and seamless HTTP requests. Perfect for data analysts and teams managing large datasets, allowing for efficient reporting and decision-making. Requires 2 accounts: PostgreSQL and Google Sheets OAuth. Save up to 10 hours a week on manual data entry, enabling your team to focus on analysis rather than administration.
Categories
APIs
Credentials
Copy or download to import into your n8n instance
{ "meta": { "instanceId": "f4f5d195bb2162a0972f737368404b18be694648d365d6c6771d7b4909d28167", "templateCredsSetupCompleted": true }, "nodes": [ { "id": "0a03e403-4a72-4382-b648-602f4bd6ef87", "name": "When clicking ‘Test workflow’", "type": "n8n-nodes-base.manualTrigger", "position": [ -1720, 380 ], "parameters": {}, "typeVersion": 1 }, { "id": "a421a477-bcee-460c-bc6a-8150a924fdd3", "name": "add create_at column", "type": "n8n-nodes-base.postgres", "position": [ -1640, -100 ], "parameters": { "query": "ALTER TABLE ONLY \"n8n_chat_histories\" ADD COLUMN \"created_at\" TIMESTAMP DEFAULT NOW();", "options": {}, "operation": "executeQuery" }, "credentials": { "postgres": { "id": "4E1voKcpNaIKnNJY", "name": "PG - Chat Memory POC" } }, "typeVersion": 2.5 }, { "id": "0cb5189e-857d-49a1-a8e9-923d4f955383", "name": "Get conversations by sessionId", "type": "n8n-nodes-base.postgres", "position": [ 380, 480 ], "parameters": { "table": { "__rl": true, "mode": "list", "value": "n8n_chat_histories", "cachedResultName": "n8n_chat_histories" }, "where": { "values": [ { "value": "={{ $('Loop Over Session IDs').item.json.session_id }}", "column": "session_id" } ] }, "schema": { "__rl": true, "mode": "name", "value": "=public" }, "options": {}, "operation": "select", "returnAll": true }, "credentials": { "postgres": { "id": "4E1voKcpNaIKnNJY", "name": "PG - Chat Memory POC" } }, "executeOnce": false, "typeVersion": 2.5 }, { "id": "72d8c711-a3f9-4f95-b79a-a9190d2b4964", "name": "Duplicate template sheet", "type": "n8n-nodes-base.httpRequest", "position": [ -400, 600 ], "parameters": { "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $('Clear Sheet Content').params.documentId.value }}/sheets/0:copyTo", "method": "POST", "options": {}, "sendBody": true, "authentication": "predefinedCredentialType", "bodyParameters": { "parameters": [ { "name": "destinationSpreadsheetId", "value": "={{ $('Clear Sheet Content').params.documentId.value }}" } ] }, "nodeCredentialType": "googleSheetsOAuth2Api" }, "credentials": { "googleSheetsOAuth2Api": { "id": "ufBkeygvc1l17m5N", "name": "Baptiste AS - Google Sheets account" } }, "typeVersion": 4.1 }, { "id": "940fae1e-1ab7-4211-8d9f-fe5b9274e5e5", "name": "Clear Sheet Content", "type": "n8n-nodes-base.googleSheets", "onError": "continueErrorOutput", "position": [ -760, 460 ], "parameters": { "clear": "specificRange", "range": "A2:C10000", "operation": "clear", "sheetName": { "__rl": true, "mode": "name", "value": "={{ $json.session_id }}" }, "documentId": { "__rl": true, "mode": "list", "value": "14bKI5J0h18Nv48jbe1IXpZWma6EtqYLFWnpKoCB5Bgc", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/14bKI5J0h18Nv48jbe1IXpZWma6EtqYLFWnpKoCB5Bgc/edit?usp=drivesdk", "cachedResultName": "Conversation logs" } }, "credentials": { "googleSheetsOAuth2Api": { "id": "ufBkeygvc1l17m5N", "name": "Baptiste AS - Google Sheets account" } }, "typeVersion": 4.5 }, { "id": "b16e85bc-a84b-4da0-85da-568a1c981e02", "name": "Rename Sheet", "type": "n8n-nodes-base.httpRequest", "position": [ -200, 600 ], "parameters": { "url": "=https://sheets.googleapis.com/v4/spreadsheets/{{ $('Clear Sheet Content').params.documentId.value }}:batchUpdate", "method": "POST", "options": {}, "jsonBody": "={\n \"requests\": [{\n \"updateSheetProperties\": {\n \"properties\": {\n \"sheetId\": {{ $json.sheetId }},\n \"title\": \"{{ $('Clear Sheet Content').item.json.session_id }}\",\n \"hidden\": false\n },\n \"fields\": \"title, hidden\"\n }\n }]\n}", "sendBody": true, "specifyBody": "json", "authentication": "predefinedCredentialType", "nodeCredentialType": "googleSheetsOAuth2Api" }, "credentials": { "googleSheetsOAuth2Api": { "id": "ufBkeygvc1l17m5N", "name": "Baptiste AS - Google Sheets account" } }, "typeVersion": 4.1 }, { "id": "63ac37f4-1f80-4e5a-9af4-ec2e05cbaccb", "name": "Schedule Trigger", "type": "n8n-nodes-base.scheduleTrigger", "position": [ -1720, 560 ], "parameters": { "rule": { "interval": [ { "triggerAtHour": 12 } ] } }, "typeVersion": 1.2 }, { "id": "81cfed36-ee79-408f-8bad-0147a8acb0b3", "name": "Sticky Note", "type": "n8n-nodes-base.stickyNote", "position": [ -1740, -480 ], "parameters": { "color": 4, "width": 320, "height": 540, "content": "## Add a datetime column\nIf not already done, add a `created_at` column to store the datetime of the message\n\n\n👉 Ideally, do it before your first interaction, otherwise all your previous conversations will be set as the time of the execution of this node.\n\n💡 **Replace** the name of your chat memory before executing the request\n\nThe table schema will at least contain the following fields: `session_id`, `message.content`, `message.type`, and `created_at`" }, "typeVersion": 1 }, { "id": "cecbaa6a-5d8b-4704-b249-bcd336875773", "name": "Set session_id", "type": "n8n-nodes-base.set", "position": [ -20, 600 ], "parameters": { "options": {}, "assignments": { "assignments": [ { "id": "3cc4ae1d-1693-4b30-9cbf-83fbb220813d", "name": "session_id", "type": "string", "value": "={{ $('Clear Sheet Content').first().json.session_id }}" } ] } }, "typeVersion": 3.4 }, { "id": "1f8b387b-52d0-4023-9d4b-4dd61272fe82", "name": "Postgres - Get session ids", "type": "n8n-nodes-base.postgres", "position": [ -1380, 420 ], "parameters": { "query": "select distinct(session_id) from n8n_chat_histories", "options": {}, "operation": "executeQuery" }, "credentials": { "postgres": { "id": "4E1voKcpNaIKnNJY", "name": "PG - Chat Memory POC" } }, "typeVersion": 2.5 }, { "id": "2ab2fc24-1c19-4c08-b20f-121bf22c6f9c", "name": "Loop Over Session IDs", "type": "n8n-nodes-base.splitInBatches", "position": [ -1060, 420 ], "parameters": { "options": {} }, "typeVersion": 3 }, { "id": "46763fb1-bf18-44a4-82b8-26e09325f159", "name": "Add conversations", "type": "n8n-nodes-base.googleSheets", "position": [ 560, 640 ], "parameters": { "columns": { "value": { "Who": "={{ $json.message.type }}", "Date": "={{ $json.created_at.toDateTime().format('yyyy-MM-dd hh:mm:ss') }}", "Message": "={{ $json.message.content }}" }, "schema": [ { "id": "Who", "type": "string", "display": true, "removed": false, "required": false, "displayName": "Who", "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "Message", "type": "string", "display": true, "removed": false, "required": false, "displayName": "Message", "defaultMatch": false, "canBeUsedToMatch": true }, { "id": "Date", "type": "string", "display": true, "removed": false, "required": false, "displayName": "Date", "defaultMatch": false, "canBeUsedToMatch": true } ], "mappingMode": "defineBelow", "matchingColumns": [] }, "options": {}, "operation": "append", "sheetName": { "__rl": true, "mode": "name", "value": "={{ $('Loop Over Session IDs').item.json.session_id }}" }, "documentId": { "__rl": true, "mode": "list", "value": "14bKI5J0h18Nv48jbe1IXpZWma6EtqYLFWnpKoCB5Bgc", "cachedResultUrl": "https://docs.google.com/spreadsheets/d/14bKI5J0h18Nv48jbe1IXpZWma6EtqYLFWnpKoCB5Bgc/edit?usp=drivesdk", "cachedResultName": "Conversation logs" } }, "credentials": { "googleSheetsOAuth2Api": { "id": "ufBkeygvc1l17m5N", "name": "Baptiste AS - Google Sheets account" } }, "typeVersion": 4.5 }, { "id": "4b711779-938d-4a51-96dc-a3a629cfdcb3", "name": "Sticky Note2", "type": "n8n-nodes-base.stickyNote", "position": [ -1400, -480 ], "parameters": { "color": 4, "width": 360, "height": 540, "content": "## For Supabase users\nAlthough in theory you could use Supabase native nodes, I found it easier to use PG. It's also more sustainable in the long term.\n\n### Get your Supabase credentials\n1. Go to your Supabase project\n2. Click on Connect\n3. Go to the \"Transaction pooler\" section and click on \"View parameters\"\n4. Copy/paste the parameters as new PG credentials in your n8n instance.\n" }, "typeVersion": 1 }, { "id": "2389393d-3e62-4349-a1cd-819d2b010f29", "name": "Sticky Note3", "type": "n8n-nodes-base.stickyNote", "position": [ -1460, 140 ], "parameters": { "color": 7, "width": 280, "height": 720, "content": "## Get all sessions \nIn this node, we execute a SQL query that collects all sessionIDs.\n\n**Replace** the name of your chat memory before executing the request" }, "typeVersion": 1 }, { "id": "eb323ee1-7afb-421d-86f4-02846782fb3e", "name": "Sticky Note4", "type": "n8n-nodes-base.stickyNote", "position": [ -500, -620 ], "parameters": { "color": 6, "width": 440, "height": 700, "content": "## Important - How it works\n- Each time this workflow is executed, all conversations are cleared and replaced in order to always have up to date data.\n- The reason is that the sessionID can be overridden in the AI Agent, with a value more permanent (e.g a userID). This way, the conversation can continue over several sessions." }, "typeVersion": 1 }, { "id": "3ca21e1a-1c1c-4064-9e06-540aef692291", "name": "Sticky Note5", "type": "n8n-nodes-base.stickyNote", "position": [ -1160, 140 ], "parameters": { "color": 7, "width": 280, "height": 720, "content": "## Loop over each session\nThis node iterates over each session and add it in a separate sheet" }, "typeVersion": 1 }, { "id": "135eb659-31d5-4760-af09-938c3913bb6c", "name": "Sticky Note6", "type": "n8n-nodes-base.stickyNote", "position": [ -1000, -480 ], "parameters": { "color": 4, "width": 360, "height": 540, "content": "## Google Sheets template\nhttps://docs.google.com/spreadsheets/d/14bKI5J0h18Nv48jbe1IXpZWma6EtqYLFWnpKoCB5Bgc/edit?usp=sharing" }, "typeVersion": 1 }, { "id": "6c329217-3f99-40bd-8ff5-57b2266f4012", "name": "Sticky Note7", "type": "n8n-nodes-base.stickyNote", "position": [ -1780, -620 ], "parameters": { "color": 4, "width": 1240, "height": 700, "content": "# Setup\n### 👉 Make sure to double check these steps before launching this workflow for the first time" }, "typeVersion": 1 }, { "id": "b06b11b7-e0c7-491c-b7ce-4e321187663c", "name": "Sticky Note8", "type": "n8n-nodes-base.stickyNote", "position": [ -860, 140 ], "parameters": { "color": 7, "width": 340, "height": 720, "content": "## Clear Google Sheet content\nThis will clear each Google Sheets data in order to have up to date data.\n\nError path: the error path is actually normal, it happens if the `session_id` doesn't already exist.\n\n**TODO**\nReplace the Google Sheets (document) with your own Google Sheets (see Setup if needed)" }, "typeVersion": 1 }, { "id": "495026a8-4b3a-4ad8-8bf4-120cfa039a63", "name": "Sticky Note9", "type": "n8n-nodes-base.stickyNote", "position": [ -460, 140 ], "parameters": { "color": 7, "width": 640, "height": 720, "content": "## Create a new Google Sheet based on a template\nThis part of the workflow prepares a fresh sheet for each session by copying a predefined template.\n\n**What it does:**\n**Clear Sheet Content**\nClears the sheet content named after the current `session_id`. This ensures any old data is removed before new data is written.\nNote: If the sheet doesn't exist yet, this step will fail silently: that's expected.\n\n**Duplicate Template Sheet**\n Duplicates the first sheet (index 0) from your Google Sheets document. This acts as a blank template for each new session.\n\n**Rename Sheet**\n Renames the newly duplicated sheet with the current `session_id` to clearly identify the session it belongs to.\n\n\n**TODO**\nUpdate the document ID with your own Google Sheets file before running the workflow." }, "typeVersion": 1 }, { "id": "8eaba5f3-3455-43c0-bffa-6a0bbde39de7", "name": "Sticky Note10", "type": "n8n-nodes-base.stickyNote", "position": [ 220, 140 ], "parameters": { "color": 7, "width": 620, "height": 720, "content": "## Store transcripts in Google Sheets\nThis section writes each session's messages into its corresponding sheet.\n\n**What it does:**\n* Appends new rows to the sheet named after the `session_id`.\n* Each row contains:\n * **Who**: The speaker type (e.g. user, assistant), taken from `message.type`\n * **Message**: The message content, from `message.content`\n * **Date**: Timestamp of the message, formatted as `yyyy-MM-dd hh:mm:ss` using `created_at`\n\n\n**TODO**\nMake sure your sheet includes the following column headers in the first row: `Who`, `Message`, `Date`." }, "typeVersion": 1 }, { "id": "6f802f26-e4b2-4787-bc0c-f3f800d79f74", "name": "Sticky Note11", "type": "n8n-nodes-base.stickyNote", "position": [ -1800, 140 ], "parameters": { "color": 7, "width": 280, "height": 720, "content": "## Triggers\nTest it manually, then, once validated, create a scheduler for it to run hourly, daily, weekly... or even create an external trigger. \nYour choice!" }, "typeVersion": 1 }, { "id": "145ee4b6-d492-4533-83a0-2096aff97cca", "name": "Sticky Note1", "type": "n8n-nodes-base.stickyNote", "position": [ -2740, -620 ], "parameters": { "color": 7, "width": 860, "height": 1480, "content": "# Store n8n AI Agent Memory Logs in Google Sheets\n\n## Overview\nThis n8n workflow retrieves AI agent chat memory logs stored in Postgres and pushes them to Google Sheets, creating one sheet per session. It’s useful for teams building chat-based products or agents and needing to review or analyze session logs in a collaborative format.\n\n## Who is it for\n* Anyone with an AI Agent in Production storing the conversation logs in Postgres (or Supabase) who wants to see transcript and have control\n* Product teams building AI agents or assistants.\n* Teams that want to centralize conversation history for analysis or support.\n* Anyone managing AI chat memory and needing to explore it in a spreadsheet.\n\n## Prerequisites\n* A Postgres database with a `n8n_chat_histories` table with an AI Agent connected to it. If you need an example, you can follow [this tutorial](https://www.youtube.com/watch?v=JjBofKJnYIU)\n* Once done, you need to run the Postgresql query to add the `created_at` column (see Setup > Add a datetime column)\n* Google Sheets access and OAuth credentials connected to n8n.\n* A Google Sheets document set up as a template (see below).\n\n## Google Sheets Template\nThis workflow expects a Google Sheets file where each session will be stored in its own tab. \nA basic tab layout is duplicated and renamed with the session ID.\n👉 [Use this template as a starting point](https://docs.google.com/spreadsheets/d/14bKI5J0h18Nv48jbe1IXpZWma6EtqYLFWnpKoCB5Bgc/edit?usp=sharing)\nNote: You can hide the template after the first tabs have been created\n\n## How it works\n1. **Trigger**\n The workflow can be launched manually or on a schedule (e.g. daily at noon).\n\n2. **Retrieve sessions**\n Runs a SQL query to get distinct `session_id` values from the `n8n_chat_histories` table.\n\n3. **Loop over sessions**\nFor each session:\n * Clears the corresponding sheet (if it exists).\n * Duplicates the template tab.\n * Renames it with the current `session_id`.\n\n4. **Fetch messages**\n Selects all messages linked to the session from Postgres.\n\n5. **Append to sheet**\n Adds each message to the Google Sheet with columns:\n\n * **Who**: speaker role (`user`, `assistant`, etc.)\n * **Message**: text content\n * **Date**: timestamp from `created_at`, formatted `yyyy-MM-dd hh:mm:ss`\n\n## Notes\n* The sheet is **cleared and rebuilt** each run to ensure logs are up-to-date.\n* If a sheet for a session doesn’t exist, it will be created by duplicating the first tab (template)\n* You can group sessions under a persistent ID (like `user_id`) by overriding `session_id` in your memory config.\n* Works perfectly with Supabase by using PG credentials from the connection pooler.\n" }, "typeVersion": 1 } ], "pinData": {}, "connections": { "Rename Sheet": { "main": [ [ { "node": "Set session_id", "type": "main", "index": 0 } ] ] }, "Set session_id": { "main": [ [ { "node": "Clear Sheet Content", "type": "main", "index": 0 } ] ] }, "Schedule Trigger": { "main": [ [ { "node": "Postgres - Get session ids", "type": "main", "index": 0 } ] ] }, "Add conversations": { "main": [ [ { "node": "Loop Over Session IDs", "type": "main", "index": 0 } ] ] }, "Clear Sheet Content": { "main": [ [ { "node": "Get conversations by sessionId", "type": "main", "index": 0 } ], [ { "node": "Duplicate template sheet", "type": "main", "index": 0 } ] ] }, "Loop Over Session IDs": { "main": [ [], [ { "node": "Clear Sheet Content", "type": "main", "index": 0 } ] ] }, "Duplicate template sheet": { "main": [ [ { "node": "Rename Sheet", "type": "main", "index": 0 } ] ] }, "Postgres - Get session ids": { "main": [ [ { "node": "Loop Over Session IDs", "type": "main", "index": 0 } ] ] }, "Get conversations by sessionId": { "main": [ [ { "node": "Add conversations", "type": "main", "index": 0 } ] ] }, "When clicking ‘Test workflow’": { "main": [ [ { "node": "Postgres - Get session ids", "type": "main", "index": 0 } ] ] } } }
Automate file organization in Google Drive using AI-powered automation with Pinecone's Vector Storage and OpenAI's GPT. This workflow allows you to intelligently categorize and retrieve files based on content, reducing the time spent searching for documents. Features include automatic file tagging for enhanced searchability, real-time syncing with Google Drive, and AI-driven insights for file management. Perfect for businesses managing thousands of documents, ensuring easy access and organization. Requires 3 accounts: Pinecone API, OpenAI API, Google Drive OAuth. Save up to 10 hours weekly by quickly locating and managing files with AI-enhanced context.
Automate the transcription of audio files stored in Google Drive directly to Notion using OpenAI’s powerful AI capabilities. This workflow streamlines the process of converting spoken content into written form, making your notes readily accessible and organized. Features include real-time syncing between Google Drive and Notion, automatic transcription of audio files, and integration with OpenAI GPT for enhanced text accuracy. Perfect for content creators, educators, and researchers who need quick access to transcribed material. Requires 4 accounts: Google Drive OAuth, Notion API, and others. Save up to 3 hours per week by automating your transcription process, allowing for faster content creation and better organization.
Automate your Google Drive file operations using HTTP requests and n8n workflows. This solution streamlines file uploads and organization, ensuring your documents are always in the right place without manual effort. Features real-time sync across Google Drive, automatic file categorization based on predefined rules, and seamless integration with external APIs for enhanced functionality. Perfect for developers managing large volumes of files or integrators seeking to simplify data flows in applications. Requires 1 account: Google Drive OAuth. Save up to 10 hours a week on file management tasks and improve data accessibility across your teams.
Automate the extraction and analysis of information from PDFs using an AI-powered workflow with OpenAI GPT, Google Gemini, and Google Drive API. Upload your PDF documents, and let the automation handle the RAG (Red, Amber, Green) assessment in real-time. Features include advanced AI embeddings for contextual understanding, document batch processing for efficiency, and seamless integration with Google Drive for easy access. Perfect for data analysts and project managers who need to evaluate project statuses from PDF reports. Requires 4 accounts: OpenAI API, Google Gemini API, Qdrant API, and Google Drive API. Save up to 10 hours weekly by automating data assessment and ensure accurate insights with reduced manual effort.
Configure credentials and update service-specific settings before executing the workflow. Review required credentials in the Technical Details section above.