Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Google Api Laravel Package

tomshaw/google-api

A Google OAuth 2.0 service client for Laravel that handles authentication, configurable token storage (including database), and scoped access to Google APIs such as Gmail and Calendar. Ships with a publishable config file and a customizable token storage adapter.

View on GitHub
Deep Wiki
Context7

Getting Started

Check the version constraints before you install anything. The package targets php ^8.5 and laravel ^13.0. If your app is on Laravel 11/12 or PHP 8.3, Composer will refuse the install or you'll end up resolving an older tagged release that doesn't match the README you're reading. Confirm what your lockfile can actually take:

composer show tomshaw/google-api --all | head -20

Trim the Google service surface immediately. The google/apiclient-services dependency is ~200 APIs by default (hundreds of MB in vendor/). Add this to your app's composer.json before the first install and list only what you use:

{
    "scripts": {
        "pre-autoload-dump": "Google\\Task\\Composer::cleanup"
    },
    "extra": {
        "google/apiclient-services": ["Gmail", "Calendar", "Drive"]
    }
}

Install and scaffold:

composer require tomshaw/google-api
php artisan vendor:publish --provider="TomShaw\GoogleApi\Providers\GoogleApiServiceProvider" --tag=config
php artisan migrate   # only if using DatabaseStorageAdapter (the default)

Wire up Google Cloud first, code second. Create an OAuth 2.0 Client ID (Web application) in the Google Cloud Console, add your redirect_uri, enable the specific APIs you need (Calendar API, Gmail API, …), and download the credentials JSON. Point auth_config in config/google-api.php at that file, or move the secrets into env-backed config — don't commit the JSON.

Your first use case — send one email end to end. Two routes, one controller, and you've validated the whole stack (credentials, scopes, token persistence):

Route::get('/google/redirect', [GoogleAuthController::class, 'index'])->name('google.redirect');
Route::get('/google/callback', [GoogleAuthController::class, 'callback'])->name('google.callback');

Once GoogleApi::gmail()->...->send() returns without throwing, the hard part is done and everything else is just another service adapter.

Implementation Patterns

The standard OAuth round trip. Keep the redirect/callback pair tiny and let the package do the work. createAuthUrl() builds the consent URL from service_scopes, prompt, access_type, etc.; the callback exchanges the code and persists via the configured storage adapter.

public function index(GoogleClient $client)
{
    return redirect()->away($client->createAuthUrl());
}

public function callback(Request $request, GoogleClient $client)
{
    $client->setAccessToken(
        $client->fetchAccessTokenWithAuthCode($request->get('code'))
    );

    return redirect()->route('homepage');
}

Treat TokenNotFoundException as your authorization gate, not as an error. This is the single most useful pattern in the package — the exception carries the auth URL, so any entry point can bounce an unauthorized user into consent without you pre-checking token state:

try {
    $events = GoogleApi::calendar()->listEvents();
} catch (TokenNotFoundException $e) {
    return redirect()->away($e->authUrl);
}

For JSON/API routes, catch it centrally in bootstrap/app.php and return 401 + the URL instead of a redirect:

$exceptions->render(fn (TokenNotFoundException $e, $request) =>
    $request->expectsJson()
        ? response()->json(['auth_url' => $e->authUrl], 401)
        : redirect()->away($e->authUrl)
);

Use forUser() for anything not running inside a request. Because the default DatabaseStorageAdapter is user-scoped, queued jobs and artisan commands have no authenticated user to key off. This is the correct pattern for scheduled digests, invoice sends, and report exports:

GoogleApi::forUser($user)
    ->gmail()
    ->from('[email protected]', 'Billing')
    ->to($user->email, $user->name)
    ->subject('Your invoice')
    ->message('<p>Thanks!</p>')
    ->send();

Wrap long-running jobs so a revoked/expired token fails loudly rather than silently half-completing.

Reach for service() when an adapter doesn't exist. Only Calendar, Gmail, Drive and Books are bundled. Everything else is one line — you get the raw Google\Service\* object with an authorized HTTP client already behind it:

use Google\Service\Sheets;
$sheets = GoogleApi::service(Sheets::class);

Keep these calls behind your own small wrapper classes so the package is only referenced in one place per service — it makes upgrading past 0.x far less painful.

Inspect tokens rather than guessing at expiry. getAccessToken() returns a readonly AccessToken value object:

$token = $client->getAccessToken();
$token?->isExpired();
$token?->hasRefreshToken();

Use hasRefreshToken() as a health check — if it's false for a stored user token, that user will break the moment their access token lapses, and the only fix is re-consent.

Swap storage deliberately, not incidentally. SessionStorageAdapter is only appropriate for a single-user, web-only tool. DatabaseStorageAdapter is the right default for multi-user apps (tokens encrypted at rest in google_tokens). For multi-tenant scale or existing infrastructure, implement StorageAdapterInterface — plus UserScopedStorageAdapter if you want forUser() to work — and point token_storage_adapter at your class. Custom adapters are the intended extension point: Redis storage with TTL is a ~40-line class.

Testing. Don't hit Google in tests. Bind an in-memory StorageAdapterInterface implementation in the container and mock GoogleClient/the service adapter classes with $this->mock(...). Assert on the fluent builder's terminal call (send(), createEvent()) and on the stored token, not on Google's response shape.

Gotchas and Tips

You are on a pre-1.0 package with 0 dependents and a single maintainer. The changelog shows real behavioral changes across minor versions (0.14 alone reworked Gmail attachment/MIME handling and added strict types and user-scoped storage). Pin an exact version ("tomshaw/google-api": "0.14.1") and review the changelog before every bump. Treat adopting it as owning it.

The google/apiclient-services cleanup bites in two directions. If you don't add the pre-autoload-dump script, your vendor directory balloons and CI slows down noticeably. If you do add it and later reference a service not in extra, you'll get a confusing Class not found for something that looks like it should exist. After adding a service, always re-run composer dump-autoload.

Refresh tokens are issued once, and only with the right settings. AccessType::Offline + Prompt::Consent is what gets you a refresh token. If a user authorized before you set those, or authorized with an older scope set, you won't get one — and hasRefreshToken() will be false forever until they re-consent. ApprovalPrompt::Auto means returning users skip the prompt, which is good UX but means you can't silently upgrade their scopes.

Changing service_scopes invalidates nobody. Existing stored tokens keep their old grants; the new scope only applies to fresh authorizations. include_grant_scopes => true helps the refresh request retain what was originally granted, but for a user whose token predates the new scope you must delete their token and re-run consent. Plan scope additions as a migration, not a config change — expect 403 insufficientPermissions otherwise.

Encrypted DB tokens are tied to APP_KEY. Rotating APP_KEY without re-encrypting the google_tokens table silently breaks every stored token. Handle this the same way you'd handle any encrypted-at-rest column during a key rotation.

The migration question is worth 60 seconds. The README says "run the migration if you wish to use database token storage." Verify whether the package auto-loads its migrations or whether you need vendor:publish --tag=migrations. If the table is missing you'll get a raw SQL error at the worst possible moment — mid-OAuth-callback.

Composer timeouts are a real failure mode. The README's "process-timeout": 600 note exists because the Google API client tree is heavy. On slow CI or Docker with a cold cache, raise it rather than retrying and hoping.

Debug Google errors by category, not by message. 403 is almost always scope or "API not enabled in this project" (check the console — enabling is per-API and easy to forget). 401 is token expiry or revocation. 400 redirect_uri_mismatch is a literal string mismatch, including trailing slashes and http vs https. And while your OAuth consent screen is in "Testing" mode, only added test users can authorize, capped at 100 — expect mysterious failures from anyone else.

Long jobs: refresh before the loop, not inside it. If a job processes 500 items over 20 minutes, a token that expires mid-run will fail halfway. Check isExpired() up front, and make sure the refreshed token gets written back through your storage adapter — the value object is readonly and won't persist itself. Verify this behavior explicitly in a test rather than assuming it.

Look in docs/ first. Per-service documentation for Calendar, Gmail, Drive and Books lives in docs/*.md and is far more useful than the README for method signatures. For anything outside those four, the Google PHP client's own docs apply — use service(), then learn the generated model classes.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums