Connect a Google Sheet to your website without a backend
Learn how to read and write a Google Sheet from a static site using fetch() - no server, no database, no Apps Script. A practical no-backend guide with real GET and POST examples powered by GKit SheetsAPI.
Why use a Google Sheet as your backend
Plenty of websites need a little bit of dynamic data - a waitlist, a list of products, form submissions, a content table someone non-technical can edit. Spinning up a server, a database, and an auth layer for that is overkill.
A Google Sheet is already a free, collaborative database with a UI your whole team knows. The only thing missing is an HTTP API. That's exactly what SheetsAPI adds: it turns any Google Sheet into a REST API your static site can call with plain fetch().
No server to deploy, no Apps Script to maintain. Your HTML/CSS/JS site on Netlify, Vercel, or GitHub Pages talks to the Sheet directly. CORS is enabled, so browser requests work out of the box.
SheetsAPI is currently in beta and free while in testing.
How the Sheet maps to JSON
The rule is simple: the first row of each tab defines the JSON field names. Every row below it becomes an object.
So a tab named products with a header row of name | price | inStock and one data row returns:
[{ "name": "Mug", "price": "12", "inStock": "true" }]Each Sheet belongs to a userKey, and each tab is addressed by sheetName. The base URL for every request is:
https://sheetsapi.gkit.mreshank.com/api
Reading data with fetch()
To list rows from a tab, do a GET against /api/spreadsheets/{userKey}/{sheetName}:
const BASE = "https://sheetsapi.gkit.mreshank.com/api";
async function getProducts() {
const res = await fetch(`${BASE}/spreadsheets/USER_KEY/products`);
const rows = await res.json();
return rows;
}You can shape the response right in the URL with query params:
// In-stock products, cheapest first, only name + price, 10 per page
const url = `${BASE}/spreadsheets/USER_KEY/products`
+ `?search=inStock:true&sort=price&fields=name,price&limit=10&offset=0`;
const res = await fetch(url);
const rows = await res.json();Useful list params:
limit(max 1000) andoffsetfor paginationsearch=field:valuefor a case-insensitive substring match (usesearch_exactfor exact matches)sort=field, orsort=-fieldfor descendingfields=a,b,cto return only the columns you needformat=json|csv|tsv|xml|jsonp(withcallbackfor JSONP)
To fetch a single row by its 1-based position, add the row number:
const res = await fetch(`${BASE}/spreadsheets/USER_KEY/products/3`);
const row = await res.json(); // the 3rd data rowWriting data with a POST
Appending a row is a POST to the same tab URL. Send a single object, or an array to append several at once:
async function addProduct(product) {
const res = await fetch(`${BASE}/spreadsheets/USER_KEY/products`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(product),
});
return res.json();
}
// addProduct({ name: "Notebook", price: "8", inStock: "true" });Updating and deleting work the same way, addressing a row by its number:
# Update row 3
curl -X PUT https://sheetsapi.gkit.mreshank.com/api/spreadsheets/USER_KEY/products/3 \
-H "Content-Type: application/json" \
-d '{"price":"9"}'
# Delete row 3
curl -X DELETE https://sheetsapi.gkit.mreshank.com/api/spreadsheets/USER_KEY/products/3That's the full CRUD set - list, read one, append, update, delete - against a Google Sheet, straight from the browser.
Locking it down for production
While you're prototyping, your API is public by default, which is perfect for quick reads on a static site. Once you're ready to protect writes, create an API key and SheetsAPI starts requiring it.
After that, send the key as a bearer token on every request:
const res = await fetch(`${BASE}/spreadsheets/USER_KEY/products`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer sk_your_key_here",
},
body: JSON.stringify({ name: "Notebook", price: "8", inStock: "true" }),
});A practical pattern: keep public, read-only endpoints on the front end, and move any writes that need a secret key into a serverless function so the key never ships to the browser. See the REST API reference for the details on keys and Google OAuth.
Next steps
You've now got a working data layer with zero infrastructure - the Sheet is your database, and fetch() is your client. For the complete list of endpoints, parameters, and response formats, read the REST API reference.
Ready to try it? Sign in with Google to connect your first Sheet. It's free while in beta.