TechEarl

Reading a Google Sheet in PHP: CSV vs the API

Two ways to read a Google Sheet from PHP: publish-to-web CSV (trivial, but public to anyone with the URL) or the Sheets API with a service account (private, scoped). The security trade-off, shown.

Ishan Karunaratne⏱️ 6 min readUpdated
Share thisCopied
Reading a Google Sheet from PHP two ways: a public publish-to-web CSV versus the authenticated Sheets API with a service account

There are two ways to read a Google Sheet from PHP, and they sit at opposite ends of a security trade-off. You can publish the sheet to the web and fetch it as CSV, which is trivial to set up but makes the data readable by anyone who has (or guesses, or is shown) the URL. Or you can read it through the Sheets API with a service account, which is a little more setup but keeps the sheet private, scoped, and revocable. The code difference is small; the exposure difference is the whole decision.

The CSV way: publish to web

In the sheet, File → Share → Publish to web → CSV. Google hands you a URL that returns the tab as CSV, and PHP reads it in a few lines:

php
$url  = 'https://docs.google.com/spreadsheets/d/e/<published-id>/pub?gid=0&single=true&output=csv';
$rows = array_map( 'str_getcsv', file( $url, FILE_IGNORE_NEW_LINES ) );
$head = array_shift( $rows );
foreach ( $rows as $row ) {
	$data = array_combine( $head, $row );
	// $data['post_id'], $data['new_currency'], ...
}

No credentials, no tokens, no libraries. That is the appeal. The cost is in the second word of "publish to web": the sheet is now public. Anyone with that URL reads the data, the URL can be shared or leaked, and a published sheet is the kind of thing that turns up in logs, referrers, and the occasional search index. It is the right tool for data that is genuinely public anyway (a price list you'd put on the site regardless). It is the wrong tool for anything you would not paste into a public Gist.

The API way: a service account

The Sheets API reads the same data, but the sheet stays private. You share it with a service account (read-only), and your server authenticates as that account. The full token-minting and reading code is in bulk-updating WordPress fields from a Google Sheet; the short version is a signed JWT exchanged for an access token, then a GET with a Bearer header.

The security difference is not theoretical, and you can see it. The same private sheet, hit without a token, is refused; hit with the service account's token, it returns the rows:

Two curl requests to the Sheets API for the same private sheet: the first with no Authorization header returns HTTP 403 PERMISSION_DENIED, the second with a service-account Bearer token returns HTTP 200 and the sheet rows
The same private sheet over the API: 403 with no token, 200 with the service account's token. A published CSV would return 200 to anyone.

That 403 is the point. With the API, the sheet is closed by default and opens only for an identity you control and can revoke. With a published CSV, the equivalent request from anyone returns 200. Same data, opposite default.

Which to use

Publish-to-web CSVSheets API + service account
Setupone menu clickservice account, key, share
Credentialsnonea JSON key on the server
Who can read the dataanyone with the URLonly the authenticated account
Revoke accessunpublish (and hope no copies)delete the key or unshare
Right forgenuinely public dataanything private

Reach for CSV only when the data is public anyway and you want the least possible machinery. For anything that lives behind a login, anything with personal data, or anything a competitor would enjoy reading, the service account is not optional, it is the baseline. The few minutes of setup buys you a private, auditable, revocable connection instead of a public URL you can never fully un-share.

Once you can read the sheet, applying it to WordPress is the pull/batch pattern; wrap the write in the safe batch updater's dry-run and change-only rails so a bad row is recoverable. If you would rather have the sheet push to WordPress instead of WordPress pulling from it, the REST-endpoint approach to updating WordPress from a Google Sheet flips the direction with an Apps Script trigger calling in.

Yes. A published sheet is readable by anyone with the URL, no Google account or permission needed. The URL is long and unguessable, but it is not secret, it travels in code, logs, referrer headers, and browser history, and once it is out you cannot fully recall it. Treat a published sheet as public data.

Not as a plain anonymous CSV fetch. The export endpoints that return CSV require the sheet to be either published or shared so that the requester is authorised. For a private sheet, that means authenticating, which is exactly what the Sheets API with a service account does.

The sheet is private and shared only with specific accounts, so an unauthenticated request is recognised as not permitted to see that resource (403 Permission Denied) rather than simply missing credentials. Either way the data is not returned, which is the behaviour you want for a private sheet.

It can. CSV is positional and header-named, so inserting or reordering columns shifts what str_getcsv hands back. Combining rows with the header (array_combine) helps, but the API is more robust because you request named ranges and get structured values rather than parsing text.

Sources

Authoritative references this article was fact-checked against.

TagsWordPressGoogle SheetsPHPSecurityService Account

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Software Systems Architect · Senior Software Engineer · Engineering Leadership

Software systems architect and senior software engineer with more than two decades designing, building, and running production software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Now a CTO, though what I write here is drawn from the full arc of that work, across architecture, engineering, and operations, not any single job.

Keep reading

Related posts