All posts
Blog

Read Google Sheets from PHP with a REST API

Use file_get_contents or Guzzle to fetch JSON from any Google Sheet — no OAuth, no SDK, no service account file required.

4 min read

The OAuth problem with Google Sheets in PHP

The official Google Sheets PHP client is powerful, but getting it working requires a service account JSON file, enabling the Sheets API in Google Cloud Console, sharing the sheet with the service account email, and managing token refresh. For a read-only use case — displaying product data, a team roster, or a price list — this is too much ceremony.

GKit SheetsAPI strips all of that away. You connect your sheet once in the dashboard, and from that point on it's a plain REST endpoint secured with a Bearer token. Any HTTP client in any language — including vanilla PHP — can read it in five lines of code.

The simplest case: file_get_contents

PHP's built-in file_get_contents can make HTTP requests when given a stream context. For a quick script or a small WordPress plugin, this is enough:

<?php
 
$userKey   = 'YOUR_USER_KEY';
$sheetName = 'YOUR_SHEET_NAME';
$apiKey    = 'sk_your_key_here';
 
$url     = "https://sheetsapi.gkit.mreshank.com/api/spreadsheets/{$userKey}/{$sheetName}";
$context = stream_context_create([
    'http' => [
        'header' => "Authorization: Bearer {$apiKey}\r\n",
        'method' => 'GET',
    ],
]);
 
$body   = file_get_contents($url, false, $context);
$result = json_decode($body, true);
 
// $result['data']  — array of row objects
// $result['meta']  — ['total' => N, 'limit' => N, 'offset' => N]
 
foreach ($result['data'] as $row) {
    echo $row['name'] . ' — ' . $row['plan'] . PHP_EOL;
}

To add query parameters, append them to the URL before the request:

$params = http_build_query([
    'search' => 'plan:pro',
    'sort'   => '-mrr',
    'limit'  => 10,
]);
$url = "https://sheetsapi.gkit.mreshank.com/api/spreadsheets/{$userKey}/{$sheetName}?{$params}";

search=plan:pro returns only rows where the plan column equals pro. Prefix the sort field with - for descending order. Add search_exact=1 if you need an exact match instead of a partial (contains) search.

Guzzle for production code

file_get_contents is fine for scripts, but in a real application you want timeout control, proper error handling, and a testable HTTP client. Guzzle is the standard choice:

<?php
 
use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
 
$client = new Client([
    'base_uri' => 'https://sheetsapi.gkit.mreshank.com',
    'timeout'  => 5.0,
]);
 
$response = $client->get('/api/spreadsheets/YOUR_USER_KEY/products', [
    RequestOptions::HEADERS => [
        'Authorization' => 'Bearer sk_your_key_here',
    ],
    RequestOptions::QUERY => [
        'sort'   => '-price',
        'limit'  => 25,
        'fields' => 'name,price,sku',   // return only these columns
    ],
]);
 
$body   = json_decode($response->getBody()->getContents(), true);
$rows   = $body['data'];
$total  = $body['meta']['total'];
 
echo "Fetched " . count($rows) . " of {$total} products\n";

fields=name,price,sku is useful when your sheet has many columns but you only need a few — it reduces payload size and speeds up the response.

WordPress: wp_remote_get

WordPress ships with its own HTTP API that respects proxy settings, handles SSL, and integrates with the WordPress transient cache. Use it instead of Guzzle inside plugins or themes:

<?php
 
function gkit_get_sheet_rows(string $sheet, array $params = []): array {
    $apiKey  = get_option('gkit_api_key');          // store the key in wp_options
    $userKey = get_option('gkit_user_key');
 
    $url = add_query_arg(
        $params,
        "https://sheetsapi.gkit.mreshank.com/api/spreadsheets/{$userKey}/{$sheet}"
    );
 
    $response = wp_remote_get($url, [
        'headers' => ['Authorization' => "Bearer {$apiKey}"],
        'timeout' => 10,
    ]);
 
    if (is_wp_error($response)) {
        return [];
    }
 
    $body = json_decode(wp_remote_retrieve_body($response), true);
    return $body['data'] ?? [];
}

Cache the result with transients to avoid hitting the API on every page load:

function gkit_get_sheet_rows_cached(string $sheet, int $ttl = 300): array {
    $cacheKey = 'gkit_sheet_' . $sheet;
    $cached   = get_transient($cacheKey);
 
    if ($cached !== false) {
        return $cached;
    }
 
    $rows = gkit_get_sheet_rows($sheet, ['limit' => 100]);
    set_transient($cacheKey, $rows, $ttl);   // cache for 5 minutes by default
 
    return $rows;
}

Laravel: the Http facade

Laravel's Http facade wraps Guzzle with a fluent interface. withToken() handles the Bearer header, and the json() method decodes the response body automatically:

<?php
 
use Illuminate\Support\Facades\Http;
 
$response = Http::withToken(config('services.sheetsapi.key'))
    ->get('https://sheetsapi.gkit.mreshank.com/api/spreadsheets/' .
          config('services.sheetsapi.user_key') . '/inventory', [
        'search' => 'status:in_stock',
        'sort'   => 'name',
        'limit'  => 50,
    ]);
 
$rows  = $response->json('data');    // directly access the data key
$total = $response->json('meta.total');

Add your keys to config/services.php:

'sheetsapi' => [
    'key'      => env('SHEETSAPI_KEY'),
    'user_key' => env('SHEETSAPI_USER_KEY'),
],

Caching in Laravel

For read-heavy pages, wrap the call with cache()->remember() so the sheet is fetched at most once per cache TTL:

$rows = cache()->remember('inventory.in_stock', now()->addMinutes(5), function () {
    return Http::withToken(config('services.sheetsapi.key'))
        ->get('https://sheetsapi.gkit.mreshank.com/api/spreadsheets/' .
              config('services.sheetsapi.user_key') . '/inventory', [
            'search' => 'status:in_stock',
            'limit'  => 100,
        ])
        ->json('data');
});

The cache key should include any query parameters that vary per request. For example, if the search term comes from user input, include it in the key: "inventory.search.{$term}".

APCu for non-framework PHP

Outside of Laravel or WordPress, APCu is a simple in-process cache that works with any PHP setup:

function fetch_sheet(string $sheet, array $params = []): array {
    $cacheKey = 'sheet_' . $sheet . '_' . md5(serialize($params));
 
    if (apcu_exists($cacheKey)) {
        return apcu_fetch($cacheKey);
    }
 
    // ... Guzzle or file_get_contents call ...
    $rows = [];  // result from API
 
    apcu_store($cacheKey, $rows, 300);   // TTL in seconds
    return $rows;
}

Putting it together

The pattern is the same regardless of which HTTP client you use: send the Authorization: Bearer header, append query parameters to the URL, and read $result['data'] for the rows and $result['meta']['total'] for the total count. Everything else — search, sort, pagination, field selection — is a query parameter away.

No OAuth dance. No SDK to install. No service account JSON to rotate. A sheet you update in Google Docs is live in your PHP app within seconds.

Next steps

Share