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

Photos Library Laravel Package

google/photos-library

A PHP client for the Google Photos Library API that lets server-side apps authenticate, list and search albums and media items, upload photos and videos, and apply filters. Generated from Google’s API definitions for straightforward Composer installation.

View on GitHub
Deep Wiki
Context7

Getting Started

The Composer package is google/photos-library (repo google/php-photoslibrary). It is archived — pin it explicitly and treat every version as the last one you'll get:

composer require google/photos-library:^1.0

First things to read, in order:

  1. samples/ in the repo — the OAuth bootstrap, upload.php, list_albums.php, create_album.php are the fastest path to a working call.
  2. src/PhotosLibrary/V1/PhotosLibraryClient.php — the generated client; its method signatures are the real documentation (they drift from the public REST docs).
  3. src/PhotosLibrary/V1/PhotosLibraryResourceFactory.php — the only pleasant part of the API; use it instead of hand-building protos.

Minimal first use case (list albums with a service-account/user credentials file):

use Google\Photos\Library\V1\PhotosLibraryClient;

$client = new PhotosLibraryClient([
    'credentials' => '/path/to/credentials.json',
    // 'transport' => 'rest', // handy when debugging with curl
]);

$response = $client->listAlbums(['pageSize' => 50]);
foreach ($response->getAlbums() ?: [] as $album) {
    printf("%s (%s)\n", $album->getTitle(), $album->getId());
}

Note that photoslibrary.* scopes are user scopes — a plain service account will not work against a real Photos library. You need an OAuth2 refresh token (or an interactive consent flow) for a human account. Get that working before writing any feature code; it is the single biggest time sink in this package.

Implementation Patterns

Wrap the client, don't inject it raw. The generated client is hard to mock and its constructor does credential resolution eagerly. Put it behind your own interface (PhotoLibrary::listAlbums(), PhotoLibrary::upload()), construct the PhotosLibraryClient lazily inside a factory, and register that factory in the container. This is also your escape hatch if you ever migrate off the archived lib.

Upload → factory → batchCreate is the only write path. There is no "add one photo" call. The flow is always:

use Google\Photos\Library\V1\PhotosLibraryResourceFactory;

$token = $client->uploadMediaItem($absolutePath);   // bytes go up here
$item  = PhotosLibraryResourceFactory::newMediaItem($description, $token);
$res   = $client->batchCreateMediaItems([
    'albumId'       => $albumId,        // omit to add to library root
    'newMediaItems' => [$item],
]);

Wrap the last two calls in a single method so callers can't accidentally upload without consuming the token. For bulk imports, upload N files, collect N tokens, then send one batchCreateMediaItems with up to 50 items — that's the hard cap per request.

Pagination is manual everywhere. Every list/search method returns a response with getNextPageToken(); loop until it's empty:

$token = null;
do {
    $page = $client->searchMediaItems([
        'albumId'  => $albumId,
        'pageSize' => 100,
        'pageToken'=> $token,
    ]);
    foreach ($page->getMediaItems() ?: [] as $item) { /* ... */ }
    $token = $page->getNextPageToken();
} while ($token);

Always coalesce ?: [] — empty result sets come back as null from the proto getters, and foreach (null) is a fatal error.

Search filters are proto objects, not arrays of strings. Date ranges need Google\Type\Date instances:

use Google\Photos\Library\V1\Filters;
use Google\Photos\Library\V1\DateFilter;
use Google\Type\Date;

$filters = new Filters();
$filters->setDateFilter(
    (new DateFilter())->setRanges([
        (new \Google\Photos\Library\V1\DateRange())
            ->setStartDate(new Date(['year' => 2024, 'month' => 1, 'day' => 1]))
            ->setEndDate(new Date(['year' => 2024, 'month' => 12, 'day' => 31])),
    ])
);
$client->searchMediaItems(['albumId' => $albumId, 'filters' => $filters]);

Date validates on construction — a bad month throws, so build these in a small value-object helper and unit-test it.

Album creation + population is the canonical workflow. PhotosLibraryResourceFactory::newAlbum($title) → $client->createAlbum($album) → use $album->getId() from the response for all subsequent batchCreateMediaItems calls. The returned album is a new instance; the one you passed in has no ID.

Integration tip — keep the credential path in config, the token in the DB. Store the refresh token per user, and build the client with:

new PhotosLibraryClient([
    'credentials' => [
        'client_id'     => config('services.google.client_id'),
        'client_secret' => config('services.google.client_secret'),
        'refresh_token' => $user->google_refresh_token,
    ],
]);

The library accepts an array, a path, or a Google\Auth\FetchAuthTokenInterface — passing your own token fetcher is the cleanest way to handle per-user tokens and refresh-token rotation.

Gotchas and Tips

  • Archived and unmaintained. No PHP 8.3+ fixes, no API updates (the 2025 Google Photos API scope restrictions landed after the last release). Budget for a rewrite onto google/apiclient or raw HTTP if this is production-critical.
  • You cannot read the whole library. searchMediaItems requires either an albumId or a filters object; and since the 2025 platform changes, apps generally only see media they created (photoslibrary.readonly.appcreateddata). Design around "albums our app owns," not "the user's photo library."
  • Photos are never returned as bytes. getBaseUrl() gives you a short-lived URL (roughly an hour) that you must suffix yourself: $baseUrl . '=w2048-h1024' for a resized image, =d to download the original. Cache the download server-side; never store the base URL in a database.
  • Upload tokens are single-use and expire quickly. If batchCreateMediaItems fails, re-upload — don't retry with the old token. This makes retry logic subtly expensive; log upload failures separately.
  • Shared albums are read-only. You can joinSharedAlbum/leaveSharedAlbum and read, but batchCreateMediaItems with a shared album ID fails. Check album ownership before offering upload UI.
  • Errors are Google\ApiCore\ApiException, not Guzzle exceptions. Use $e->getStatus() (RESOURCE_EXHAUSTED for quota, PERMISSION_DENIED, INVALID_ARGUMENT) and $e->getBasicMessage(). Catching \Exception broadly will hide the quota signal you actually need to handle.
  • Quota is low by default (order of 10k requests/day per project, with per-method costs). A pagination loop over a large album burns requests fast — set pageSize to 100 and cache results.
  • Credentials config quirks: the credentials option is passed straight to Google\Auth; relative paths resolve from the process CWD, not the app root. GOOGLE_APPLICATION_CREDENTIALS env var works as a fallback, which can silently mask a bad config value in tests.
  • Debugging aid: set 'transport' => 'rest' in the constructor options — messages become readable JSON errors instead of gRPC-style ones, and you can enableDebugOutput() via the underlying Google\ApiCore options if you need the raw request.
  • Changing scopes invalidates stored tokens. Adding photoslibrary.sharing later means every user must re-consent. Decide the full scope set on day one.
  • Extension point: if you need behaviour the client lacks (custom retry with backoff, request logging), subclass nothing — decorate. PhotosLibraryClient is generated and regenerating/updating will clobber edits; wrap it.
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