Overview & Architecture
A streaming portal has two moving parts: TMDB (The Movie Database) for metadata and VidCore for the actual video embed. TMDB gives you titles, posters, descriptions, ratings, and IDs. VidCore takes those IDs and renders a working video player. Neither requires a backend — both are callable from the browser.
TMDB — Metadata & Search
Free API that returns titles, posters, descriptions, ratings, cast, trailers, and IMDB/TMDB IDs. Requires a free API key. Used for search, trending lists, and building your media grid.
Free API KeyVidCore — Video Player
Embed API that takes an IMDB or TMDB ID and serves a fullscreen video player. Supports movies and TV shows (season + episode). No account needed — just construct the URL.
No AccountThe data flow for every page load looks like this:
User searches "Inception" ↓ TMDB /search/movie → returns results with id, title, poster_path ↓ User clicks a result (id = 27205) ↓ TMDB /movie/27205 → full details: runtime, genres, tagline, imdb_id ↓ Render detail page — build embed URL with the id ↓ VidCore iframe → https://vidcore.net/movie/27205?autoPlay=true
tt1234567) and TMDB integer IDs interchangeably in the {id} parameter. TMDB returns both in its API responses — use whichever is more convenient.
TMDB Setup & API Key
TMDB offers a free API with generous rate limits (40 requests per 10 seconds). You need an API key to authenticate requests.
Create a free TMDB account
Register at themoviedb.org/signup and verify your email address.
Request an API key
Go to your account settings → API → click "Create" under API Key (v3 auth). Select "Developer" for the use case and fill in the form. Your key is issued instantly.
Store the key safely
For a client-side site the key will be visible in source code — this is acceptable for read-only TMDB usage (it cannot modify data without a session token). Store it in a single config.js file so it's easy to rotate.
Test the key
Open this URL in your browser, replacing YOUR_KEY with your key. You should get a JSON object with configuration data.
https://api.themoviedb.org/3/configuration?api_key=YOUR_KEY
const TMDB_KEY = 'YOUR_TMDB_API_KEY'; const TMDB_BASE = 'https://api.themoviedb.org/3'; const IMG_BASE = 'https://image.tmdb.org/t/p/w500'; // poster images const IMG_BACK = 'https://image.tmdb.org/t/p/w1280'; // backdrop images
TMDB Endpoints Reference
These are the four TMDB endpoints you'll use in a streaming portal. All are GET requests to https://api.themoviedb.org/3 and require ?api_key=YOUR_KEY.
Search Movies
en-US).
Search TV Shows
Movie Details
tt6263850) — also accepted by VidCore.
https://image.tmdb.org/t/p/w500 for a full URL.
https://image.tmdb.org/t/p/w1280.
{id, name} objects.
Trending Movies & TV (for your home page)
movie, tv, or all — the type of content to return.
day or week — the trending window.
&append_to_response=videos,credits to a movie or TV details call to fetch trailers and cast in a single request, reducing your total API calls. Example: /movie/27205?api_key=KEY&append_to_response=videos,credits
Here's a reusable fetch helper that handles all four endpoints:
// tmdb.js — import TMDB_KEY and TMDB_BASE from config.js /** Search movies by title */ async function searchMovies(query, page = 1) { const url = `${TMDB_BASE}/search/movie?api_key=${TMDB_KEY}&query=${encodeURIComponent(query)}&page=${page}`; const res = await fetch(url); return res.json(); // { results: [...], total_pages, total_results } } /** Search TV shows by title */ async function searchTV(query, page = 1) { const url = `${TMDB_BASE}/search/tv?api_key=${TMDB_KEY}&query=${encodeURIComponent(query)}&page=${page}`; const res = await fetch(url); return res.json(); } /** Get full movie details (including imdb_id, genres, runtime) */ async function getMovie(id) { const url = `${TMDB_BASE}/movie/${id}?api_key=${TMDB_KEY}&append_to_response=videos,credits`; const res = await fetch(url); return res.json(); } /** Get full TV show details */ async function getTV(id) { const url = `${TMDB_BASE}/tv/${id}?api_key=${TMDB_KEY}&append_to_response=videos,credits`; const res = await fetch(url); return res.json(); } /** Trending this week — media_type: 'movie' | 'tv' | 'all' */ async function getTrending(mediaType = 'all', window = 'week') { const url = `${TMDB_BASE}/trending/${mediaType}/${window}?api_key=${TMDB_KEY}`; const res = await fetch(url); return res.json(); } /** Build a full poster URL from a TMDB poster_path */ function posterURL(path, size = 'w500') { return path ? `https://image.tmdb.org/t/p/${size}${path}` : 'assets/no-poster.webp'; }
VidCore — Movie Embeds
VidCore is the video player layer. For movies, the embed URL takes a single ID (IMDB or TMDB) and renders a fullscreen player with server selection, subtitle support, and Chromecast.
tt6263850) and TMDB integer ID (533535).
Use this URL as the src of an <iframe>. Here are the three example URLs from the VidCore docs:
# Basic usage with IMDB ID https://vidcore.net/movie/tt6263850?autoPlay=true # With custom theme color (hex, no #) https://vidcore.net/movie/533535?theme=16A085 # With autoplay and subtitles https://vidcore.net/movie/533535?autoPlay=true&sub=en
Building the embed URL dynamically from a TMDB result:
function buildMovieEmbed(id, options = {}) { const params = new URLSearchParams({ autoPlay: true, ...(options.theme && { theme: options.theme }), ...(options.sub && { sub: options.sub }), ...(options.server && { server: options.server }), }); return `https://vidcore.net/movie/${id}?${params}`; } function renderMoviePlayer(containerId, movieId) { const el = document.getElementById(containerId); el.innerHTML = ` <iframe src="${buildMovieEmbed(movieId)}" style="width:100%;height:100%;border:none;" allowfullscreen sandbox="allow-scripts allow-same-origin allow-forms allow-pointer-lock" ></iframe> `; } // Example usage after fetching from TMDB: const movie = await getMovie(533535); renderMoviePlayer('player-container', movie.id);
VidCore — TV Show Embeds
TV show embeds take three path parameters: the show ID, a season number, and an episode number. VidCore also supports auto-next episode functionality for binge-watching.
1, not 01.
TV-specific example URLs from the VidCore docs:
# Basic — Breaking Bad, Season 1, Episode 5 https://vidcore.net/tv/tt4052886/1/5?autoPlay=true # With auto-next episode button https://vidcore.net/tv/63174/1/5?nextButton=true&autoNext=true # With theme and autoplay https://vidcore.net/tv/63174/2/1?theme=16A085&autoPlay=true
A complete TV player builder, including TMDB season/episode data fetching:
function buildTVEmbed(id, season, episode, options = {}) { const params = new URLSearchParams({ autoPlay: true, nextButton: true, autoNext: true, ...(options.theme && { theme: options.theme }), ...(options.sub && { sub: options.sub }), }); return `https://vidcore.net/tv/${id}/${season}/${episode}?${params}`; } /** Fetch season details from TMDB (returns episode list) */ async function getSeason(showId, seasonNum) { const url = `${TMDB_BASE}/tv/${showId}/season/${seasonNum}?api_key=${TMDB_KEY}`; const res = await fetch(url); return res.json(); // { episodes: [{episode_number, name, overview, still_path}] } } // Usage: load Season 1 Episode 1 of show with TMDB id 63174 const season = await getSeason(63174, 1); const ep = season.episodes[0]; // first episode document.getElementById('player').src = buildTVEmbed(63174, 1, ep.episode_number);
VidCore Optional Parameters
Both the movie and TV embed URLs support these optional query string parameters. Mix and match as needed.
Controls whether the media title is displayed in the player. Pass false to hide it.
Determines if the poster image is shown before playback begins.
Controls whether the media starts playing automatically on load. Set true for seamless UX.
Starts the video at a specified time in seconds. Useful for resuming from a saved position.
Changes the player's accent color. Pass a hex code without the # (e.g. theme=16A085).
Changes the default server for the player. Set to a server name to pre-select it for your users.
Controls whether the server selector button is shown or hidden from the player UI.
Controls whether the fullscreen button is shown or hidden. Set false if you manage fullscreen yourself.
Controls whether the Chromecast button is shown or hidden in the player controls.
Sets the default subtitle language (e.g. en, es, fr). User can change it in the player.
Displays the "Next Episode" button when 90% of the current episode has been watched.
Automatically loads the next episode when the current one ends. Requires nextButton=true.
A fully configured embed URL combining several parameters:
# Movie — themed, English subs, autoplay, server selector hidden https://vidcore.net/movie/533535?autoPlay=true&theme=c0392b&sub=en&hideServer=true # TV show — auto-next, themed, French subs https://vidcore.net/tv/63174/3/1?autoPlay=true&nextButton=true&autoNext=true&theme=8e44ad&sub=fr
Putting It All Together
Here's a complete minimal streaming portal: a search page that queries TMDB and renders a detail modal with a VidCore embed. Copy this as a starting point and style it to match your site.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>StreamSite</title> <script src="config.js"></script> <!-- TMDB_KEY, TMDB_BASE, IMG_BASE --> </head> <body> <input id="q" placeholder="Search movies..." oninput="search(this.value)"> <div id="results"></div> <!-- Modal for fullscreen player --> <div id="modal" style="display:none;position:fixed;inset:0;background:#000;z-index:999"> <button onclick="closeModal()" style="position:absolute;top:12px;right:16px;z-index:1000;...">✕</button> <iframe id="player" style="width:100%;height:100%;border:none" allowfullscreen sandbox="allow-scripts allow-same-origin allow-pointer-lock allow-forms" ></iframe> </div> <script> let timer; // Debounced search function search(q) { clearTimeout(timer); if (q.trim().length < 2) return; timer = setTimeout(async () => { const url = `${TMDB_BASE}/search/movie?api_key=${TMDB_KEY}&query=${encodeURIComponent(q)}`; const data = await (await fetch(url)).json(); renderResults(data.results); }, 400); } function renderResults(movies) { document.getElementById('results').innerHTML = movies.map(m => ` <div class="card" onclick="openMovie(${m.id})"> <img src="${IMG_BASE}${m.poster_path}" loading="lazy" alt="${m.title}"> <h3>${m.title}</h3> <p>${m.release_date?.slice(0,4) || ''} · ⭐ ${m.vote_average?.toFixed(1)}</p> </div> `).join(''); } function openMovie(id) { const src = `https://vidcore.net/movie/${id}?autoPlay=true`; document.getElementById('player').src = src; document.getElementById('modal').style.display = 'block'; } function closeModal() { document.getElementById('player').src = ''; // stop playback document.getElementById('modal').style.display = 'none'; } // Load trending movies on page load (async () => { const url = `${TMDB_BASE}/trending/movie/week?api_key=${TMDB_KEY}`; const data = await (await fetch(url)).json(); renderResults(data.results); })(); </script> </body> </html>
player.src = '' when closing the modal stops audio/video from playing in the background. If you just hide the modal with CSS without clearing the src, the video keeps playing.
To support TV shows alongside movies, add a type toggle (Movies / TV) to your search UI and switch between /search/movie and /search/tv. For TV results, open a season/episode picker that calls /tv/{id}/season/{n} before building the VidCore URL.
Map to avoid hitting the limit on repeat searches.
Next Steps
You now have a fully functional streaming portal. Here's where to go from here:
Add background music
Let users play music while they browse. The Music guide shows how to add a persistent audio player with zero server cost.
Music GuideAdd a virtual browser tab
Embed a Hyperbeam virtual machine so users can browse the full web right from your site — the ultimate streaming companion.
VMs GuideDeploy to Cloudflare
Push your site live with DDoS protection, CDN caching, and analytics all configured in under 15 minutes.
Deploy GuideAdd user accounts
Let users save watchlists and continue where they left off. The Accounts guide covers the full auth + storage setup.
Accounts Guide