Search by

jatniel / laravel-pexels

jatniel

Laravel integration for the Pexels API: search photos, collections, local downloads and Blade components

Package info

github.com/jatniel/laravel-pexels

pkg:composer/jatniel/laravel-pexels

Fund package maintenance!

Jatniel Guzmán

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-09-27 19:19 UTC

This package is auto-updated.

Last update: 2026-09-27 19:30:13 UTC


README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

A Laravel package for the Pexels API. Search photos with filters, browse curated photos and collections, download images to any storage disk, and drop photos into your views with Blade components.

Developed by Jatniel Guzmán • LinkedIn • X/Twitter

Requirements

  • PHP 8.3+
  • Laravel 11, 12 or 13

Installation

Install the package via Composer:

composer require jatniel/laravel-pexels

Publish the configuration file:

php artisan vendor:publish --tag="pexels-config"

Add your Pexels API key to your .env file:

PEXELS_API_KEY=your-api-key-here

Get your free API key at pexels.com/api.

Usage

Search Photos

use Jatniel\Pexels\Facades\Pexels;

// Search photos
$photos = Pexels::photos()->search('nature', perPage: 15, page: 1);

// Search with filters
$photos = Pexels::photos()->search(
    'ocean',
    orientation: 'landscape', // landscape, portrait, square
    size: 'large',            // large (24MP), medium (12MP), small (4MP)
    color: 'blue',            // color name or hex code (#ffffff)
    locale: 'es-ES',
);

// Get curated photos
$curated = Pexels::photos()->curated(perPage: 10);

// Get a specific photo by ID
$photo = Pexels::photos()->find(12345);

// Get a random photo (by query, or from curated photos)
$random = Pexels::photos()->random('ocean');
$random = Pexels::photos()->random();

// Get the URL of a photo directly
$url = Pexels::photos()->url(12345, 'medium');

Pagination

search(), curated() and the collection methods return a Laravel LengthAwarePaginator of Photo (or Collection) objects:

$photos = Pexels::photos()->search('nature', perPage: 20, page: request('page', 1));

$photos->total();       // Total results on Pexels
$photos->currentPage();
$photos->lastPage();

foreach ($photos as $photo) {
    echo $photo->getUrl('medium');
}
{{ $photos->links() }}

Working with Photos

$photo = Pexels::photos()->find(12345);

// Get photo URL in different sizes
$photo->getUrl('original');
$photo->getUrl('large2x');
$photo->getUrl('large');
$photo->getUrl('medium');
$photo->getUrl('small');

// Get available sizes
$photo->getSizes(); // ['original', 'large2x', 'large', 'medium', 'small', ...]

// Get attribution (required by Pexels), escaped and safe to print with {!! !!}
$photo->getAttribution(); // <a href="...">Photo by John Doe on Pexels</a>
$photo->getAttribution(withLink: false); // Photo by John Doe on Pexels

// Photo data
$photo->id;
$photo->width;
$photo->height;
$photo->photographer;
$photo->avgColor;
$photo->alt;

// Photos (and collections) are Arrayable and JsonSerializable
return response()->json($photo);

Collections

// Get your collections
$collections = Pexels::collections()->all();

// Get photos from a collection
$photos = Pexels::collections()->photos('collection-id');

Download Photos Locally

$photo = Pexels::photos()->find(12345);

// Download a single size
$paths = Pexels::storage()->download($photo, 'original');
// ['original' => '/storage/pexels/12345/original.jpg']

// Download multiple sizes (sizes already stored are skipped)
$paths = Pexels::storage()->download($photo, ['original', 'medium', 'small']);

// Force re-download
$paths = Pexels::storage()->download($photo, 'original', force: true);

// Async download (queued on the configured connection and queue)
Pexels::storage()->downloadAsync($photo, ['original', 'medium']);

// Check if photo exists locally
Pexels::storage()->exists(12345, 'medium');

// Get local URL
Pexels::storage()->localUrl(12345, 'medium');

// Delete local photo
Pexels::storage()->delete(12345); // All sizes
Pexels::storage()->delete(12345, 'medium'); // Specific size

Blade Components

Image Component

{{-- Basic usage --}}
<x-pexels-image id="12345" />

{{-- With specific size --}}
<x-pexels-image id="12345" size="medium" />

{{-- Random photo by query --}}
<x-pexels-image query="mountains" size="large" />

{{-- With attribution (required by Pexels license) --}}
<x-pexels-image id="12345" attribution />

{{-- Use locally downloaded image --}}
<x-pexels-image id="12345" local />

{{-- With custom attributes --}}
<x-pexels-image id="12345" class="rounded-lg shadow-md" />

Background Component

{{-- Basic usage --}}
<x-pexels-background id="12345" class="min-h-screen">
    <h1>Welcome</h1>
</x-pexels-background>

{{-- Random background --}}
<x-pexels-background query="ocean sunset" class="hero-section">
    <div class="content">Your content here</div>
</x-pexels-background>

{{-- With attribution --}}
<x-pexels-background id="12345" attribution attribution-position="bottom-right">
    <h1>Welcome</h1>
</x-pexels-background>

Available sizes: original, large2x, large, medium, small, portrait, landscape, tiny

If the Pexels API fails (network error, rate limit, photo not found), the components render nothing and the exception is reported through Laravel's exception handler, so a Pexels outage never breaks your page.

Error Handling

All exceptions extend Jatniel\Pexels\Exceptions\PexelsException:

use Jatniel\Pexels\Exceptions\PexelsException;
use Jatniel\Pexels\Exceptions\PhotoNotFoundException;
use Jatniel\Pexels\Exceptions\RateLimitException;

try {
    $photo = Pexels::photos()->find(12345);
} catch (PhotoNotFoundException $e) {
    // The photo does not exist
} catch (RateLimitException $e) {
    // Local limit or Pexels API limit (HTTP 429) reached
} catch (PexelsException $e) {
    // Missing API key, connection error or any other API error
}

Caching and Rate Limiting

API responses are cached (1 hour by default), and cached responses do not count toward the rate limit. The local rate limiter stops requests before you hit the Pexels limit (200 requests/hour on the free plan). Connection errors are retried twice.

Configuration

// config/pexels.php

return [
    // API keys. PEXELS_API_KEY_TEST is used outside production when set.
    'api_key' => env('PEXELS_API_KEY'),
    'api_key_test' => env('PEXELS_API_KEY_TEST'),

    // HTTP timeout in seconds
    'timeout' => env('PEXELS_TIMEOUT', 10),

    // Cache settings
    'cache' => [
        'enabled' => env('PEXELS_CACHE_ENABLED', true),
        'ttl' => env('PEXELS_CACHE_TTL', 3600), // 1 hour
    ],

    // Storage settings for local downloads
    'storage' => [
        'disk' => env('PEXELS_STORAGE_DISK', 'public'),
        'path' => env('PEXELS_STORAGE_PATH', 'pexels'),
    ],

    // Rate limiting (free plan: 200 req/hour)
    'rate_limit' => [
        'enabled' => env('PEXELS_RATE_LIMIT_ENABLED', true),
        'requests_per_hour' => env('PEXELS_RATE_LIMIT_REQUESTS', 200),
    ],

    // Queue settings for async downloads
    'queue' => [
        'connection' => env('PEXELS_QUEUE_CONNECTION'),
        'name' => env('PEXELS_QUEUE_NAME', 'pexels'),
    ],

    // Attribution format
    'attribution' => [
        'format' => 'Photo by :photographer on Pexels',
        'link_to_profile' => true,
    ],
];

Attribution

Pexels requires attribution to photographers. Use the attribution prop on Blade components or call $photo->getAttribution() to generate proper credit.

Read more: Pexels License

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Issues and pull requests are welcome on GitHub. Please run composer test, composer analyse and composer format before submitting.

Security Vulnerabilities

If you discover a security vulnerability, please email hello@jatniel.dev instead of opening a public issue.

Credits

License

The MIT License (MIT). Please see License File for more information.