Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.discovr.media/llms.txt

Use this file to discover all available pages before exploring further.

When your user opens a Discovr-powered app, they sign in, pick a profile, and browse content. This guide walks through the canonical navigation model so your app feels native and predictable. The core pattern is simple: start with a profile selector, then browse tabbed pages (Home, Movies, Series). Tapping a navigation row (like “Romance” or “Animation”) drills into a new page with that filter applied—giving you a natural breadcrumb stack. Discovr enforces a three-level depth limit to prevent navigation so deep users can’t find their way back.
This guide assumes an active session and profile. If you haven’t signed in a user yet, start with Sessions, Profiles & Sign Out. Code snippets use TypeScript, Kotlin, and Swift tabs—select your language to follow along.

Profile Select → Home

After sign-in, your user sees a profile picker. This is your chance to load the user’s profiles and let them choose one.
1

Fetch available profiles

Call getProfiles() to get the user’s profiles.
const { profiles } = await discovr.getProfiles();
console.log("Available profiles:", profiles.map(p => p.name));
2

Select a profile

When the user picks a profile, call selectProfile(). This starts a profile session that all page and row fetches use.
const profileId = profiles[0].id; // user's choice
await discovr.selectProfile(profileId);
console.log("Profile selected. Session active.");
3

Load and display the Home page

Once a profile is selected, fetch the Home page (with no filters) and its rows. The user sees the hero carousel and first rows immediately.
const homePage = await discovr.createPage({ name: "Home" });
const pageData = await discovr.getPage(homePage.id);
console.log("Hero items:", pageData.hero.length);

Home/Movies/Series Tabs

Once on Home, users typically see top-level tabs (Home, Movies, Series) that let them browse by media type. Each tab is a separate page with different filters. Create each page once and reuse its ID. Switching tabs doesn’t recreate the page—it just swaps the visible rows.
// Create pages for each tab once, on app launch or after sign-in
const tabs = {
	home: await discovr.createPage({ name: "Home" }),
	movies: await discovr.createPage({
		pageFilters: { media_type: "movie" },
		name: "Movies",
	}),
	series: await discovr.createPage({
		pageFilters: { media_type: "tv" },
		name: "Series",
	}),
};

// Later: user taps "Movies" tab
const moviesData = await discovr.getPage(tabs.movies.id);
renderPage(moviesData); // Display Movies tab rows
Each tab is a distinct page with its own snapshot. This means Home and Movies never interfere—they paginate independently, and switching tabs doesn’t lose your place in the other one.

Drill-Down with Navigation Rows

Some rows are navigation-type rows (not media). Instead of displaying movies, they display drill-down destinations: “Romance”, “Animation”, “Trending”, etc. When the user taps one, you push its pre-created page ID onto your navigation stack and load it—no page creation needed.
1

Identify navigation rows

Rows come in types. Navigation rows have type: "navigation". When you fetch rows, check the type.
const rows = await discovr.getRows(homePageId);
rows.forEach(row => {
	if (row.type === "navigation") {
		console.log(`Navigation row: ${row.title}`);
	}
});
2

Fetch items in the navigation row

Call getRowItems() to get the destinations in that row (e.g., genre names). Each navigation item already carries a pageId.
const navRow = rows.find(r => r.type === "navigation");
const items = await discovr.getRowItems(homePageId, navRow.id);
items.forEach(item => {
	console.log(`Destination: ${item.label} → page ${item.pageId}`);
});
3

Push the page ID when the user taps a destination

Navigation items already link to their destination page. When tapped, push the item’s pageId onto your stack and load it.
// User taps "Romance" in the navigation row
const romanceItem = items.find(i => i.label === "Romance");

// Push the pre-existing page ID onto your navigation stack
navigationStack.push(romanceItem.pageId);
const pageData = await discovr.getPage(romanceItem.pageId);
renderPage(pageData);
Each page’s title field is the breadcrumb for that level—“Home”, “Movies”, “Anime”, “Romance”. The API returns it directly from getPage(), so you don’t need to maintain a separate label list. To build the full breadcrumb trail (“Home > Movies > Anime”), map your navigation stack (page IDs you’ve already loaded) to their titles:
// navigationStack: [{ id: "...", title: "Home" }, { id: "...", title: "Movies" }, ...]
const breadcrumbTrail = navigationStack.map(p => p.title).join(" > ");
// "Home > Movies > Anime"
When you push a page onto the stack, store its title alongside its ID so you can render the trail without extra fetches:
// On tap:
const pageData = await discovr.getPage(romanceItem.pageId);
navigationStack.push({ id: romanceItem.pageId, title: pageData.title });

Back Behavior

When the user taps back, pop the current page ID from your stack and redisplay the previous one. The previous page’s snapshot is still cached in Discovr, so you get the same results without refetching.
function goBack() {
	if (navigationStack.length > 1) {
		navigationStack.pop(); // Remove current page
		const previousPageId = navigationStack[navigationStack.length - 1];
		const pageData = await discovr.getPage(previousPageId);
		renderPage(pageData); // Show the previous page (same snapshot)
	}
}

Three-Level Depth Limit

Discovr enforces a maximum of three nesting levels (not counting the root Home). Think of it as:
  • Level 1: Home / Movies / Series (root pages)
  • Level 2: Romance / Animation (drill from Level 1)
  • Level 3: Trending Romance (drill from Level 2)
Trying to drill deeper is not supported. Design your UX so navigation destinations don’t nest more than three levels.
If you try to create a page at depth 4+, the operation may fail or behave unexpectedly. Always cap your navigation stack at 3 levels and prevent the user from drilling further.
function canDrillDeeper(): boolean {
	// Home=1, Movies=2, Romance=3. Depth limit is 3.
	return navigationStack.length < 3;
}

// Before creating a new page on navigation tap:
if (canDrillDeeper()) {
	const newPage = await discovr.createPage(pageFilters);
	navigationStack.push(newPage.id);
} else {
	showToast("Cannot drill deeper. Try another category.");
}

Continue Watching on Home

Most apps show a “Continue Watching” row between the hero and recommendation rows on the Home page. This row comes from profile context, not from the page itself. Fetch it separately and insert it into your Home UI:
const [homePageData, continueWatching] = await Promise.all([
	discovr.getPage(homePageId),
	discovr.getProfileContextLists().then(lists => lists.playback), // Continue Watching = playback list
]);

// In your UI:
// 1. Render hero from homePageData.hero
// 2. Render continueWatching row
// 3. Render homePageData.rows below
See Profile Context for full details on Continue Watching, watchlists, and other user state.

Putting It Together

Here’s a skeleton of how navigation typically flows in a Discovr app:
  1. User signs in
  2. Profile picker shows available profiles
  3. User selects a profile → Home loads with hero + rows
  4. Tabs (Home, Movies, Series) each have their own page ID
  5. User taps a navigation row item (e.g., “Romance”) → new page is created and pushed onto the stack
  6. Breadcrumb updates (Home > Movies > Romance)
  7. User taps back → pop the stack, redisplay the previous page
  8. Continue Watching appears between hero and rows on Home
Session expiration can disrupt navigation. If the user browses for >30 minutes and taps an old page, you’ll get a SESSION_EXPIRED error. See Session management for how to recover gracefully.

Next: Session management