How to Connect to BigQuery from PHP

4 min readBigQuery

BigQuery is a query API, not a database you open a socket to. There is no host, no port, and no PDO driver. PHP talks to it through the google/cloud-bigquery client, which authenticates with Application Default Credentials and submits jobs over HTTPS. If you have connected PHP to PostgreSQL or MySQL, almost none of that knowledge carries over. This guide covers install, auth, queries with parameters, the cost controls that keep a runaway query from costing real money, the location mistake that looks like a permissions error, and the failures you will actually see.

Install

composer require google/cloud-bigquery

google/cloud-bigquery 1.38 is current as of June 2026 and runs on PHP 8.1+. It pulls in the gRPC or REST transport plus the gax (Google API extensions) layer. For heavy result sets, install the grpc and protobuf PECL extensions; the pure-REST path works without them but is slower on large reads.

Authentication

There is no username and password. BigQuery uses Application Default Credentials (ADC), resolved in a ladder:

  1. The GOOGLE_APPLICATION_CREDENTIALS env var pointing at a service-account JSON key file.
  2. Credentials from gcloud auth application-default login (local dev).
  3. The attached service account when running on GCP (Cloud Run, GCE, App Engine).

Local development:

export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
<?php
require 'vendor/autoload.php';
 
use Google\Cloud\BigQuery\BigQueryClient;
 
$bigQuery = new BigQueryClient([
    'projectId' => 'my-billing-project',
]);

The projectId here is the billing project, the one charged for the query. The data you read can live in another project entirely; you reference it as project.dataset.table in SQL. Conflating the two is a common source of "where did this bill come from" confusion.

Run a query

$query = 'SELECT name, total FROM `bigquery-public-data.usa_names.usa_1910_2013` LIMIT 10';
 
$queryJobConfig = $bigQuery->query($query);
$results = $bigQuery->runQuery($queryJobConfig);
 
foreach ($results as $row) {
    printf("%s: %d\n", $row['name'], $row['total']);
}

runQuery blocks until the job finishes and pages results automatically as you iterate. For long jobs, startQuery returns immediately and you poll $job->reload() / $job->isComplete() yourself.

Parameters

Never build SQL with string concatenation. Use named parameters:

$query = 'SELECT name, total FROM `bigquery-public-data.usa_names.usa_1910_2013`
          WHERE state = @state AND gender = @gender
          LIMIT 100';
 
$jobConfig = $bigQuery->query($query)
    ->parameters([
        'state'  => 'CA',
        'gender' => 'F',
    ]);
 
$results = $bigQuery->runQuery($jobConfig);

For NULLs or types the client cannot infer, pass types() alongside parameters() so BigQuery binds the right column type instead of guessing.

The location trap

A BigQuery dataset lives in a region (US, EU, asia-northeast1, ...). A query has to run in the same location as the data it touches. If you query an EU dataset without telling the client, you get "Not found: Dataset ... was not found in location US", which reads like a permissions or typo problem but is purely geographic:

$jobConfig = $bigQuery->query($query)->location('EU');

Set the location whenever your data is outside the multi-region US default. This is the single most common BigQuery-from-PHP mistake.

Cost controls

BigQuery bills by bytes scanned, so a careless SELECT * on a large table is a real invoice. Two guards:

// Dry run: returns the byte estimate, scans nothing, costs nothing.
$jobConfig = $bigQuery->query($query)->dryRun(true);
$job = $bigQuery->startQuery($jobConfig);
$bytes = $job->info()['statistics']['totalBytesProcessed'];
 
// Hard cap: kill the query if it would exceed the limit.
$jobConfig = $bigQuery->query($query)->maximumBytesBilled(1_000_000_000);

Dry-run before any query against unfamiliar tables, and set maximumBytesBilled in production so one bad query cannot run away.

Errors you will actually hit

SymptomCause
Could not load the default credentialsNo GOOGLE_APPLICATION_CREDENTIALS, no gcloud login, not on GCP. ADC found nothing.
Not found: Dataset ... in location USData is in EU/asia; set ->location().
Access Denied: ... bigquery.jobs.createService account lacks roles/bigquery.jobUser on the billing project.
Access Denied: ... on tableLacks roles/bigquery.dataViewer on the dataset. Job and data roles are separate.
Quota exceeded: bytes billedmaximumBytesBilled cap hit. The guard worked.

Two roles, two projects: jobUser on billing, dataViewer on data. Granting one and not the other is the usual permission snag.

Where Mako fits

Mako connects to BigQuery and gives you AI-assisted autocomplete for exploring tables and writing SQL, with the byte-scan estimate shown before you run, so you can validate a query interactively before wiring it into PHP. It is read and query only: no dataset administration. Try it free at mako.ai.

Mako — open source

Skip the terminal. Use Mako.

Connect your database, write queries with AI assistance, and import/export data in clicks. Free to start.