Query BigQuery from TypeScript on Node, with the errors handled as documented

A small, correct client: install the official library, authenticate from the ambient identity with no key, run a parameterised query, and handle exactly the error reasons the vendor lists as retryable — with the three that must never be retried called out.

markdownguidev1DCMH-3D1Kstage: buildtrust: officialfamily: dataservice: bigqueryinterface: sdk@google-cloud/bigqueryparameterised queryambient credentialsApplication Default Credentialsretryable errorsexponential backoffroles/bigquery.jobUserroles/bigquery.dataViewer

Created 21 Oct 2026 · Modified 23 Oct 2026 · 1 version

Query BigQuery from TypeScript

Step 1 — the official client

npm i @google-cloud/bigquery

Check the installed package's own type definitions for every option you use. Google Cloud client libraries rename and move options between major versions, and the .d.ts in node_modules is the only authority for the version you actually have. This hub does not reproduce the API surface for that reason.

Step 2 — no key, ever

Construct the client with no credentials argument. It picks up the ambient identity: the attached service account on Cloud Run, Compute Engine, GKE with Workload Identity Federation, or your impersonated credentials locally.

Service account keys are "a security risk if not managed correctly", and the attached-identity path exists so that none is needed (reference/identity/predefined-roles-and-permissions.md §5).

Step 3 — parameterise the query

Pass values as query parameters, not as interpolated strings. Two reasons, and the second is the one people forget:

  1. string interpolation into SQL is an injection vector;
  2. a parameterised query is the same query text every time, which matters for caching and for reading your

own slot usage.

Step 4 — handle exactly the errors the vendor says to retry

Retry, with exponential backoffNever retry
backendErrorinvalidQuery — the SQL is wrong
internalErrorbillingTierLimitExceeded — the on-demand model refused a query of that size. Retrying changes nothing; reduce the scan, or move to capacity pricing
jobBackendErroraccessDenied — a permission, not a transient
jobInternalErrorresourcesExceeded — the query has a stage that will not distribute
rateLimitExceeded

A client that retries the right-hand column is the most common wasteful pattern in a BigQuery integration: it turns a clear failure into a slow one, and on an on-demand project it can turn a refusal into a bill.

Step 5 — the two grants, which are separate

ToRole
run a query (create a job)a job-running role such as roles/bigquery.jobUser
read the data it touchesa data-reading role such as roles/bigquery.dataViewer, on the dataset

Job permission and data permission are different grants on different objects. An identity with only the first gets accessDenied on the data; with only the second it cannot start the job. Verify the exact role names against the current roles reference before writing them into IaC (reference/identity/predefined-roles-and-permissions.md).

Step 6 — verify

CheckExpected
run the query from the deployed workloadrows, with no key in the deployment
run it with a deliberately wrong column nameinvalidQuery — and your client does not retry
remove the dataset-level read roleaccessDenied — proving which grant carries the data permission

For a migration

This is also the smallest honest benchmark for a warehouse migration: the same query, parameterised, run against the source and against BigQuery, with bytes scanned recorded. That number — not a node count — is the input a BigQuery cost estimate needs (guides/parity/data.md §2).

Read this document as markdown — the same bytes an agent receives.