
Google Sheets API in JavaScript: Setup, Auth, Reading Data
To use the Google Sheets API from JavaScript, enable the Sheets API in a Google Cloud project, pick an auth method (an API key for public sheets, a service account for server code, or OAuth for a user's own sheets), then call spreadsheets.values.get. In Node.js that's a few lines with the official googleapis (or the smaller @googleapis/sheets) package; in the browser you use Google's gapi client plus Google Identity Services. This guide covers setup, the three auth options and reading data. Writing, updating and deleting rows is covered in our Google Sheets API CRUD guide.
Is the Google Sheets API free?
Yes, for standard use. As of October 2026 the usage limits page says:
| Limit | Value |
|---|---|
| Read requests | 300 per minute per project, 60 per minute per user per project |
| Write requests | 300 per minute per project, 60 per minute per user per project |
| Daily cap | None, as long as you stay inside the per-minute quotas |
| Request timeout | 180 seconds |
| Payload size | No hard limit; 2 MB recommended |
Going over a quota returns HTTP 429 Too Many Requests; retry with exponential backoff. Google also says that exceeding the quota limits "is planned to incur charges" to your Cloud billing account later in 2026, so check that page if you run near the limits.
Step 1: Create a project and enable the API
- Open the Google Cloud console and create a project (or select one).
- Go to APIs & Services > Library, search for Google Sheets API, and click Enable.
- Go to APIs & Services > Credentials and create the credential type for your auth choice below.
The spreadsheet ID is the long string in the sheet URL: https://docs.google.com/spreadsheets/d/SPREADSHEET_ID/edit.
Step 2: Choose an auth method
| Method | Can read | Runs where | Use it for |
|---|---|---|---|
| API key | Only sheets shared as "Anyone with the link" | Browser or server | Public data: a published price list, a status table |
| Service account | Sheets shared with the service account's email | Server only (Node.js, Cloud Run, etc.) | Your app reading your own sheets |
| OAuth 2.0 (user) | The signed-in user's sheets | Browser or server | Apps that act on a user's own files |
Two rules hold for all of them:
- Use the narrowest scope that works. For reading, that's
https://www.googleapis.com/auth/spreadsheets.readonly. - Never ship a service account key or OAuth client secret to the browser.
Option A: API key for a public sheet (simplest)
Create an API key under Credentials > Create credentials > API key, then edit it: under API restrictions select only the Google Sheets API, and under Application restrictions limit it to your website's HTTP referrers (or your server's IPs). Share the sheet as Anyone with the link: Viewer.
No library is needed; plain fetch works in the browser and in Node.js 18+:
const SPREADSHEET_ID = 'your-spreadsheet-id';
const API_KEY = 'your-restricted-api-key';
const range = encodeURIComponent('Sheet1!A1:D');
const res = await fetch(
`https://sheets.googleapis.com/v4/spreadsheets/${SPREADSHEET_ID}/values/${range}?key=${API_KEY}`
);
if (!res.ok) throw new Error(`Sheets API error ${res.status}`);
const { values = [] } = await res.json();
console.log(values); // [["Name", "Price"], ["Pro", "12"], ...]
An API key cannot read a private sheet and cannot write; you'll get 403 or 404.
Option B: Service account in Node.js (server-to-server)
A service account is a robot Google identity for your backend.
- In IAM & Admin > Service Accounts, create a service account. It needs no project roles to read Sheets.
- Copy its email (
name@project-id.iam.gserviceaccount.com) and share the spreadsheet with that email as Viewer, exactly like sharing with a person. This step is what most "The caller does not have permission" errors come from. - Get credentials:
- Running on Google Cloud (Cloud Run, Cloud Functions, GKE): attach the service account to the service. The library finds it automatically through Application Default Credentials, so there is no key file.
- Running elsewhere: create a JSON key under the service account's Keys tab. Note that Google Cloud organizations created on or after May 3, 2024 block key creation by default through a secure-by-default org policy; an admin has to allow it, or you can use Workload Identity Federation instead.
Install the Sheets-only client. As of October 2026, current versions of googleapis and @googleapis/sheets require Node.js 22 or later:
npm install @googleapis/sheets
Read a range:
// read-sheet.mjs
import { sheets, auth } from '@googleapis/sheets';
const googleAuth = new auth.GoogleAuth({
keyFile: 'service-account.json', // omit on Google Cloud to use the attached account
scopes: ['https://www.googleapis.com/auth/spreadsheets.readonly'],
});
const api = sheets({ version: 'v4', auth: googleAuth });
const { data } = await api.spreadsheets.values.get({
spreadsheetId: process.env.SPREADSHEET_ID,
range: 'Orders!A2:E',
});
for (const [id, customer, total] of data.values ?? []) {
console.log(id, customer, total);
}
You can also set GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json and drop keyFile. Keep the JSON file out of Git.
The full googleapis package exposes the same call as google.sheets({ version: 'v4', auth }); it just installs clients for every Google API.
Option C: OAuth in the browser (user's own sheets)
Use this when a signed-in user should read their own spreadsheets. Create an OAuth client ID of type Web application, add your site under Authorized JavaScript origins, and configure the OAuth consent screen. Google's browser quickstart loads two scripts: https://apis.google.com/js/api.js (the API client) and https://accounts.google.com/gsi/client (Google Identity Services). Condensed:
// Called after both scripts have loaded
let tokenClient;
async function init() {
await new Promise((resolve) => gapi.load('client', resolve));
await gapi.client.init({
apiKey: API_KEY,
discoveryDocs: ['https://sheets.googleapis.com/$discovery/rest?version=v4'],
});
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: CLIENT_ID,
scope: 'https://www.googleapis.com/auth/spreadsheets.readonly',
callback: async (resp) => {
if (resp.error) throw resp;
const res = await gapi.client.sheets.spreadsheets.values.get({
spreadsheetId: SPREADSHEET_ID,
range: 'Sheet1!A1:D10',
});
console.log(res.result.values);
},
});
}
// Must run from a user gesture, e.g. a button click
document.querySelector('#authorize').onclick = () =>
tokenClient.requestAccessToken({ prompt: 'consent' });
The old gapi.auth2 sign-in library is deprecated; use Google Identity Services as above. Sensitive or restricted scopes may need Google's app verification before you can publish to the public.
Reading data: the details that trip people up
A1 notation. Ranges look like Sheet1!A1:D10, Sheet1!A:A (whole column) or just Sheet1. Quote sheet names with spaces: 'Q3 Sales'!A1:C.
Empty cells. Trailing empty rows and columns are omitted, so rows can have different lengths. Read with defaults: const [name = '', email = ''] = row;.
Value formatting. By default you get formatted strings ("$1,200.00"). Pass options to get raw values:
await api.spreadsheets.values.get({
spreadsheetId,
range: 'Orders!A2:E',
valueRenderOption: 'UNFORMATTED_VALUE', // numbers as numbers; 'FORMULA' returns formulas
dateTimeRenderOption: 'FORMATTED_STRING', // dates as text instead of serial numbers
});
Several ranges in one request. Each call counts against the per-minute quota, so batch:
const { data } = await api.spreadsheets.values.batchGet({
spreadsheetId,
ranges: ['Orders!A2:E', 'Customers!A2:C'],
});
data.valueRanges.forEach((r) => console.log(r.range, r.values?.length ?? 0));
Rows as objects. The API returns arrays; map them with the header row:
const [header, ...rows] = data.values ?? [];
const records = rows.map((row) => Object.fromEntries(header.map((h, i) => [h, row[i] ?? ''])));
Sheet names and metadata. api.spreadsheets.get({ spreadsheetId, fields: 'sheets.properties.title' }) lists tab names without downloading cell data.
Can Google Sheets run JavaScript?
Yes, inside the sheet, through Google Apps Script, which runs modern JavaScript (V8) on Google's servers. Open Extensions > Apps Script in a sheet:
function logOrders() {
const values = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('Orders')
.getRange('A2:E')
.getValues();
Logger.log(values.length);
}
Use Apps Script for automations and custom functions that live in the sheet; use the Sheets API when your own app or server needs the data.
When a spreadsheet stops being the right database
Sheets is fine for small, human-edited datasets. With frequent writes, many concurrent users or more than a few thousand rows, the 60-requests-per-user-per-minute quota and lack of queries start to hurt. A real database such as Supabase, or a spreadsheet-style UI on top of SQL like NocoDB or Baserow, scales better. If you only need some public data, our free APIs directory may already have it.
FAQ
How do I get a Google Sheets API key?
In the Google Cloud console, enable the Google Sheets API, then go to APIs & Services > Credentials > Create credentials > API key. Restrict it to the Sheets API and your domain. It only works for sheets shared publicly ("Anyone with the link"); private sheets need a service account or OAuth.
Can I use the Google Sheets API without a service account?
Yes. Use an API key for public sheets, or OAuth so a signed-in user grants access to their own sheets. A service account is only needed when your server reads sheets without a user present.
Why do I get "The caller does not have permission" (403)?
Usually the sheet isn't shared with the identity making the call. Share it with the service account's email, or make it public when using an API key. Also check that the Sheets API is enabled in the same project as your credentials.
Can I call the Google Sheets API from React or another front end?
Yes, with an API key (public sheets) or OAuth with Google Identity Services (user's sheets). Don't put a service account key in front-end code; call your own backend instead.
What's the difference between googleapis and @googleapis/sheets?
Both are official Google packages. googleapis contains clients for every Google API; @googleapis/sheets contains only the Sheets client, so it installs faster and is smaller. The API calls are the same.