PHPackages                             olamilekan/laravel-google-sheets - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [PDF &amp; Document Generation](/categories/documents)
4. /
5. olamilekan/laravel-google-sheets

ActiveLibrary[PDF &amp; Document Generation](/categories/documents)

olamilekan/laravel-google-sheets
================================

A Laravel package for working with Google Sheets API with support for multiple spreadsheet connections

v1.1.0(4w ago)57MITPHPPHP ^8.1CI passing

Since Mar 30Pushed 4w agoCompare

[ Source](https://github.com/oluwatosinolamilekan/laravel-google-sheets)[ Packagist](https://packagist.org/packages/olamilekan/laravel-google-sheets)[ RSS](/packages/olamilekan-laravel-google-sheets/feed)WikiDiscussions main Synced 3w ago

READMEChangelogDependencies (8)Versions (12)Used By (0)

Laravel Google Sheets
=====================

[](#laravel-google-sheets)

A fluent Laravel package for reading, writing, and managing Google Sheets with first-class support for multiple spreadsheet connections.

---

Requirements
------------

[](#requirements)

- PHP 8.1+
- Laravel 10, 11, 12, or 13
- A Google Cloud project with the Sheets API enabled
- A service account JSON credentials file

Installation
------------

[](#installation)

```
composer require olamilekan/laravel-google-sheets
```

Publish the configuration file:

```
php artisan vendor:publish --tag=google-sheets-config
```

Configuration
-------------

[](#configuration)

### 1. Credentials

[](#1-credentials)

Place your service account JSON file somewhere secure (e.g. `storage/app/google/service-account.json`) and set the path in your `.env`:

```
GOOGLE_SHEETS_CREDENTIALS_PATH=/path/to/service-account.json
```

### 2. Spreadsheet Connections

[](#2-spreadsheet-connections)

Define as many named connections as you need in `config/google-sheets.php`:

```
'sheets' => [

    'default' => [
        'spreadsheet_id' => env('GOOGLE_SHEETS_SPREADSHEET_ID'),
        'sheet' => 'Sheet1',
    ],

    'users' => [
        'spreadsheet_id' => env('GOOGLE_SHEETS_USERS_SPREADSHEET_ID'),
        'sheet' => 'Users',
    ],

    'reports' => [
        'spreadsheet_id' => env('GOOGLE_SHEETS_REPORTS_SPREADSHEET_ID'),
        'sheet' => 'Monthly',
    ],

],
```

Set the default connection:

```
GOOGLE_SHEETS_DEFAULT_CONNECTION=default
GOOGLE_SHEETS_SPREADSHEET_ID=your-spreadsheet-id-here
```

---

Usage
-----

[](#usage)

### Using the Facade

[](#using-the-facade)

```
use Olamilekan\GoogleSheets\Facades\GoogleSheets;
```

### Reading Data

[](#reading-data)

```
// All rows from the default connection (first row treated as headers)
$rows = GoogleSheets::all();

// Specific range
$rows = GoogleSheets::range('A1:D10')->get();

// First row only
$row = GoogleSheets::first();

// Get column headers
$headers = GoogleSheets::headers();

// Without header mapping (raw arrays)
$rows = GoogleSheets::connection('users')->withoutHeaders()->get();
```

### Querying Data

[](#querying-data)

```
// Find rows where a column matches a value
$admins = GoogleSheets::find('role', 'admin');

// Where clause with operators
$highScores = GoogleSheets::where('score', '>=', 90);

// Partial text matching
$results = GoogleSheets::where('name', 'like', 'john');
```

### Writing Data

[](#writing-data)

```
// Append rows
GoogleSheets::append([
    ['Alice', 'alice@example.com', 'admin'],
    ['Bob', 'bob@example.com', 'user'],
]);

// Update a specific range
GoogleSheets::range('A2:C2')->update([
    ['Alice Updated', 'alice-new@example.com', 'superadmin'],
]);

// Batch update multiple ranges at once
GoogleSheets::batchUpdate([
    'A2:C2' => [['Alice', 'alice@example.com', 'admin']],
    'A3:C3' => [['Bob', 'bob@example.com', 'user']],
]);

// Clear a range
GoogleSheets::range('A2:C100')->clear();
```

### Associative Rows, Upserts, And Validation

[](#associative-rows-upserts-and-validation)

```
// Map associative arrays to the sheet's header row before appending
GoogleSheets::connection('users')->appendAssoc([
    ['name' => 'Alice', 'email' => 'alice@example.com', 'role' => 'admin'],
]);

// Update existing rows by key column and append missing rows
GoogleSheets::connection('users')->upsert('email', [
    ['name' => 'Alice Updated', 'email' => 'alice@example.com', 'role' => 'owner'],
    ['name' => 'Charlie', 'email' => 'charlie@example.com', 'role' => 'user'],
]);

// Validate imported rows with Laravel validation rules
$validRows = GoogleSheets::connection('users')->validate([
    'name' => ['required', 'string'],
    'email' => ['required', 'email'],
]);

// Write row-level validation errors to an "Import Errors" tab before failing
$validRows = GoogleSheets::connection('users')->validateWithErrorSheet([
    'name' => ['required', 'string'],
    'email' => ['required', 'email'],
]);

// Ensure required sheet headers exist
GoogleSheets::connection('users')->requireHeaders(['name', 'email', 'role']);
```

### Import Diff Preview

[](#import-diff-preview)

Preview the impact of an import before writing anything. The preview separates new, changed, deleted, invalid, and conflict rows.

```
use App\Models\User;
use Olamilekan\GoogleSheets\Facades\GoogleSheets;

$preview = GoogleSheets::connection('users')
    ->diffAgainst(User::query(), key: 'email')
    ->rules([
        'name' => ['required', 'string'],
        'email' => ['required', 'email'],
    ])
    ->preview();

$preview->counts();
// ['new' => 1, 'changed' => 2, 'deleted' => 0, 'invalid' => 1, 'conflicts' => 0]

$preview->new;       // rows in the sheet that are not in the query
$preview->changed;   // rows where sheet values differ from existing query values
$preview->deleted;   // query rows missing from the sheet
$preview->invalid;   // rows failing validation or missing the key
$preview->conflicts; // duplicate key rows in the sheet or query
```

By default, changed rows compare sheet columns that also exist on the query/model row, excluding the key. You may narrow the comparison:

```
$preview = GoogleSheets::connection('users')
    ->diffAgainst(User::query(), key: 'email')
    ->only(['name', 'role'])
    ->except(['updated_at'])
    ->preview();
```

### Sync Methods

[](#sync-methods)

Sync methods return a `SyncReport` with created, updated, deleted, skipped, conflict, and failed counts.

```
use App\Models\User;
use Olamilekan\GoogleSheets\Facades\GoogleSheets;

// Database / Eloquent -> Google Sheet
$report = GoogleSheets::connection('users')
    ->syncFromModel(User::class, keyColumn: 'email', options: [
        'columns' => ['name', 'email', 'role'],
        'conflict' => 'app_wins',
    ]);

// Google Sheet -> Database / Eloquent
$report = GoogleSheets::connection('users')
    ->syncToModel(User::class, keyColumn: 'email');

// CSV -> Google Sheet
$report = GoogleSheets::connection('users')
    ->importCsv(storage_path('app/users.csv'), keyColumn: 'email');

// Google Sheet -> CSV
$report = GoogleSheets::connection('users')
    ->exportCsv(storage_path('app/users-export.csv'));

// API -> Google Sheet
$report = GoogleSheets::connection('orders')
    ->syncFromApi('https://api.example.com/orders', keyColumn: 'order_id', options: [
        'data_key' => 'data',
        'headers' => ['Authorization' => 'Bearer '.$token],
    ]);

// Google Sheet -> API
$report = GoogleSheets::connection('orders')
    ->syncToApi('https://api.example.com/orders/bulk');

// Conflict-aware two-way sync
$report = GoogleSheets::connection('users')
    ->syncTwoWay(User::class, keyColumn: 'email', options: [
        'conflict' => 'fail', // app_wins, sheet_wins, skip, fail
    ]);

$report->counts();
$report->created();
$report->updated();
$report->conflicts();
$report->errors();
```

Run syncs in the queue:

```
GoogleSheets::connection('users')
    ->queueSync('syncFromModel', [User::class, 'email'], queue: 'imports');
```

Every sync is audit logged through Laravel's logger and kept in the in-process audit log:

```
$records = GoogleSheets::connection('users')->syncAuditLog();
```

Notify teams when a sync finishes or fails:

```
GoogleSheets::connection('users')->syncFromModel(User::class, 'email', [
    'notify' => [
        'slack_webhook' => config('services.slack.sync_webhook'),
        'mail_to' => 'ops@example.com',
    ],
]);
```

### Import And Export Classes

[](#import-and-export-classes)

```
use App\Models\User;
use Olamilekan\GoogleSheets\Imports\SheetImport;

class UsersImport extends SheetImport
{
    public function target()
    {
        return User::query();
    }

    public function key(): string
    {
        return 'email';
    }
    public ?string $errorSheet = 'Import Errors';

    public function rules(): array
    {
        return ['email' => ['required', 'email']];
    }

    public function model(array $row): User
    {
        return User::updateOrCreate(
            ['email' => $row['email']],
            ['name' => $row['name']]
        );
    }
}

GoogleSheets::import(new UsersImport(), 'users');
```

Preview an import from the command line without writing rows:

```
php artisan google-sheets:sync "App\\Imports\\UsersImport" users --dry-run
```

Dry-run imports compare the sheet rows against the import class `target()` using the column returned by `key()`. Validation rules are applied when the import defines `rules()`.

```
use App\Models\Report;
use Olamilekan\GoogleSheets\Exports\SheetExport;

class ReportsExport extends SheetExport
{
    public bool $replace = true;

    public function headings(): array
    {
        return ['Date', 'Name', 'Total'];
    }

    public function collection()
    {
        return Report::query()
            ->latest()
            ->get()
            ->map(fn (Report $report) => [
                $report->created_at->toDateString(),
                $report->name,
                $report->total,
            ]);
    }
}

GoogleSheets::export(new ReportsExport(), 'reports');
```

### Multiple Connections

[](#multiple-connections)

```
// Switch between configured connections
$users  = GoogleSheets::connection('users')->all();
$reports = GoogleSheets::connection('reports')->all();

// Create an ad-hoc connection to any spreadsheet
$data = GoogleSheets::make('some-spreadsheet-id', 'TabName')->all();
```

### Switching Sheets (Tabs) at Runtime

[](#switching-sheets-tabs-at-runtime)

```
$sheet = GoogleSheets::connection('default');

$sheet1Data = $sheet->sheet('Sheet1')->all();
$sheet2Data = $sheet->sheet('Sheet2')->all();
```

### Sheet / Tab Management

[](#sheet--tab-management)

```
// List all sheet tabs in a spreadsheet
$tabs = GoogleSheets::listSheets();   // ['Sheet1', 'Users', 'Reports']

// Check if a tab exists
GoogleSheets::sheetExists('Users');   // true

// Create a new tab
GoogleSheets::createSheet('Archive');

// Duplicate an existing tab
GoogleSheets::duplicateSheet('Sheet1', 'Sheet1 Copy');

// Delete a tab
GoogleSheets::deleteSheet('Archive');
```

### Caching

[](#caching)

Enable caching in config or at runtime to reduce API calls:

```
// In config/google-sheets.php
'cache' => [
    'enabled' => true,
    'store'   => 'redis',
    'ttl'     => 300,  // seconds
    'prefix'  => 'google_sheets_',
],

// At runtime
$rows = GoogleSheets::enableCache(600)->all();
$rows = GoogleSheets::disableCache()->all();
```

Write operations now clear remembered read cache keys for the active spreadsheet, so cached ranges are refreshed after updates.

### Retry And Backoff

[](#retry-and-backoff)

Transient Google Sheets API failures are retried by default, including rate limits, quota throttling, and backend errors. The delay uses exponential backoff with jitter.

```
// In config/google-sheets.php
'retry' => [
    'enabled' => true,
    'attempts' => 3,
    'delay' => 250,      // milliseconds
    'max_delay' => 5000, // milliseconds
],

// At runtime
$rows = GoogleSheets::withRetries(attempts: 5, delay: 500)->all();
$rows = GoogleSheets::withoutRetries()->all();
```

### Chunked Processing

[](#chunked-processing)

```
GoogleSheets::chunk(100, function ($chunk) {
    foreach ($chunk as $row) {
        // process each row
    }
});

GoogleSheets::lazy(500)->each(function (array $row) {
    // process one row at a time
});
```

### Formatting, Formulas, And Named Ranges

[](#formatting-formulas-and-named-ranges)

```
GoogleSheets::connection('reports')
    ->sheet('Monthly')
    ->boldHeader()
    ->freezeRows(1)
    ->autoResizeColumns(1, 4);

GoogleSheets::connection('reports')->append([
    ['Total', GoogleSheets::formula('SUM(C2:C100)')],
]);

$summaryRows = GoogleSheets::connection('reports')
    ->namedRange('MonthlySummary')
    ->get();
```

### Testing

[](#testing)

```
use Olamilekan\GoogleSheets\Facades\GoogleSheets;

$fake = GoogleSheets::fake([
    'users' => [
        ['name' => 'Alice', 'email' => 'alice@example.com'],
    ],
]);

GoogleSheets::connection('users')->appendAssoc([
    ['name' => 'Bob', 'email' => 'bob@example.com'],
]);

$fake->assertAppended('users', ['name' => 'Bob', 'email' => 'bob@example.com']);
```

### Artisan Commands

[](#artisan-commands)

```
php artisan google-sheets:list users
php artisan google-sheets:clear reports --sheet=Monthly --range=A2:D100
php artisan google-sheets:sync "App\\Imports\\UsersImport" users
php artisan google-sheets:sync "App\\Imports\\UsersImport" users --dry-run
php artisan google-sheets:sync "App\\Exports\\ReportsExport" reports
```

### Spreadsheet Metadata

[](#spreadsheet-metadata)

```
$title = GoogleSheets::getTitle();
$id    = GoogleSheets::getSpreadsheetId();
```

### Dependency Injection

[](#dependency-injection)

```
use Olamilekan\GoogleSheets\GoogleSheetsManager;

class UserImportService
{
    public function __construct(
        protected GoogleSheetsManager $sheets
    ) {}

    public function import(): void
    {
        $rows = $this->sheets->connection('users')->all();

        foreach ($rows as $row) {
            User::updateOrCreate(
                ['email' => $row['email']],
                ['name' => $row['name']]
            );
        }
    }
}
```

---

API Reference
-------------

[](#api-reference)

### `GoogleSheetsManager`

[](#googlesheetsmanager)

MethodDescription`connection(?string $name)`Get a named connection (lazy-loaded &amp; cached)`make(string $spreadsheetId, string $sheet)`Create an ad-hoc sheet instance`getDefaultConnection()`Get the default connection name`purge(?string $name)`Remove a resolved connection`reconnect(?string $name)`Purge and re-resolve a connection### `Sheet`

[](#sheet)

MethodReturnsDescription`spreadsheet(string $id)``static`Override the spreadsheet ID`sheet(string $name)``static`Switch to a different tab`range(string $range)``static`Set A1 range for the next operation`get()``Collection`Read rows (headers mapped)`all()``Collection`Read all rows from the sheet`first()``?array`First data row`last()``?array`Last data row`headers()``array`Column headers (row 1)`find(col, val)``Collection`Filter rows by column value`where(col, op, val)``Collection`Filter with comparison operators`chunk(size, cb)``void`Process rows in chunks`append(array $rows)``int`Append rows (returns row count)`update(array $rows)``int`Update range (returns row count)`batchUpdate(array $data)``int`Update multiple ranges`clear()``bool`Clear values in range`appendAssoc(array)``int`Append associative rows mapped to sheet headers`updateAssoc(array)``int`Update associative rows mapped to sheet headers`upsert(key, rows)``int`Update rows by key column and append missing rows`syncRows(rows, key, options)``SyncReport`Sync array/collection rows into the sheet`syncFromModel(model, key, options)``SyncReport`Sync Eloquent model records into the sheet`syncToModel(model, key, options)``SyncReport`Sync sheet rows into an Eloquent model`importCsv(path, key, options)``SyncReport`Sync a CSV file into the sheet`exportCsv(path, options)``SyncReport`Export sheet rows to a CSV file`syncFromApi(url, key, options)``SyncReport`Pull JSON rows from an API into the sheet`syncToApi(url, options)``SyncReport`Push sheet rows to an API endpoint`syncTwoWay(target, key, options)``SyncReport`Run conflict-aware two-way sync`queueSync(method, args, queue)``PendingDispatch`Dispatch a sync method to Laravel's queue`syncAuditLog()``Collection`Read in-process sync audit records`validate(rules)``Collection`Validate rows with Laravel validation rules`requireHeaders(array)``static`Ensure required headers exist`lazy(size)``LazyCollection`Iterate rows lazily from a collection-backed read`createSheet(string)``static`Add a new tab`deleteSheet(string)``bool`Remove a tab`duplicateSheet(src, new)``static`Copy a tab`listSheets()``array`List all tab names`sheetExists(string)``bool`Check if a tab exists`namedRange(string)``static`Set a named range for the next operation`listNamedRanges()``array`List named ranges`formula(string)``string`Create a formula cell value`boldHeader()``static`Bold the first row`freezeRows(int)``static`Freeze leading rows`autoResizeColumns(start, end)``static`Auto-resize columns`formatRange(range, format)``static`Apply cell formatting`withHeaders()``static`Map first row as keys (default)`withoutHeaders()``static`Return raw arrays`enableCache(?int $ttl)``static`Enable caching`disableCache()``static`Disable caching`withRetries(?attempts, ?delay)``static`Enable retries and optionally override attempts/delay`withoutRetries()``static`Disable retries for the current sheet instance---

Environment Variables
---------------------

[](#environment-variables)

VariableDefaultDescription`GOOGLE_SHEETS_CREDENTIALS_PATH``storage/app/google/service-account.json`Path to credentials`GOOGLE_SHEETS_DEFAULT_CONNECTION``default`Default connection name`GOOGLE_SHEETS_SPREADSHEET_ID`—Spreadsheet ID for default connection`GOOGLE_SHEETS_APPLICATION_NAME``Laravel Google Sheets`App name for API requests`GOOGLE_SHEETS_CACHE_ENABLED``false`Enable response caching`GOOGLE_SHEETS_CACHE_STORE``null` (default driver)Cache store to use`GOOGLE_SHEETS_CACHE_TTL``300`Cache lifetime in seconds`GOOGLE_SHEETS_RETRY_ENABLED``true`Retry transient Google API failures`GOOGLE_SHEETS_RETRY_ATTEMPTS``3`Maximum attempts per API call`GOOGLE_SHEETS_RETRY_DELAY``250`Initial retry delay in milliseconds`GOOGLE_SHEETS_RETRY_MAX_DELAY``5000`Maximum retry delay in milliseconds`GOOGLE_SHEETS_VALUE_RENDER``FORMATTED_VALUE`Value render option`GOOGLE_SHEETS_VALUE_INPUT``USER_ENTERED`Value input optionLicense
-------

[](#license)

MIT

###  Health Score

42

—

FairBetter than 89% of packages

Maintenance94

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~61 days

Total

2

Last Release

29d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/b15f9827b42076aa512806780f5eb0331cc2fd553eb150eb5fe2c5dc4864937b?d=identicon)[oluwatosinolamilekan](/maintainers/oluwatosinolamilekan)

---

Top Contributors

[![oluwatosinolamilekan](https://avatars.githubusercontent.com/u/29930457?v=4)](https://github.com/oluwatosinolamilekan "oluwatosinolamilekan (18 commits)")

---

Tags

laravelspreadsheetgoogle apigoogle-sheets

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/olamilekan-laravel-google-sheets/health.svg)

```
[![Health](https://phpackages.com/badges/olamilekan-laravel-google-sheets/health.svg)](https://phpackages.com/packages/olamilekan-laravel-google-sheets)
```

###  Alternatives

[maatwebsite/excel

Supercharged Excel exports and imports in Laravel

12.7k152.8M848](/packages/maatwebsite-excel)[avadim/fast-excel-laravel

Lightweight and very fast XLSX Excel Spreadsheet Export/Import for Laravel

4054.7k1](/packages/avadim-fast-excel-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
