How to connect Bubble to Google Sheets
Bubble has a good database of its own, but a lot of the data an app needs already lives in a Google Sheet: a price list the sales team maintains, a catalogue a client edits, sign-ups collected by a form, a list of locations. Rather than copying it into Bubble and keeping two versions in sync, you can read and write the sheet directly from the app.
Bubble cannot talk to Google Sheets out of the box. Google's own Sheets API requires a Google Cloud project, an OAuth consent screen and refresh tokens, which is a lot of setup for a no-code project and does not fit the API Connector's authentication options well. SheetDB removes that layer: it turns a spreadsheet into a JSON REST API with plain HTTPS URLs, and Bubble's API Connector handles plain HTTPS URLs very well. This guide sets up the connection and configures the four calls every app needs: read, add, update and delete.
All examples use 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 add and delete rows while you follow along.
What you need
- A Bubble app on any plan. The API Connector is a free, Bubble-made plugin.
- A Google Sheet whose first row contains the column names. Column names become the JSON keys, so keep them short, lowercase and without spaces (
name,age) to make the Bubble expressions readable. - A SheetDB account. The free plan is enough for building and testing.
Step 1: create the API for your sheet
- Sign in to SheetDB with the Google account that owns or can edit the spreadsheet.
- Click Create new API, paste the spreadsheet URL and save. SheetDB shows the API URL, in the form
https://sheetdb.io/api/v1/<your id>. - Open the URL in a browser tab. You should see the rows of the first tab as a JSON array: one object per row, keyed by the header row. This is exactly what Bubble will receive.
- Optional but recommended for anything beyond a prototype: in the API Settings enable authentication (HTTP Basic Auth or a Bearer token) and disable the permissions you will not use. The permissions and authentication docs describe the options.
The demo response looks like this, and every value is a string, because that is what a spreadsheet cell holds. We will come back to that in the tips section.
[
{ "id": "1", "name": "Tom", "age": "15", "comment": "" },
{ "id": "2", "name": "Alex", "age": "24", "comment": "" },
{ "id": "3", "name": "John", "age": "51", "comment": "" },
{ "id": "4", "name": "Steve", "age": "22", "comment": "special" },
{ "id": "5", "name": "James", "age": "19", "comment": "" }
]
Step 2: add SheetDB in Bubble's API Connector
- In the Bubble editor open Plugins, click Add plugins, search for API Connector and install it. It is made by Bubble and free.
- Open the API Connector and click Add another API. Give it a Name such as
SheetDB; this name prefixes every call in the editor. - Pick the Authentication that matches your SheetDB settings. For the demo choose None or self-handled. If you enabled Basic Auth in step 1, choose HTTP Basic Auth and enter the username and password from the API settings; for a Bearer token choose Private key in header, set the key name to
Authorizationand the value toBearer <your token>. Bubble adds these credentials on its servers, so they never reach the visitor's browser. - Under Shared headers for all calls add
Content-Typewith the valueapplication/json. The write calls send JSON bodies and this saves repeating the header.
Two API Connector conventions are used throughout the rest of the guide. A name in square brackets inside a URL, like [name], becomes a call parameter you can fill from the Bubble expression editor. A name in angle brackets inside a body, like <age>, does the same for the request body. Tick Private on a parameter to keep it on the server; untick it when its value should come from the page, for example from an input.
Step 3: read the sheet into a Repeating Group
- Click Add another call under the SheetDB API. Name it
Get people. - Set Use as to Data and Data type to JSON.
- Choose GET and enter the API URL:
https://sheetdb.io/api/v1/58f61be4dda40. - Click Initialize call. Bubble makes the request and shows the fields it found (
id,name,age,comment) with a detected type for each. Check the types and click Save. - On a page, draw a Repeating Group. Set its Type of content to
Get people(Bubble lists it as SheetDB - Get people) and its Data source to Get data from an external API → SheetDB - Get people. - Inside the first cell add a Text element with Current cell's Get people's name, another with … age, and preview the page. The rows of the sheet appear, one per cell.
Every column in the header row is available as a field, and because the data is fetched when the page loads, the next visitor sees whatever is in the sheet at that moment (with a short cache, see the tips section).
Read, add, update, delete
The four operations map to four API Connector calls. Reads are Data calls that you use as data sources; writes are Action calls that you run from workflows. Each subsection lists the settings to enter and the SheetDB documentation page with every optional parameter.
Read and search rows
Get people from step 3 returns the whole tab. To load only matching rows, add a second Data call, Search people, with a GET request to the search endpoint. The bracketed [name] becomes a parameter; untick Private so the page can set it, and enter a sample value such as Steve before initializing:
https://sheetdb.io/api/v1/58f61be4dda40/search?name=[name]&cast_numbers=id,age
The search value accepts * as a wildcard (Ste*), ! for negation and >, <, >=, <= for comparisons, and you can add several column=value conditions to the URL. The other read parameters work here too: limit and offset for paging, sort_by and sort_order for ordering, sheet to read another tab. Doing the filtering on the API side keeps the response small, which matters on mobile and against your request quota.
Add a row
Create an Action call named Add person: method POST, URL https://sheetdb.io/api/v1/58f61be4dda40, Body type JSON, and this body. The keys are the column names; data is an array, so one call can insert several rows if you need to.
{
"data": [
{ "id": "INCREMENT", "name": "<name>", "age": "<age>", "comment": "<comment>" }
]
}
Keep the quotes around the placeholders: a cell is text, and an unquoted <age> would produce invalid JSON when the input is empty. INCREMENT is a special value SheetDB replaces with the highest number in that column plus one, which gives every row a unique id without any logic in Bubble. Untick Private on name, age and comment, enter sample values, initialize, and check that the response is {"created": 1}. Then, in a workflow, for example When Button Save is clicked, add the action from Plugins → SheetDB - Add person and map the body parameters to your inputs' values. Details and the optional return_values parameter are on the create page.
Update a row
Updates address rows by a column and a value in the URL: /{column}/{value}. Create an Action call Update person with method PATCH and this URL, where [id] is a non-private parameter:
https://sheetdb.io/api/v1/58f61be4dda40/id/[id]
The JSON body contains only the fields to change; the other cells of the row stay as they are:
{
"data": { "comment": "<comment>" }
}
Initialize it with id = 4 and any comment; the response is {"updated": 1}. In the workflow, map id to Current cell's Get people's id (or Parent group's …) so the action edits the row the user clicked. The URL value uses the same syntax as search, so /comment/special would update every row whose comment is "special". PUT on the same URL is accepted as well; see the update docs.
Delete a row
Deleting works the same way, without a body: an Action call Delete person, method DELETE, and the URL below. The response is {"deleted": 1}. Because a wildcard in the value deletes every matching row, keep the id parameter tied to a specific record and never to a free-text input. Reference: delete.
https://sheetdb.io/api/v1/58f61be4dda40/id/[id]
Tips: field types, refreshing and the request quota
- Numbers. The API returns text. Add
cast_numbers=id,ageto the URL of your read calls (as in the search example above) and click Reinitialize call; Bubble then detects those fields as numbers, so you can sort, sum and compare them. - Refreshing after a write. A Repeating Group with an API data source does not know that your action changed the sheet. After Add person or Delete person, add the element action Display list on the Repeating Group with the same Get data from an external API source, or navigate to the page again. SheetDB clears its cache when a write goes through the API, so the next read is fresh.
- Cache. Read responses are cached for 15 seconds by default (adjustable per API in its settings, see cache). Edits made by hand in the spreadsheet show up in the app within that time.
- Quota. Each API Connector call is one SheetDB request. Load a list once per page view rather than once per cell, use search instead of loading everything and filtering with
:filtered, and check the limits page for rate limits. - Several tabs. Add
?sheet=Ordersto any URL to work with another tab of the same spreadsheet; one SheetDB API covers the whole file. - Testing writes. The demo API accepts writes so you can try the calls, but it resets every 15 minutes. Use your own API for anything you want to keep.
Limitations: when not to use a sheet as your Bubble database
- Per-user private data. Bubble's privacy rules do not apply to API Connector data. Anyone who can trigger the call gets the rows it returns, so keep user accounts, payments and personal records in Bubble's database and use the sheet for shared content.
- Large or hot tables. A read returns the whole tab or the matching rows in one response. Hundreds of rows are fine; tens of thousands of rows or many writes per second are not what a spreadsheet is for. The Google Sheets as a database guide explains where the limits are.
- Relationships. There are no joins. Keep one tab per entity, store an
idof the related row, and fetch related data with a second call, or denormalise. - Exposed URL. API Connector calls run on Bubble's servers, but a description of each call, including the URL and every parameter not marked as private, is sent to the browser. Enable authentication in SheetDB, keep the credentials in the API Connector, and disable unused permissions so that the URL alone is not enough to change the sheet.
If the sheet is the right place for the data, this setup gives you a back office your team already knows: they edit rows in Google Sheets, the Bubble app shows and writes them. The same API also feeds a table on your website, an Adalo app or an n8n or Make automation, all from one spreadsheet.
FAQ
Does Bubble have a native Google Sheets integration?
No. Bubble connects to Google Sheets either through a marketplace plugin that wraps Google's own API (you need a Google Cloud project, OAuth consent and token handling) or through the API Connector talking to a REST API. SheetDB is the second option: it turns the sheet into a JSON API, and Bubble only has to call plain HTTPS URLs.
Do I need a Google Cloud project or OAuth to connect Bubble to Google Sheets?
No. You sign in to SheetDB with Google once and it reads and writes the sheet with your account. Bubble never talks to Google. If you want to protect the API, enable HTTP Basic Auth or a Bearer token in the SheetDB API settings and select the matching option in the API Connector; Bubble sends those credentials from its servers, not from the browser.
Why are numbers from my sheet text in Bubble?
The API returns every cell as a string, because that is what a cell holds, and Bubble types the fields from the sample response when you initialize the call. Add cast_numbers=age (a comma-separated list of columns) to the call URL, click Reinitialize call, and Bubble detects those fields as numbers. You can also change a field type by hand in the initialization dialog.
Can a Google Sheet be the main database of a Bubble app?
For content, catalogues, settings, forms and small back offices, yes; that is what the guide on using Google Sheets as a database covers, including limits. For user accounts, passwords and data that must stay private per user, keep Bubble's built-in database with privacy rules: an API call from the API Connector cannot be scoped to the logged-in user the way Bubble's database can.
How do I keep the sheet safe if the API URL is in my Bubble app?
Tick Private on every parameter that should not reach the browser, turn on authentication in the SheetDB API settings and in the API Connector, and disable the permissions you do not use (for example leave only Read, Search and Create on; the search call from this guide needs the Search permission). With authentication enabled, a URL alone is not enough to read or change the sheet.
Is the Bubble Google Sheets integration free?
The API Connector is included in every Bubble plan and SheetDB has a free plan with a monthly request quota. Each API Connector call is one SheetDB request, so use limit, the search endpoint and the built-in cache instead of loading the whole sheet on every page.
Connect your Bubble app to a Google Sheet
Sign in with Google, paste the spreadsheet URL and add the API URL to the API Connector. Free plan, no credit card, no Google Cloud project.
Create free APIHave question?
If you have any questions feel free to ask us via chat or .