n8n and Make Google Sheets automation with SheetDB - SheetDB

How to automate Google Sheets with n8n or Make using SheetDB

A Google Sheet is the most common "database" in small automations: new leads land in a sheet, a sheet drives a daily report, a sheet holds the list of customers to e-mail. n8n and Make both ship Google Sheets nodes, and they work. But every one of those nodes needs a Google credential, which means a Google Cloud project with the Sheets API enabled and an OAuth consent screen for self-hosted n8n, a Google account connection that can expire and has to be re-authorised, and a permission scope that covers every spreadsheet in that account.

This guide takes a different route. SheetDB turns one spreadsheet into a JSON REST API with plain HTTPS URLs. In n8n you call it with the HTTP Request node, in Make with the HTTP module, and neither tool ever talks to Google. You also get server-side search, sorting and limits, a cache that protects Google's quota, and an API that the same sheet can share with your website or app. Below you will find the four requests every automation needs, a step-by-step n8n setup, a ready-made n8n workflow you can import, and the equivalent Make scenario.

Every example targets a real demo spreadsheet, this one, exposed at https://sheetdb.io/api/v1/58f61be4dda40. Its first tab has the columns id, name, age and comment, and it resets every 15 minutes, so you can run the write requests without breaking anything.

Step 1: create the API for your sheet

  1. Sign in to SheetDB with the Google account that can edit the spreadsheet.
  2. Click Create new API, paste the spreadsheet URL and save. You get an API URL in the form https://sheetdb.io/api/v1/<your id>.
  3. Open it in a browser: the rows of the first tab come back as a JSON array of objects, keyed by the header row. Make sure row 1 holds clean column names (email, not E-mail address); they become the keys you map in the workflow.
  4. Before the workflow goes live, open the API Settings, enable HTTP Basic Auth or a Bearer token (authentication) and disable the permissions the workflow does not need.

Read, add, update, delete: the four requests

Whatever tool you use, the automation is a sequence of these HTTP calls. Shown with curl so you can test them from a terminal first; the n8n and Make sections translate them into node settings.

# every row of the first tab
curl https://sheetdb.io/api/v1/58f61be4dda40

# rows matching a condition (AND); /search_or for OR
curl "https://sheetdb.io/api/v1/58f61be4dda40/search?comment=special&age=>18"

# add one or more rows
curl -X POST https://sheetdb.io/api/v1/58f61be4dda40 \
  -H "Content-Type: application/json" \
  -d '{"data": [{"id": "INCREMENT", "name": "Maria", "age": "30", "comment": "from curl"}]}'
# → {"created": 1}

# update the row(s) where id = 4
curl -X PATCH https://sheetdb.io/api/v1/58f61be4dda40/id/4 \
  -H "Content-Type: application/json" \
  -d '{"data": {"comment": "special, updated"}}'
# → {"updated": 1}

# delete the row(s) where name = Maria
curl -X DELETE https://sheetdb.io/api/v1/58f61be4dda40/name/Maria
# → {"deleted": 1}
  • Read (GET /api/v1/{id}) returns the whole tab; ?sheet=Name picks another tab, limit, offset, sort_by and sort_order page and order the rows, cast_numbers=age returns listed columns as numbers. Docs: read.
  • Search (GET /search?column=value) filters on the server. Values accept * as a wildcard, ! for negation and >, <, >=, <=; /search_or matches any condition instead of all. Docs: search.
  • Add (POST /api/v1/{id}) appends the objects in data; one request can insert many rows. INCREMENT, TIMESTAMP and DATETIME are special values filled in by SheetDB. Docs: create.
  • Update (PATCH /{column}/{value}) changes only the fields in data on every row that matches; PUT is accepted on the same URL. Docs: update.
  • Delete (DELETE /{column}/{value}) removes every matching row; the value uses the search syntax, so be precise. Docs: delete.

Step 2: read rows in n8n with the HTTP Request node

  1. Create a workflow, add a trigger (start with the manual trigger, Execute workflow) and add an HTTP Request node after it.
  2. Leave Method on GET and set URL to https://sheetdb.io/api/v1/58f61be4dda40/search.
  3. Turn on Send Query Parameters and add a parameter with Name comment and Value special. n8n encodes the query string for you; you could also type the full URL with ?comment=special and leave the toggle off.
  4. Leave Authentication on None for the demo. For your own API choose Generic Credential Type → Basic Auth (username and password from the SheetDB settings) or Header Auth with the name Authorization and the value Bearer <token>.
  5. Execute the node. The output panel shows one item per matching row, with id, name, age and comment as fields. n8n splits the JSON array into items automatically, so the next node runs once per row.

That is already useful on its own: put a Schedule Trigger in front and a Gmail, Slack or CRM node behind, and you have "for every row where status is new, do something" without a line of code.

Step 3: add, update and delete rows from n8n

Add a row

  1. Add another HTTP Request node. Method POST, URL https://sheetdb.io/api/v1/58f61be4dda40.
  2. Turn on Send Body, keep Body Content Type on JSON and set Specify Body to Using JSON.
  3. Paste the body below into the JSON field and switch the field to Expression mode, so the {{ … }} parts are evaluated with the data of the incoming item:
{
  "data": [
    {
      "id": "INCREMENT",
      "name": "{{ $json.name }} (copy)",
      "age": "{{ $json.age }}",
      "comment": "added by n8n"
    }
  ]
}

Quote every value, even numbers: a cell is text, and a missing value would otherwise produce invalid JSON. If you prefer clicking to typing, leave Specify Body on Using Fields Below and add one field per column (id, name, age, comment): SheetDB also accepts a flat object without the data wrapper and treats it as one row. The JSON form is easier to read and copy, and it is the only way to insert several rows in one request. The response is {"created": 1}. To insert all incoming items in one request instead of one request per item, put an Aggregate node before the HTTP Request and build data from the aggregated list.

Update a row

Method PATCH, URL in expression mode: https://sheetdb.io/api/v1/58f61be4dda40/id/{{ $json.id }}, Send Body on, JSON body {"data": {"comment": "processed"}}. Only the listed fields change. The value after /id/ can be any column and any search expression, so /status/new marks every new row as processed in a single request; the response tells you how many rows it touched, for example {"updated": 3}.

Delete a row

Method DELETE, URL https://sheetdb.io/api/v1/58f61be4dda40/id/{{ $json.id }}, no body. The response is {"deleted": 1}. Because a wildcard such as /name/* deletes every matching row, never build this URL from untrusted input. A safer pattern for "remove processed rows" is a PATCH that sets a status column and a filter on that column when reading.

Step 4: import the ready-made n8n workflow

The workflow below is a real n8n export. It searches the demo sheet for rows whose comment is "special", adds a copy of each row with INCREMENT as the id, then writes a timestamp into the comment of the original row. Copy the JSON, open an empty workflow in n8n and paste it on the canvas (Ctrl+V or Cmd+V), or save it as a .json file and use Import from File in the workflow menu. Click Execute workflow and then open the demo sheet to see the new row and the updated comment.

{
    "name": "SheetDB – Google Sheets: search, add, update",
    "nodes": [
        {
            "parameters": [],
            "id": "c3a1f0e2-0d3b-4a7e-9d51-6f2a8b1c4d01",
            "name": "Run manually",
            "type": "n8n-nodes-base.manualTrigger",
            "typeVersion": 1,
            "position": [
                0,
                0
            ]
        },
        {
            "parameters": {
                "url": "https://sheetdb.io/api/v1/58f61be4dda40/search",
                "sendQuery": true,
                "queryParameters": {
                    "parameters": [
                        {
                            "name": "comment",
                            "value": "special"
                        }
                    ]
                },
                "options": []
            },
            "id": "c3a1f0e2-0d3b-4a7e-9d51-6f2a8b1c4d02",
            "name": "Search rows (GET)",
            "type": "n8n-nodes-base.httpRequest",
            "typeVersion": 4.2,
            "position": [
                220,
                0
            ]
        },
        {
            "parameters": {
                "method": "POST",
                "url": "https://sheetdb.io/api/v1/58f61be4dda40",
                "sendBody": true,
                "specifyBody": "json",
                "jsonBody": "={\n  \"data\": [\n    {\n      \"id\": \"INCREMENT\",\n      \"name\": \"{{ $json.name }} (copy)\",\n      \"age\": \"{{ $json.age }}\",\n      \"comment\": \"added by n8n\"\n    }\n  ]\n}",
                "options": []
            },
            "id": "c3a1f0e2-0d3b-4a7e-9d51-6f2a8b1c4d03",
            "name": "Add row (POST)",
            "type": "n8n-nodes-base.httpRequest",
            "typeVersion": 4.2,
            "position": [
                440,
                0
            ]
        },
        {
            "parameters": {
                "method": "PATCH",
                "url": "=https://sheetdb.io/api/v1/58f61be4dda40/id/{{ $('Search rows (GET)').item.json.id }}",
                "sendBody": true,
                "specifyBody": "json",
                "jsonBody": "={ \"data\": { \"comment\": \"special, seen by n8n {{ $now.toFormat('yyyy-MM-dd HH:mm') }}\" } }",
                "options": []
            },
            "id": "c3a1f0e2-0d3b-4a7e-9d51-6f2a8b1c4d04",
            "name": "Update row (PATCH)",
            "type": "n8n-nodes-base.httpRequest",
            "typeVersion": 4.2,
            "position": [
                660,
                0
            ]
        }
    ],
    "connections": {
        "Run manually": {
            "main": [
                [
                    {
                        "node": "Search rows (GET)",
                        "type": "main",
                        "index": 0
                    }
                ]
            ]
        },
        "Search rows (GET)": {
            "main": [
                [
                    {
                        "node": "Add row (POST)",
                        "type": "main",
                        "index": 0
                    }
                ]
            ]
        },
        "Add row (POST)": {
            "main": [
                [
                    {
                        "node": "Update row (PATCH)",
                        "type": "main",
                        "index": 0
                    }
                ]
            ]
        }
    },
    "settings": {
        "executionOrder": "v1"
    },
    "active": false
}

To point it at your own sheet, replace the API URL in the three HTTP Request nodes, and if your API uses authentication, open each node, set Authentication to Generic Credential Type and pick the Basic Auth or Header Auth credential you created. Note two n8n details used in the workflow: the PATCH node refers to the row found by the first node with $('Search rows (GET)').item.json.id, because the item coming out of the POST node is the API response ({"created": 1}), not the row; and the expressions start with =, which is how n8n marks a parameter as an expression in an export.

Step 5: schedule it, secure it, handle errors

  • Triggers. Replace the manual trigger with a Schedule Trigger for periodic sync, a Webhook node to accept form submissions or CRM events, or the trigger node of the app you want to sync (HubSpot, Pipedrive, Shopify, Typeform…) followed by the POST request from step 3.
  • Credentials. Keep the SheetDB username and password or token in an n8n credential, never in the URL. In the SheetDB settings, leave enabled only the permissions the workflow uses; a workflow that only appends rows needs create and nothing else.
  • Errors. SheetDB answers with normal HTTP status codes and a JSON error field (errors). In the HTTP Request node settings enable Retry On Fail with a few seconds between tries for transient Google errors, and use Continue (using error output) to route failures to a notification instead of stopping the run.
  • Rate limits and quota. Each node execution is one SheetDB request, and the API has per-minute rate limits listed on the limits page. When a workflow processes many items, insert them in one POST as described above, or enable Batching in the node options with a small interval. Reads within the cache window (15 seconds by default, see cache) are served from cache and do not hit Google.

Step 6: the same automation in Make

Make (formerly Integromat) has the same building block, the HTTP app with the Make a request module. A scenario that searches the sheet and adds a row looks like this:

  1. Create a scenario and add HTTP → Make a request. URL: https://sheetdb.io/api/v1/58f61be4dda40/search, Method: GET. Under Query String add an item with the name comment and the value special. Set Parse response to Yes, so Make turns the JSON into mappable fields.
  2. Run the module once (Run once) to load the data structure. The rows are in the module's Data output ({{1.data}}), an array; add an Iterator (Flow control) after it and set its Array to that field, so every row becomes a bundle of module 2 with the columns as fields ({{2.name}}, {{2.age}}).
  3. Add a second HTTP → Make a request module. URL: https://sheetdb.io/api/v1/58f61be4dda40, Method: POST, body content type JSON (application/json) entered as a raw JSON string (in the classic module that is Body type Raw, Content type JSON and the Request content field; in the current one Body content type application/JSON with Body input method JSON string). Paste the body below; the values are mapped from the iterator (module 2):
{
  "data": [
    {
      "id": "INCREMENT",
      "name": "{{2.name}}",
      "age": "{{2.age}}",
      "comment": "added by Make"
    }
  ]
}
  1. For updates and deletes use the same module with Method PATCH or DELETE and a URL such as https://sheetdb.io/api/v1/58f61be4dda40/id/{{2.id}}, mapping the id from the iterator.
  2. To authenticate, use the HTTP → Make a Basic Auth request module with the username and password from the SheetDB settings, or keep Make a request and add an Authorization header with Bearer <token> under Headers.
  3. Set the scenario schedule (the clock icon on the first module), or start it from a Webhooks → Custom webhook module.

Every module run is one operation in Make and one request in SheetDB, so the same advice applies: search on the server, insert many rows per request, and let the cache absorb repeated reads.

Example automations

  • Sync CRM data with Google Sheets. Trigger on a new or updated contact in your CRM node, then GET /search?email={{ $json.email }}. If the search returns a row, PATCH /email/… with the changed fields; if it returns an empty array, POST a new row. An empty array gives the HTTP Request node no output items, so the workflow would stop there: turn on Always Output Data in the node settings, which passes one empty item instead, and branch with an IF node on whether {{ $json.email }} exists.
  • Form to sheet to e-mail. A Webhook node receives the submission, a POST appends it with DATETIME in a created column, and a Gmail node sends the confirmation. The HTML form to Google Sheets guide covers the form side, including posting to SheetDB directly when no workflow is needed.
  • Daily report. Schedule Trigger at 8:00, GET /search?status=open&sort_by=age&sort_order=desc, an Aggregate node, and a Slack or e-mail message rendered from the rows.
  • Sheet as a lookup table. Keep prices, mappings or feature flags in a tab, read it with GET /search?key=…&single_object=true inside any workflow, and let non-technical colleagues change the values in the sheet instead of in the workflow.

Limitations: when the built-in nodes are the better choice

  • Cell-level operations. SheetDB works with rows keyed by the header row. If the automation needs to write to arbitrary cell ranges, format cells or add sheets with specific layouts, the Google Sheets node with a Google credential is the right tool.
  • Many spreadsheets. One SheetDB API covers one spreadsheet (all its tabs). A workflow that touches dozens of different files is easier with one Google credential than with dozens of APIs, unless you create them programmatically through the global API.
  • Very high volume. A spreadsheet is not a queue. Thousands of writes per hour hit Google's own per-spreadsheet limits regardless of the tool in front; batch rows per request and consider a database when you get there. The Google Sheets as a database guide has the numbers that matter.
  • Private per-user data. The API URL plus credentials gives access to every row of the exposed tabs. Keep secrets and personal data out of tabs that automations or websites read.

For everything else, an HTTPS URL that any tool can call is the simplest integration there is, and the same URL powers a table on your website, a WordPress page, a Bubble or Adalo app and your own code.

FAQ

Why use SheetDB instead of the built-in Google Sheets node in n8n or Make?

The built-in nodes work, but each of them needs a Google credential: a Google Cloud project with the Sheets API enabled and an OAuth consent screen for self-hosted n8n, and a Google account connection that can expire and has to be re-authorised. SheetDB replaces that with a plain HTTPS URL. You also get server-side search, sorting, limits and caching, and the same API serves your website or app, so the sheet does not depend on the automation tool.

How do I add a row to Google Sheets from n8n?

Use the HTTP Request node with method POST, the API URL, Send Body on, body content type JSON and a body such as {"data": [{"name": "…", "age": "…"}]}, where the keys are the column headers of the sheet. Use expressions like {{ $json.email }} to fill the values from the previous node.

How do I update or delete a row from a workflow?

Send PATCH (update) or DELETE to /api/v1/{id}/{column}/{value}, for example /id/4. The value accepts the same syntax as search, including wildcards and comparisons, so /status/pending updates every pending row. The response tells you how many rows changed: {"updated": 1} or {"deleted": 1}.

Does every workflow run count as a SheetDB request?

Every HTTP Request node execution is one request, whether it reads one row or a thousand. To stay within your plan quota, insert many rows in one POST (the data array can hold many objects), read once and filter in the workflow, and let the cache serve repeated reads. The current rate limits are on the limits page.

Can I do the same in Make?

Yes. Make's HTTP → Make a request module has the same fields: URL, method, headers, query string and a JSON body. The Make section above walks through a scenario that searches the sheet and adds a row; Make parses the JSON response so the columns are available as mappable fields in the next module.

How do I stop anyone with the URL from writing to my sheet?

Enable HTTP Basic Auth or a Bearer token in the SheetDB API settings, then store the credentials in n8n (Authentication → Generic Credential Type → Basic Auth or Header Auth) or in the Make HTTP module. In the API settings also disable the permissions the workflow does not need. If the workflow only writes, turn read off; if it only reads, turn create, update and delete off.

Automate your Google Sheet in minutes

Sign in with Google, paste the spreadsheet URL and call the API URL from n8n, Make or any other tool. Free plan, no credit card, no Google Cloud project.

Create free API

Have question?

If you have any questions feel free to ask us via chat or .