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

Laravel Unsplash Laravel Package

marksitko/laravel-unsplash

Laravel Unsplash is a clean wrapper around the Unsplash API for Laravel apps. Search and fetch photos, collections, users, and topics, track downloads, and use a fluent facade with config-driven credentials to add stock imagery to any project in minutes.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the wrapper and publish its config:

composer require marksitko/laravel-unsplash
php artisan vendor:publish --tag=unsplash-config

Then set your Unsplash Access Key (not the Secret Key — the secret is only needed for OAuth flows you almost certainly don't need):

UNSPLASH_ACCESS_KEY=your-access-key

Look at these three things first, in this order:

  1. config/unsplash.php — the access key binding and any default query params the package merges into requests.
  2. The Unsplash facade (and its underlying client class) — this is the entire public surface area; skim method signatures before reading the README examples.
  3. The package's tests — they're the most accurate documentation of accepted parameter shapes.

First real use case — populating a media picker or seed data with a search:

use Marksitko\Unsplash\Unsplash; // or the registered facade alias

$results = Unsplash::searchPhotos('mountain sunrise', [
    'per_page' => 12,
    'page'     => 1,
    'lang'     => 'de', // new in v3.1.0
]);

v3.1.0 relevance: the only change is a new lang query parameter on photo search. If you previously hard-coded en-US behaviour or worked around locale by appending terms to the query string, you can drop that workaround. No breaking changes, no config migration — this is a drop-in minor bump.

Implementation Patterns

Wrap the facade in a small service, not in controllers. The facade is convenient but untestable-by-default and easy to scatter across the codebase. One UnsplashService (or a repository) that owns caching, retries, and parameter normalisation pays for itself the first time you need to swap an image source.

Drive lang from the application locale. The new parameter is most valuable when it's derived, not typed:

$lang = substr(app()->getLocale(), 0, 2); // 'en', 'de', 'fr'

$photos = Unsplash::searchPhotos($term, [
    'per_page' => 20,
    'lang'     => $lang,
]);

Clamp it to the languages Unsplash actually supports (a two-letter ISO-639-1 code) and fall back to en — an unsupported code returns fewer or less-relevant results rather than an error, so failures here are silent.

Treat search as a cacheable, rate-limited read. Unsplash's demo tier is famously tight (tens of requests per hour). Cache result sets keyed on a hash of query + params, with a short TTL (minutes to an hour) for search and a much longer one for individual photo lookups, which never change:

$key = 'unsplash:search:' . md5($term . serialize($params));

return Cache::remember($key, now()->addMinutes(30), fn () => Unsplash::searchPhotos($term, $params));

Build the query array, don't concatenate strings. Pass lang, page, per_page, orientation, color, order_by as an associative array. Keeping parameters structured means the cache key, the log context, and the request all derive from one source of truth — and it's what makes adding lang a one-line change rather than a refactor.

Use it behind a queued job for anything bulk. Seeding a catalogue of images, warming a cache, or syncing a collection should be a queued job with rateLimit/backoff configured. Synchronous loops over search results in a request cycle will hit the rate limit and take your page down with it.

Fake the transport in tests. Bind an Http::fake() response (or swap the client in the container) rather than hitting the live API. Assert on the outgoing URL to prove lang is being forwarded correctly — that's the one thing a v3.1.0 upgrade could silently regress if you were passing params positionally.

Gotchas and Tips

  • lang affects search relevance and metadata, not image content. It does not translate titles you display; the alt_description/description fields come back in whatever language Unsplash has for that photo. Don't promise localised captions to stakeholders.
  • lang is a photo-search parameter. Passing it to unrelated endpoints (random photo, collection listing, photo lookup) is a no-op at best and an unexpected 400 at worst. Keep it scoped to the search call.
  • Access Key vs Secret Key confusion is the #1 setup failure. A missing/invalid key surfaces as a 401 from Unsplash, not a helpful config exception — check config/unsplash.php and your config:cache state first.
  • Stale config after deploy. If you added UNSPLASH_ACCESS_KEY and it's still null in production, you forgot php artisan config:cache. Same trap applies if you override defaults in the config file.
  • Attribution and hotlinking are contractual, not optional. Unsplash's API guidelines require crediting the photographer with a link back to their profile and linking to Unsplash; images must be hotlinked from the returned URLs rather than downloaded and re-served. Build this into your view component early — retrofitting attribution across an existing gallery is tedious.
  • Rate limits are per-application, not per-user. A shared cache is the mitigation; per-user caching just multiplies your request count. Watch for the X-Ratelimit-Remaining header and log it so you notice degradation before users do.
  • Pagination is 1-indexed and capped. per_page has a hard maximum (30 for search on most tiers) and very deep pages stop being useful. Don't build an infinite-scroll UI that assumes unbounded page values.
  • Extension point: the client is bound in the container, so you can decorate or rebind it to inject logging, retry middleware, or a Http::withHeaders() client. Prefer this over editing vendor code when you need custom headers or a proxy.
  • Pin the constraint as ^3.1 rather than ^3.0 if lang support is load-bearing for a localised feature — 3.0 would silently accept and ignore the parameter.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings