Accessing Google Sheets from Go

Reading data from a Google Sheet in a Go program requires a GCP project and, typically, the gcloud CLI. First, enable the Sheets API:

$ gcloud services enable sheets.googleapis.com --project=<PROJECT-NAME>

To check which APIs are already active for your project:

$ gcloud services list --enabled --project=<PROJECT-NAME>

Using a service account

The simplest working approach is a service account: a virtual account with its own email address and permissions, attached to a GCP project. This is ideal when running on a VM, as it avoids using your primary Google account and can be scoped tightly.

Create a service account in the GCP console, then use Manage Keys in its Actions menu to add a new key. This downloads a private key file, which you must store securely. The program below expects the key via the -keyfile flag:

package main

import (
  "context"
  "flag"
  "fmt"
  "io/ioutil"
  "log"

  "golang.org/x/oauth2/google"
  "google.golang.org/api/option"
  "google.golang.org/api/sheets/v4"
)

func main() {
  keyFilePath := flag.String("keyfile", "", "path to the credentials file")
  flag.Parse()

  ctx := context.Background()
  credentials, err := ioutil.ReadFile(*keyFilePath)
  if err != nil {
    log.Fatal("unable to read key file:", err)
  }

  scopes := []string{
    "https://www.googleapis.com/auth/spreadsheets.readonly",
  }
  config, err := google.JWTConfigFromJSON(credentials, scopes...)
  if err != nil {
    log.Fatal("unable to create JWT configuration:", err)
  }

  srv, err := sheets.NewService(ctx, option.WithHTTPClient(config.Client(ctx)))
  if err != nil {
    log.Fatalf("unable to retrieve sheets service: %v", err)
  }

  // ...

Requested scopes are declared when building the auth config; here, only read-only access to Sheets is granted. After a successful sheets.NewService call, the sheets package reads the document — the sample prints the title and all values in columns A and B of Sheet1:

  docId := "1qsNWsZuw98r9HEl01vwxCO5O1sIsI-fr0bJ4KGVvWsU"
  doc, err := srv.Spreadsheets.Get(docId).Do()
  if err != nil {
    log.Fatalf("unable to retrieve data from document: %v", err)
  }
  fmt.Printf("The title of the doc is: %s\n", doc.Properties.Title)

  val, err := srv.Spreadsheets.Values.Get(docId, "Sheet1!A:B").Do()
  if err != nil {
    log.Fatalf("unable to retrieve range from document: %v", err)
  }

  fmt.Printf("Selected major dimension=%v, range=%v\n", val.MajorDimension, val.Range)
  for _, row := range val.Values {
    fmt.Println(row)
  }
}

The docId is the path segment in the spreadsheet URL right after /d/. Note that the service account must be given access to the sheet explicitly: unless the sheet is world-readable, you’ll need to add the account’s email (found in the IAM page’s Details tab) as an editor or viewer on the document.

OAuth flow

Alternatively, OAuth requires more setup in the GCP console, following the official Go quickstart. This sample assumes a saved credentials.json file, passed with -credfile. Unlike the quickstart, it automates the token exchange — no need to manually copy a code from a browser — although a one-time interactive login is still required. The sheet processing code is identical to the service account version.

Note on ADC

Application Default Credentials (ADC) also work, though I initially had trouble getting it to succeed. The configuration was likely at fault rather than the code, as the logic is the same. ADC may be simpler in some cases, but service accounts are arguably more dependable across machines because their configuration is explicit and avoids hidden default behaviors.