bjthecod3r / laravel-tmdb
A fluent, fully-typed Laravel wrapper for The Movie Database (TMDB) API.
Requires
- php: ^8.2
- illuminate/contracts: ^11.0 || ^12.0 || ^13.0
- illuminate/http: ^11.0 || ^12.0 || ^13.0
- illuminate/support: ^11.0 || ^12.0 || ^13.0
- nesbot/carbon: ^2.72 || ^3.0
Requires (Dev)
- orchestra/testbench: ^9.0 || ^10.0 || ^11.0
- phpunit/phpunit: ^10.5 || ^11.0 || ^12.0
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 (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.
