bjthecod3r/laravel-tmdb

A fluent, fully-typed Laravel wrapper for The Movie Database (TMDB) API.

Maintainers

Package info

github.com/BJTheCod3r/laravel-tmdb

pkg:composer/bjthecod3r/laravel-tmdb

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-06-03 21:16 UTC

This package is auto-updated.

Last update: 2026-06-03 21:25:55 UTC


README

Laravel TMDB — a fluent, fully-typed Laravel wrapper for The Movie Database API

Tests Latest Stable Version Total Downloads License

Laravel TMDB

A fluent, fully-typed Laravel wrapper for The Movie Database (TMDB) API.

use BjTheCod3r\Tmdb\Facades\Tmdb;

$movie = Tmdb::movies()->details(27205);

$movie->title;          // "Inception"
$movie->year();         // 2010
$movie->genres->pluck('name'); // Illuminate\Support\Collection

Tmdb::image()->url($movie->posterPath, 'w500');

Supports Laravel 11, 12 and 13 on PHP 8.2+.

Installation

composer require bjthecod3r/laravel-tmdb

Publish the config file (optional):

php artisan vendor:publish --tag=tmdb-config

Configuration

Add your credentials to .env. The package prefers the v4 Read Access Token (sent as a Bearer header); if it is absent it falls back to the classic v3 API key (sent as an api_key query parameter).

TMDB_TOKEN=your-read-access-token
# or, for the v3 key:
TMDB_API_KEY=your-api-key

# optional
TMDB_LANGUAGE=en-US
TMDB_REGION=US
TMDB_INCLUDE_ADULT=false

You can find both credentials under Settings → API in your TMDB account.

Usage

The Tmdb facade exposes one accessor per endpoint group. Calls return typed resource objects; list endpoints return a Paginated wrapper. Every method accepts an optional $params array for extra TMDB query parameters (page, append_to_response, region, …).

Movies

Tmdb::movies()->details(27205);
Tmdb::movies()->details(27205, ['append_to_response' => ['credits', 'videos']]);
Tmdb::movies()->credits(27205);          // Credits (cast + crew)
Tmdb::movies()->images(27205);           // Collection<Image>
Tmdb::movies()->videos(27205);           // Collection<Video>
Tmdb::movies()->reviews(27205);          // Paginated<Review>
Tmdb::movies()->recommendations(27205);  // Paginated<Movie>
Tmdb::movies()->similar(27205);          // Paginated<Movie>
Tmdb::movies()->watchProviders(27205);
Tmdb::movies()->popular();
Tmdb::movies()->topRated();
Tmdb::movies()->nowPlaying();
Tmdb::movies()->upcoming();

TV

Tmdb::tv()->details(1396);
Tmdb::tv()->season(1396, 1);             // Season (with episodes)
Tmdb::tv()->episode(1396, 1, 1);         // Episode
Tmdb::tv()->credits(1396);
Tmdb::tv()->popular();
Tmdb::tv()->topRated();
Tmdb::tv()->onTheAir();
Tmdb::tv()->airingToday();

People

Tmdb::people()->details(6193);
Tmdb::people()->movieCredits(6193);
Tmdb::people()->tvCredits(6193);
Tmdb::people()->images(6193);            // Collection<Image>
Tmdb::people()->popular();

Search

Tmdb::search()->movies('inception', ['year' => 2010]); // Paginated<Movie>
Tmdb::search()->tv('breaking bad');                    // Paginated<TvShow>
Tmdb::search()->people('nolan');                       // Paginated<Person>
Tmdb::search()->companies('warner');                   // Paginated<ProductionCompany>
Tmdb::search()->collections('matrix');                 // Paginated<MovieCollection>
Tmdb::search()->keywords('superhero');                 // Paginated<Keyword>

// Multi-search returns mixed media; promote each result to its concrete type:
foreach (Tmdb::search()->multi('matrix') as $result) {
    $resource = $result->asResource(); // Movie | TvShow | Person
}

Search and discover requests send the configured TMDB_INCLUDE_ADULT default as include_adult; pass ['include_adult' => 'true'] to override per call.

Discover

discover() returns a fluent builder; call get() to execute.

Tmdb::discover()->movies()
    ->withGenres([28, 12])          // Action, Adventure
    ->year(2023)
    ->withMinimumVoteAverage(7.5)
    ->sortBy('popularity.desc')
    ->page(1)
    ->get();                        // Paginated<Movie>

// Any TMDB discover filter is available via where():
Tmdb::discover()->tv()->where('with_networks', 213)->get();

Trending

Tmdb::trending()->movies('week');  // Paginated<Movie>
Tmdb::trending()->tv('day');       // Paginated<TvShow>
Tmdb::trending()->people('week');  // Paginated<Person>
Tmdb::trending()->all('day');      // Paginated<MediaResult>

Genres & Configuration

Tmdb::genres()->movies();          // Collection<Genre>
Tmdb::genres()->tv();              // Collection<Genre>

Tmdb::configuration()->details();  // image base URLs & sizes
Tmdb::configuration()->countries();
Tmdb::configuration()->languages();

Resources

Endpoint methods return typed objects (Movie, TvShow, Person, …) with documented properties — your IDE autocompletes them and dates come back as CarbonImmutable instances:

$movie = Tmdb::movies()->details(27205);

$movie->title;           // string
$movie->releaseDate;     // CarbonImmutable
$movie->voteAverage;     // float
$movie->genres;          // Collection<Genre>

Every resource also keeps the raw TMDB payload. Any field — even one not mapped to a typed property — is reachable via property access, array access, get() (dot notation supported), and toArray() / jsonSerialize():

$movie->get('belongs_to_collection.name');
$movie['original_title'];
return $movie; // a controller can return it directly as JSON

Pagination

List endpoints return a Paginated wrapper that is iterable and JSON-serializable:

$page = Tmdb::movies()->popular(['page' => 1]);

$page->results;        // Collection<Movie>
$page->page;           // 1
$page->totalPages;     // 500
$page->totalResults;   // 10000
$page->hasMorePages(); // true
$page->nextPage();     // 2

foreach ($page as $movie) {
    // ...
}

Image URLs

TMDB resources expose relative image paths. Build absolute URLs with the image() helper (sizes come from Tmdb::configuration()->details()):

Tmdb::image()->url($movie->posterPath, 'w500');
Tmdb::image()->original($movie->backdropPath);

The CDN root defaults to https://image.tmdb.org/t/p/ and can be changed via TMDB_IMAGE_BASE_URL (config key image_base_url).

Error handling

Failed requests throw typed exceptions, all extending TmdbException:

Exception When
AuthenticationException 401 — invalid/missing credentials
ResourceNotFoundException 404 — unknown resource
ValidationException 400 / 422 — invalid request
RateLimitException 429 — exposes ->retryAfter (seconds)
ApiException connection errors and unexpected 5xx

If neither credential is configured, an AuthenticationException is thrown before any request is sent.

Rate-limit (429) and 5xx responses to GET requests are retried automatically per the retry config before the exception is thrown, honouring the TMDB Retry-After header when present. Write requests (POST/DELETE) are never retried, since they are not idempotent.

use BjTheCod3r\Tmdb\Exceptions\ResourceNotFoundException;

try {
    Tmdb::movies()->details(999999999);
} catch (ResourceNotFoundException $e) {
    // ...
}

Dependency injection

The facade is convenient, but you can also type-hint the manager or the underlying client:

use BjTheCod3r\Tmdb\Tmdb;

public function __construct(private Tmdb $tmdb) {}

An escape hatch for endpoints not yet wrapped:

Tmdb::client()->get('movie/27205/keywords');

Testing

Because the client is built on Laravel's Http facade, you can fake TMDB in your own app's tests:

use Illuminate\Support\Facades\Http;

Http::fake([
    'api.themoviedb.org/3/movie/*' => Http::response(['id' => 27205, 'title' => 'Inception']),
]);

The package's own suite:

composer install
vendor/bin/phpunit

License

The MIT License (MIT). See LICENSE.md.