tightenco/simplecast
A Laravel wrapper for the Simplecast podcast hosting API, giving you Eloquent-like models and collections for podcasts, episodes, and analytics. An expressive facade and simple configuration make it easy to pull Simplecast data into any PHP or Laravel application.
The SDK is intentionally thin: one client class, one API key, methods that map to Simplecast API resources. Treat it as a typed-ish HTTP wrapper rather than a framework.
Install and wire it up
composer require tightenco/simplecast
Get an API key from the Simplecast dashboard (Settings → API), then keep it out of source:
SIMPLECAST_API_KEY=your-key-here
First use case: render a podcast page with episodes
use Simplecast\Simplecast;
$simplecast = new Simplecast(config('services.simplecast.key'));
$podcast = $simplecast->podcast($podcastId);
$episodes = $simplecast->episodes($podcastId);
In Laravel, register it once in AppServiceProvider so you reuse the underlying HTTP client:
$this->app->singleton(Simplecast::class, fn () =>
new Simplecast(config('services.simplecast.key'))
);
Where to look first
src/Simplecast.php — the SDK is small enough that the class is the documentation; read the method signatures before assuming endpoint coverage.Start with a read-only scenario — fetch podcast metadata and the episode list — before touching write operations.
Wrap the SDK behind your own service. Don't inject Simplecast directly into controllers. A PodcastService that returns arrays or your own DTOs lets you swap the SDK, add caching, and mock it cleanly:
class PodcastService
{
public function __construct(private Simplecast $simplecast) {}
public function podcast(string $id): array
{
return Cache::remember("simplecast.podcast.$id", now()->addHour(), fn () =>
$this->simplecast->podcast($id)
);
}
public function episodes(string $podcastId): array
{
return Cache::remember("simplecast.episodes.$podcastId", now()->addMinutes(30), fn () =>
$this->simplecast->episodes($podcastId)
);
}
}
Sync to a local table for anything nontrivial. Episode data changes on a schedule you don't control. A scheduled command that upserts into an episodes table gives you search, relationships, ordering, and resilience against API downtime:
// app/Console/Commands/SyncEpisodes.php
public function handle(PodcastService $podcasts): void
{
foreach ($podcasts->episodes($this->podcastId) as $episode) {
Episode::updateOrCreate(
['simplecast_id' => $episode['id']],
[
'title' => $episode['title'],
'published_at'=> $episode['published_at'],
'description' => $episode['description'],
'audio_url' => $episode['enclosure_url'] ?? null,
],
);
}
}
Then schedule it hourly and let the front end read only from the database.
Controller usage via dependency injection:
public function show(PodcastService $podcasts)
{
return view('podcast.show', [
'podcast' => $podcasts->podcast($this->podcastId),
'episodes' => $podcasts->episodes($this->podcastId),
]);
}
Testing. Never hit the live API in tests. Bind a fake in the container:
$this->mock(Simplecast::class, function ($mock) {
$mock->shouldReceive('episodes')->andReturn([['id' => 'abc', 'title' => 'Pilot']]);
});
Write operations (if you use them). Create/update/delete calls return the created resource; capture the ID and persist it locally immediately so you're not re-fetching to reconcile.
No Laravel auto-discovery. There is no service provider, facade, or config file shipped. You must bind it yourself. Forgetting this and calling new Simplecast(...) inline in several places means several HTTP clients and several places to update config.
Not everything is covered. The SDK is a convenience wrapper, not a complete client. If a Simplecast endpoint is missing, drop to raw Guzzle/HTTP against the same base URL and bearer token rather than forking the package. Check src/Simplecast.php before assuming a method exists — the API changes more often than the SDK does.
Responses are associative arrays, not objects. Access with $episode['id'], not $episode->id, and don't assume keys exist — guard optional fields like enclosure_url with ??. Shapes vary between endpoints.
IDs are opaque strings. Casting them to integers or assuming numeric IDs will break. Store as strings (often UUID-ish), use string columns in your migrations.
Errors throw. Non-2xx responses raise exceptions, not null returns. Catch around user-triggered calls:
try {
$episode = $this->simplecast->episode($episodeId);
} catch (Throwable $e) {
report($e);
abort(404);
}
404 for missing episodes and 429 for rate limiting are the two you'll actually hit. Handle 429 with caching plus an exponential backoff retry rather than hammering the API.
Paginate deliberately. Episode collections come back paginated. Check the default page size and the pagination structure in the response — a podcast with hundreds of episodes is the classic case of silently truncating your list. Loop or request explicit pages.
Cache aggressively but pick the TTL by endpoint. Podcast metadata changes rarely (hour-plus); the newest episode can appear at any publish time (minutes). If you need "show the newest episode within five minutes," cache the podcast object longer than the episode list.
Configure timeouts and retries. The SDK wraps an HTTP client; a hung Simplecast call should not hang your web request. If the client isn't injectable, keep SDK calls off the request path entirely — that's the strongest argument for the sync-to-database pattern.
Keep the key server-side only. Never expose it to the browser; if you need client-side episode rendering, proxy through a Laravel route or read from your synced table.
Watch for breaking API drift. Because the SDK is thin and rarely updated, a Simplecast API change can surface as a missing array key rather than an exception. The local-table sync pattern insulates you: validate on ingest and log when expected keys are absent.
How can I help you explore Laravel packages today?