PHPackages                             mahdimajidzadeh/laravel-unsplash - 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. [API Development](/categories/api)
4. /
5. mahdimajidzadeh/laravel-unsplash

ActiveLibrary[API Development](/categories/api)

mahdimajidzadeh/laravel-unsplash
================================

Laravel package for the Unsplash API

v0.1.5(4y ago)1111.4k5MITPHPPHP ~5.6|~7.0|^8.0CI failing

Since Jan 4Pushed 2w ago2 watchersCompare

[ Source](https://github.com/MahdiMajidzadeh/Laravel-Unsplash)[ Packagist](https://packagist.org/packages/mahdimajidzadeh/laravel-unsplash)[ Docs](https://github.com/MahdiMajidzadeh/Laravel-Unsplash)[ RSS](/packages/mahdimajidzadeh-laravel-unsplash/feed)WikiDiscussions master Synced today

READMEChangelog (5)Dependencies (2)Versions (10)Used By (0)

Laravel-Unsplash
================

[](#laravel-unsplash)

[![tests](https://github.com/MahdiMajidzadeh/Laravel-Unsplash/actions/workflows/tests.yml/badge.svg)](https://github.com/MahdiMajidzadeh/Laravel-Unsplash/actions/workflows/tests.yml)[![packagist](https://camo.githubusercontent.com/c52263be52f7c4af4add92ddf0e2a4583538887988eb1b11726a5609026ffd1f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6d616864696d616a69647a616465682f6c61726176656c2d756e73706c6173682e737667)](https://packagist.org/packages/mahdimajidzadeh/laravel-unsplash)[![license](https://camo.githubusercontent.com/a8d6c1d810bfa9c463ae3144c83489f29885bda8e3399d90a93d6a63dd90331e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6d616864696d616a69647a616465682f6c61726176656c2d756e73706c6173682e737667)](LICENSE)

A Laravel package covering the whole [Unsplash API](https://unsplash.com/documentation): photos, users, the logged-in user, collections, topics, search, stats and the user authentication (OAuth) workflow.

Install
-------

[](#install)

Via Composer

```
$ composer require mahdimajidzadeh/laravel-unsplash
```

If you do not run Laravel 5.5 (or higher), then add the service provider and the facade alias in `config/app.php`:

```
'providers' => [
    MahdiMajidzadeh\LaravelUnsplash\LaravelUnsplashServiceProvider::class,
],

'aliases' => [
    'Unsplash' => MahdiMajidzadeh\LaravelUnsplash\Facades\Unsplash::class,
],
```

On Laravel 5.5+ package auto-discovery takes care of both.

Publishing the configuration is optional — the package ships with defaults — but useful if you want to tweak them:

```
$ php artisan vendor:publish --tag=unsplash-config
```

That copies the defaults to `config/unsplash.php`.

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

[](#configuration)

Register an application at [unsplash.com/oauth/applications](https://unsplash.com/oauth/applications)and add your keys to `.env`:

```
UNSPLASH_ACCESS_KEY=your-access-key

# Only needed for the user authentication (OAuth) workflow
UNSPLASH_SECRET_KEY=your-secret-key
UNSPLASH_REDIRECT_URI=https://your-app.test/unsplash/callback

# Optional: act on behalf of a single user on every request
UNSPLASH_ACCESS_TOKEN=
```

The legacy `ApplicationID` configuration key is still honoured, so configuration files published by older versions of this package keep working.

Usage
-----

[](#usage)

Everything is reachable from the `Unsplash` facade:

```
use MahdiMajidzadeh\LaravelUnsplash\Facades\Unsplash;

$photos = Unsplash::photos()->photos(['per_page' => 30])->get();
```

You can also resolve `MahdiMajidzadeh\LaravelUnsplash\Unsplash` from the container, or instantiate a single resource directly — as in previous versions of this package:

```
$unsplash = new MahdiMajidzadeh\LaravelUnsplash\Photo();
$photos   = $unsplash->photos()->get();
```

Every endpoint method performs the request and returns the resource, so the result can be read with:

MethodReturns`get()`the decoded body (`stdClass` or array of `stdClass`)`getArray()`the decoded body cast to an array`toArray()`the decoded body as a nested array`raw()`the raw JSON body`status()`the HTTP status code`headers()`all response headers`totalItems()`total number of items available`totalPages()`total number of pages available`links()`the parsed `Link` header (`first`, `prev`, `next`, `last`)`rateLimit()`requests allowed per hour`rateLimitRemaining()`requests left for the current hour`response()`the response object, for anything else```
$photos = Unsplash::photos()->photos(['page' => 2, 'per_page' => 30]);

$photos->get();                       // the photos
$photos->totalPages();                // 1234
$photos->response()->nextPage();      // 3
$photos->rateLimitRemaining();        // 987
```

See the [Unsplash documentation](https://unsplash.com/documentation) for the parameters accepted by each endpoint; they are passed straight through as the `$params` array, with a few conveniences:

- `null` values are dropped, so you can pass optional parameters unconditionally.
- Booleans are sent as the `true`/`false` strings Unsplash expects.
- Lists are sent as the comma separated values the API documents for multi value parameters, so `['collections' => [123, 456]]` becomes `collections=123,456`. An empty list is left out entirely.
- Nested parameters such as `location` and `exif` on a photo update keep their keys.

### Photos

[](#photos)

```
$photos = Unsplash::photos();

$photos->photos($params)->get();              // GET    /photos
$photos->single($id, $params)->get();         // GET    /photos/:id
$photos->random($params)->get();              // GET    /photos/random
$photos->statistics($id, $params)->get();     // GET    /photos/:id/statistics
$photos->download($id);                       // GET    /photos/:id/download — returns the URL
$photos->trackDownload($id)->get();           // GET    /photos/:id/download — chainable
$photos->update($id, $params)->get();         // PUT    /photos/:id                (write_photos)
$photos->like($id)->get();                    // POST   /photos/:id/like            (write_likes)
$photos->unlike($id);                         // DELETE /photos/:id/like            (write_likes)
```

`all()` is an alias of `photos()`, `find()` of `single()` and `statistic()` of `statistics()`.

Unsplash requires `download($id)` (or `trackDownload($id)`) to be called whenever your application downloads a photo, so the photographer gets credited.

`getID()` and `getURL()` are available on any response holding photos — a single photo, a list, or search results:

```
Unsplash::photos()->random()->getID();          // WLUHO9A_xik
Unsplash::photos()->random()->getURL();         // 1600x900, cropped
Unsplash::photos()->random()->getURL(800, 600);
// https://images.unsplash.com/photo-1417325384643-aac51acc9e5d?ixid=...&w=800&h=600&fit=crop
```

Unsplash serves its images through Imgix, so `getURL()` takes the `raw` url from the api response and appends the sizing parameters to it. Use `getSizedURL()` to pick one of the sizes Unsplash returns as-is, or to pass your own [Imgix parameters](https://docs.imgix.com/apis/rendering):

```
Unsplash::photos()->random()->getSizedURL('regular');        // raw, full, regular, small, thumb
Unsplash::photos()->random()->getSizedURL('raw', ['w' => 800]);
```

All three methods return `null` when the response holds no photo.

### Users

[](#users)

```
$users = Unsplash::users();

$users->single($username, $params)->get();       // GET /users/:username
$users->portfolio($username);                    // GET /users/:username/portfolio — returns the URL
$users->photos($username, $params)->get();       // GET /users/:username/photos
$users->likes($username, $params)->get();        // GET /users/:username/likes
$users->collections($username, $params)->get();  // GET /users/:username/collections
$users->statistics($username, $params)->get();   // GET /users/:username/statistics
```

`find()` is an alias of `single()` and `statistic()` of `statistics()`.

### Current user

[](#current-user)

These endpoints need a bearer token — see [User authentication](#user-authentication).

```
$me = Unsplash::withAccessToken($token)->me();

$me->profile()->get();          // GET /me           (read_user)
$me->update($params)->get();    // PUT /me           (write_user)
```

### Collections

[](#collections)

```
$collections = Unsplash::collections();

$collections->collections($params)->get();       // GET    /collections
$collections->single($id, $params)->get();       // GET    /collections/:id
$collections->photos($id, $params)->get();       // GET    /collections/:id/photos
$collections->related($id)->get();               // GET    /collections/:id/related
$collections->create($params)->get();            // POST   /collections                          (write_collections)
$collections->update($id, $params)->get();       // PUT    /collections/:id                      (write_collections)
$collections->delete($id);                       // DELETE /collections/:id                      (write_collections)
$collections->addPhoto($id, $photoId)->get();    // POST   /collections/:collection_id/add       (write_collections)
$collections->removePhoto($id, $photoId);        // DELETE /collections/:collection_id/remove    (write_collections)
```

`create()` also accepts the title directly:

```
Unsplash::withAccessToken($token)->collections()->create('Good dogs', 'A description', false);
```

`all()` is an alias of `collections()` and `find()` of `single()`.

### Topics

[](#topics)

```
$topics = Unsplash::topics();

$topics->topics($params)->get();                 // GET /topics
$topics->single($idOrSlug, $params)->get();      // GET /topics/:id_or_slug
$topics->photos($idOrSlug, $params)->get();      // GET /topics/:id_or_slug/photos
```

`all()` is an alias of `topics()` and `find()` of `single()`.

### Search

[](#search)

```
$search = Unsplash::search();

$search->photo($query, $params)->get();          // GET /search/photos
$search->collection($query, $params)->get();     // GET /search/collections
$search->user($query, $params)->get();           // GET /search/users
```

`photos()`, `collections()` and `users()` are aliases of the above. Search responses wrap their matches, so `results()` returns them without the surrounding counters:

```
$search = Unsplash::search()->photo('dogs', ['orientation' => 'landscape']);

$search->results();      // the photos
$search->totalItems();   // 1337
$search->totalPages();   // 134
```

### Stats

[](#stats)

```
$stats = Unsplash::stats();

$stats->total()->get();     // GET /stats/total
$stats->month()->get();     // GET /stats/month
```

### User authentication

[](#user-authentication)

Public requests are authenticated with your access key. To read private data or act on behalf of a user, send them through the OAuth workflow and use the resulting bearer token.

```
use MahdiMajidzadeh\LaravelUnsplash\Facades\Unsplash;

// 1. Send the user to Unsplash to authorize your application
Route::get('unsplash/redirect', function () {
    return Unsplash::oauth()->redirect(['read_user', 'write_likes']);

    // or build the URL yourself:
    // return redirect(Unsplash::oauth()->authorizeUrl(['read_user'], null, $state));
});

// 2. Exchange the code Unsplash sends back for an access token
Route::get('unsplash/callback', function (Illuminate\Http\Request $request) {
    $token = Unsplash::oauth()->accessToken($request->query('code'));

    // 3. Use it — access tokens do not expire
    return Unsplash::withAccessToken($token)->me()->profile()->get();
});
```

`requestToken($code)` gives you the whole token response (`access_token`, `token_type`, `scope`, `created_at`) instead of just the token.

Available scopes are listed in `OAuth::SCOPES`: `public`, `read_user`, `write_user`, `read_photos`, `write_photos`, `write_likes`, `write_followers`, `read_collections`, `write_collections`. The default scopes used by `redirect()` and `authorizeUrl()` come from the `unsplash.scopes`configuration entry.

`withAccessToken($token)` returns a copy, so the shared instance keeps using your access key. It is available on the `Unsplash` entry point and on every resource:

```
$collections = Unsplash::collections()->withAccessToken($token);
$collections->create('Good dogs');
```

### Errors

[](#errors)

Non 2xx responses throw an exception carrying the status code and the messages Unsplash returned:

```
use MahdiMajidzadeh\LaravelUnsplash\Exceptions\NotFoundException;
use MahdiMajidzadeh\LaravelUnsplash\Exceptions\UnsplashException;

try {
    Unsplash::photos()->single('does-not-exist')->get();
} catch (NotFoundException $e) {
    $e->status();   // 404
    $e->errors();   // ['Couldn't find Photo']
} catch (UnsplashException $e) {
    // any other API error
}
```

`UnauthorizedException` (401), `ForbiddenException` (403), `NotFoundException`(404), `ValidationException` (422) and `RateLimitException` (429) all extend `UnsplashException`, which extends `RuntimeException`.

Requests that never reach the API — DNS failures, timeouts, refused connections — throw a `ConnectionException`, which extends `UnsplashException` too. Catching `UnsplashException` therefore covers every failure mode, and `$e->getPrevious()` gives you the underlying Guzzle exception.

Testing
-------

[](#testing)

`Unsplash::fake()` answers from a queue of responses instead of calling the API. When the container is booted it replaces the bound instance, so the facade and anything type hinting `Unsplash` receive the fake too:

```
use MahdiMajidzadeh\LaravelUnsplash\Facades\Unsplash;

public function test_it_shows_a_random_photo()
{
    $unsplash = Unsplash::fake([
        ['id' => 'abc123', 'urls' => ['raw' => 'https://images.unsplash.com/photo-1']],
    ]);

    $this->get('/')->assertSee('abc123');

    // Every request made against the fake is recorded.
    $this->assertSame(1, $unsplash->recordedCount());
    $this->assertSame('/photos/random', $unsplash->recordedRequest()->getUri()->getPath());
}
```

Plain arrays become `200` responses. Use `FakeResponse` when you need to control the status code, the headers or the pagination metadata:

```
use MahdiMajidzadeh\LaravelUnsplash\Testing\FakeResponse;

Unsplash::fake([
    FakeResponse::make(['id' => 'abc123'], 200, ['X-Ratelimit-Remaining' => '12']),
    FakeResponse::error("Couldn't find Photo", 404),
    FakeResponse::paginated([['id' => 'a'], ['id' => 'b']], 40, 10),
]);
```

Responses are returned in order, and `Unsplash::fake()` returns the fake so you can inspect it. Requests are recorded as PSR-7 request objects, readable with `recorded()`, `recordedRequest($index)` and `recordedCount()`.

Deprecated endpoints
--------------------

[](#deprecated-endpoints)

`Photo::curated()`, `Collection::curated()` and `Collection::featured()` are kept for backwards compatibility, but Unsplash retired those endpoints — use the topic endpoints instead. The `Unsplush` base class is likewise kept as an alias of `Endpoint`.

Contributing
------------

[](#contributing)

Run the test suite with:

```
$ composer test
```

Code style is enforced with [Pint](https://laravel.com/docs/pint). It needs PHP 8.2+, so it is not a development dependency of this package — install it globally and run it from the package root:

```
$ composer global require laravel/pint
$ pint --test
```

Changelog
---------

[](#changelog)

See [CHANGELOG.md](CHANGELOG.md).

License
-------

[](#license)

The MIT License (MIT). See [LICENSE](LICENSE) for details.

###  Health Score

46

↑

FairBetter than 92% of packages

Maintenance63

Regular maintenance activity

Popularity29

Limited adoption so far

Community15

Small or concentrated contributor base

Maturity64

Established project with proven stability

 Bus Factor1

Top contributor holds 83.3% 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 ~307 days

Recently: every ~376 days

Total

6

Last Release

1611d ago

PHP version history (2 changes)v0.1PHP ~5.6|~7.0

v0.1.5PHP ~5.6|~7.0|^8.0

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/6115476?v=4)[Mahdi Majidzadeh](/maintainers/MahdiMajidzadeh)[@MahdiMajidzadeh](https://github.com/MahdiMajidzadeh)

---

Top Contributors

[![MahdiMajidzadeh](https://avatars.githubusercontent.com/u/6115476?v=4)](https://github.com/MahdiMajidzadeh "MahdiMajidzadeh (25 commits)")[![claude](https://avatars.githubusercontent.com/u/81847?v=4)](https://github.com/claude "claude (3 commits)")[![nidhalkratos](https://avatars.githubusercontent.com/u/10298337?v=4)](https://github.com/nidhalkratos "nidhalkratos (1 commits)")[![StyleCIBot](https://avatars.githubusercontent.com/u/11048387?v=4)](https://github.com/StyleCIBot "StyleCIBot (1 commits)")

---

Tags

laravellaravel-packagephotophpunsplashapilaravellaravel-packagephotoUnsplash

### Embed Badge

![Health badge](/badges/mahdimajidzadeh-laravel-unsplash/health.svg)

```
[![Health](https://phpackages.com/badges/mahdimajidzadeh-laravel-unsplash/health.svg)](https://phpackages.com/packages/mahdimajidzadeh-laravel-unsplash)
```

###  Alternatives

[craftcms/cms

Craft CMS

3.6k3.7M3.4k](/packages/craftcms-cms)[eslazarev/wildberries-sdk

Wildberries OpenAPI clients (generated).

353.6k](/packages/eslazarev-wildberries-sdk)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5226.7k](/packages/simplestats-io-laravel-client)

PHPackages © 2026

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