Podscope is a self-hosted podcast membership platform built on Laravel. Private RSS feeds, Stripe-powered subscriptions, Cloudflare Stream video, and full ownership of your content and subscriber data.
No platform lock-in. No revenue share. No monthly SaaS bill. Just your podcast, your way.
Every paying member gets a unique feed URL that works with every podcast app. Cancel a subscription and the feed stops -- automatically, no manual action required.
Create show-specific plans or an all-shows tier. Monthly recurring billing, automatic renewal, and a grace period on cancellation. All handled by Stripe.
Upload episode videos directly from the admin panel. Signed playback tokens keep videos locked to paid members -- no public URL, no workarounds.
Full namespace support: chapters, transcripts, persons, funding, soundbites, GUID, locked, medium, and more. Your feed works with every modern podcast app.
Store audio files and artwork on local disk or Backblaze B2 cloud storage. Switch between backends from settings -- no code changes, no file migrations.
Let paid episodes breathe. Set a preview period so new episodes are publicly available for a configurable number of days before the paywall kicks in.
OP3 prefix on public feeds for directory-level analytics. Internal play tracking on private feeds. A dedicated stats dashboard shows plays by episode, show, and type.
Manage shows, episodes, members, plans, pages, media, and settings from a clean, modern admin built on Filament 5. No clunky legacy CMS interface.
Podscope is a standard Laravel application. If you can run a PHP site, you can run Podscope.
Clone the repo, run composer install, configure your .env file, and run migrations. Works on any server with PHP 8.4 and a queue worker.
Add your Stripe keys, connect Backblaze B2 or use local storage, set up Mailgun for notifications, and configure Cloudflare Stream if you want video.
Create your show, upload episodes, set up membership plans, and submit your RSS feed to Apple Podcasts and Spotify. Your members subscribe and get immediate access.
Podscope runs on the same stack as any modern Laravel application. No exotic dependencies.
A complete install from a fresh server. Full documentation covers CloudPanel, Nginx, and Supervisor configuration.
Podscope is free to self-host. No monthly platform fee, no revenue share, no third-party holding your subscriber list hostage.
Podscope is under active development. New features ship regularly.
ImportEpisodeJob discarded the return value of writeStream() while both disks are configured throw=>false, so a failed upload returned false instead of raising and the job saved the episode anyway; file_size and duration were recorded from the temp file, so the DOWNLOAD had succeeded and only the up
mutateFormDataBeforeFill blanks file_path for transcripts not on the public disk (FileUpload can only render public-disk files) and the restore matched the record by ARRAY POSITION, guarded by an entry-count check. Adding or removing a row changed the count, the restore was skipped entirely, and the
the two delete paths were mirror images that each leaked exactly what the other cleaned. Episode::deleting removed transcripts + the CF Stream video but never audio_path/artwork_path; Show::deleting removed audio + artwork by hand, and because episodes.show_id is cascadeOnDelete the episode rows wen
:deleting never removes audio/artwork objects, so a DB sum under-reports exactly the files worth deleting. 6h cache, 5m negative cache on failure, 250k-object cap; breakdown by file type, by show, and unreferenced files, plus a Rescan action. Settings > Storage gained storage_cost_per_gb_month (defa
during the window the episode is invisible everywhere (single isPublished() gate -- site, both feeds, search, play endpoints), the edit form shows the go-live time, and a confirming "Publish Now" action skips the wait. Asymmetric publish-now detection (15 min past / 60 s future) so a form left open
Filament SPA mode (navigation now Livewire-swaps instead of full page reloads -- the dominant cost was re-downloading/parsing panel assets per click); prod-side route:cache, view:cache, filament:optimize (component + Blade icon caches -- none existed); FPM pool ondemand->dynamic with warm spares and
Filament compose (RichEditor, images to B2), test-send-to-me, confirmed send with live recipient count, atomic no-double-send; public subscribe forms (Turnstile, fails closed -- verified live) on episode/show/archive pages when a show's newsletter_enabled toggle is on; verification emails with token
extract-transcripts run on prod: 21/21 episodes indexed. Found and fixed a live data-loss bug during verification: EditEpisode nulled B2 transcript file_paths on form fill and never restored them on save, so any edit-save of a B2-transcript episode silently severed its transcript -- episodes 152 and