Build powerful extensions for the Teevi platform
- Overview
- Quick Start
- Installation
- Extension Development
- Build Configuration
- The Manifest File
- User Inputs
- Runtime Environment
- Building and Distribution
- Best Practices
- Examples
- FAQ
Teevi extensions allow you to integrate content from various sources into the Teevi platform. This guide provides comprehensive instructions for creating, testing, and distributing Teevi extensions.
An extension consists of:
- Source code: TypeScript implementation of the extension interface
- Manifest file: JSON metadata describing your extension (auto-generated)
- Icon: Visual representation of your extension
Create a new Teevi extension in 5 minutes:
# Create a new directory
mkdir my-teevi-extension && cd my-teevi-extension
# Initialize project
npm init -y
npm install --save @teeviapp/core
npm install --save-dev typescript @teeviapp/vite vite
# Create TypeScript config
npx tsc --init
# Create source directory
mkdir -p src publicCreate a basic extension (src/index.ts):
import type { TeeviVideoExtension } from "@teeviapp/core"
export default {
fetchShowsByQuery: async (query) => {
console.log(`Searching for: ${query}`)
return [
{
kind: "movie",
id: "example-id",
title: "Example Movie",
year: 2023,
},
]
},
fetchShow: async (id) => {
return {
kind: "movie",
id,
title: "Example Movie",
overview: "This is an example movie",
releaseDate: "2023-05-25",
duration: 7200,
genres: ["Example"],
}
},
fetchEpisodes: async (id) => {
return []
},
fetchVideoAssets: async (id) => {
return [
{
url: "https://example.com/video.mp4",
},
]
},
} satisfies TeeviVideoExtensionCreate Vite config (vite.config.ts):
import { defineConfig } from "vite"
import teevi from "@teeviapp/vite"
export default defineConfig({
plugins: [
teevi({
name: "My First Extension",
capabilities: ["metadata", "video"],
}),
],
})Add an icon to public/icon.png, then build your extension:
npx vite buildYour extension is now ready in the dist directory!
To start developing a Teevi extension, you'll need to set up your development environment:
# Initialize a new project
npm init -y
# Install dependencies
npm install --save @teeviapp/core
npm install --save-dev typescript @teeviapp/vite vite
# Create TypeScript configuration
npx tsc --initA basic tsconfig.json for Teevi extensions:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}Teevi extensions are built on these key concepts:
| Concept | Description |
|---|---|
| Extension | A TypeScript module that exports an object implementing the Teevi extension interface |
| Capabilities | Functionality your extension provides (metadata, video, feed) |
| Manifest | JSON metadata describing your extension and its capabilities |
| User Inputs | Configuration values that users provide to customize your extension |
Teevi extensions must export a default object that implements one of the extension interfaces:
// src/index.ts
import type { TeeviVideoExtension } from "@teeviapp/core"
// Extensions must be exported as a default object
export default {
// Required methods based on your extension's capabilities
// ...
} satisfies TeeviVideoExtensionTeevi extensions can implement one or more capabilities:
Provides basic show information and search functionality:
import type { TeeviMetadataExtension } from "@teeviapp/core"
export default {
fetchShowsByQuery: async (query) => {
// Implement search functionality
return []
},
fetchShow: async (id) => {
// Implement show details retrieval
// ...
},
fetchEpisodes: async (showId, season) => {
// Implement episode listing
// ...
},
} satisfies TeeviMetadataExtensionAdds video playback sources to your extension:
import type { TeeviVideoExtension } from "@teeviapp/core"
export default {
// Include metadata capabilities...
fetchVideoAssets: async (mediaId) => {
// Implement video source retrieval
// ...
},
} satisfies TeeviVideoExtensionProvides content collections and recommendations:
import type { TeeviFeedExtension } from "@teeviapp/core"
export default {
fetchFeedCollections: async () => {
// Return collections of content
return [
{
id: "recommended",
title: "Recommended For You",
shows: [
/* list of TeeviShowEntry objects */
],
},
]
},
} satisfies TeeviFeedExtensionTeevi provides a Vite plugin that simplifies the build process for extensions.
Create a vite.config.ts file in your project root:
import { defineConfig } from "vite"
import teevi from "@teeviapp/vite"
export default defineConfig({
plugins: [
teevi({
// Display name of your extension
name: "My Extension",
// Capabilities your extension provides
capabilities: ["metadata", "video", "feed"],
// Optional configuration inputs for your extension
inputs: [
{
id: "api_key",
name: "API Key",
required: true,
},
{
id: "adult_content",
name: "Allow Adult Content",
required: false,
},
],
// Directory containing assets (default: "public")
assetsDir: "public",
// Name of icon file in assets directory (default: "icon.png")
iconResourceName: "icon.png",
}),
],
})The manifest describes your extension to the Teevi platform. The Vite plugin generates it automatically during the build process.
Example manifest:
{
"id": "your-extension-name", // from package.json name
"name": "My Extension", // from teevi plugin configuration
"version": "1.0.0", // from package.json version
"description": "Description of the app", // from package.json description
"author": "Your Name", // from package.json author
"homepage": "https://example.com", // from package.json homepage
"hash": "sha256-hash-of-bundle", // generated from the bundle
"capabilities": ["metadata", "video"], // from teevi plugin configuration
"iconResourceName": "icon.png", // from teevi plugin configuration
"inputs": [
// from teevi plugin configuration
{
"id": "api_key",
"name": "API Key",
"required": true
}
]
}Your extension should include an icon in the assets directory:
- Default path:
public/icon.png - Recommended size: 512×512 pixels
Teevi extensions can request configuration values from users:
// In vite.config.ts
teevi({
// ...
inputs: [
{
id: "api_key", // Unique identifier
name: "API Key", // Display name
required: true, // Whether input is required
},
],
})Access these values in your extension code:
export default {
fetchShowsByQuery: async (query) => {
const apiKey = Teevi.getInputValueById("api_key")
if (!apiKey) {
throw new Error("API KEY is required")
}
// Use apiKey in your API requests
const response = await fetch(
`https://api.example.com/search?q=${query}&api_key=${apiKey}`
)
const data = await response.json()
// ...
},
// ...
}Teevi provides a specialized runtime environment for extensions:
Teevi includes these Web APIs natively:
- ✅
fetch: For making network requests - ✅
URLandURLSearchParams: For URL manipulation - ✅
localStorage: For persistent storage - ❌ Other Web APIs: Not available by default
For additional Web APIs, use polyfills from libraries like
core-js
A global Teevi object provides access to platform features and settings:
// Access runtime information
const language = Teevi.language // Current language (e.g., "en", "it")
const userAgent = Teevi.userAgent // User agent information
// Get user-provided configuration
const apiKey = Teevi.getInputValueById("api_key")Build your extension with Vite:
# Add build script to package.json
"scripts": {
"build": "vite build"
}
# Build the extension
npm run buildThis creates three files in the dist directory:
main.js: The bundled extension codemanifest.json: Extension metadataicon.png: Extension icon (copied from assets directory)
To distribute your extension:
- Build the extension (
npm run build) - Deploy the contents of the
distdirectory to a static hosting platform (e.g., GitHub Pages or as a Releases artifact, Netlify, or Vercel) - Share the published URL so users can directly access the extension online
Teevi supports deep linking, enabling users to directly install extensions from a single URL. This feature simplifies sharing and onboarding.
Construct a URL like teeviapp://extensions?install={YOUR_DIRECTORY_URL}
- Use TypeScript strictly: Enable
strict: truein yourtsconfig.json - Export as default object: Always export your extension as a default object
- Use async/await: For cleaner asynchronous code
- Implement caching: Cache API responses to reduce network requests
- Optimize bundles: Keep dependencies minimal
- Lazy load resources: Load resources only when needed
- Support internationalization: Use
Teevi.languageto provide localized content - Handle edge cases: Account for missing data or failed requests
- Provide meaningful metadata: Include accurate descriptions, genres, etc.
- Implement search correctly: Ensure search is fast and returns relevant results
You can explore these official extensions to see real-world implementations:
- TMDB Extension: Uses The Movie Database API to provide movie and TV show metadata
- Jellyfin Extension: Integrates with Jellyfin media servers
// src/index.ts
import type { TeeviVideoExtension } from "@teeviapp/core"
export default {
fetchShowsByQuery: async (query) => {
const apiKey = Teevi.getInputValueById("api_key")
const response = await fetch(
`https://api.themoviedb.org/3/search/movie?api_key=${apiKey}&query=${encodeURIComponent(
query
)}`
)
if (!response.ok) return []
const data = await response.json()
return data.results.map((movie) => ({
kind: "movie",
id: String(movie.id),
title: movie.title,
posterURL: movie.poster_path
? `https://image.tmdb.org/t/p/w500${movie.poster_path}`
: undefined,
year: movie.release_date
? new Date(movie.release_date).getFullYear()
: undefined,
}))
},
fetchShow: async (id) => {
const apiKey = Teevi.getInputValueById("api_key")
const response = await fetch(
`https://api.themoviedb.org/3/movie/${id}?api_key=${apiKey}`
)
if (!response.ok)
throw new Error(`Failed to fetch movie: ${response.status}`)
const movie = await response.json()
return {
kind: "movie",
id: String(movie.id),
title: movie.title,
posterURL: movie.poster_path
? `https://image.tmdb.org/t/p/w500${movie.poster_path}`
: undefined,
backdropURL: movie.backdrop_path
? `https://image.tmdb.org/t/p/original${movie.backdrop_path}`
: undefined,
overview: movie.overview,
releaseDate: movie.release_date,
duration: movie.runtime * 60, // Convert minutes to seconds
genres: movie.genres.map((g) => g.name),
}
},
fetchEpisodes: async () => {
return [] // Movies don't have episodes
},
fetchVideoAssets: async (id) => {
const apiKey = Teevi.getInputValueById("api_key")
const response = await fetch(
`https://api.themoviedb.org/3/movie/${id}/videos?api_key=${apiKey}`
)
if (!response.ok) return []
const data = await response.json()
// Example: find YouTube trailer
const trailer = data.results.find(
(video) =>
video.site === "YouTube" &&
(video.type === "Trailer" || video.type === "Teaser")
)
if (!trailer) return []
return [
{
url: `https://www.youtube.com/watch?v=${trailer.key}`,
},
]
},
} satisfies TeeviVideoExtensionQ: Can I use third-party libraries in my extension?
A: Yes, you can use any npm package as long as it's compatible with the Teevi runtime environment.
Q: How do I handle authentication for external APIs?
A: Use the inputs feature to securely collect API keys from users.
Q: Can my extension have a UI?
A: No, extensions only provide data. The Teevi app handles all UI rendering.
Q: What's the maximum bundle size?
A: While there's no hard limit, we recommend keeping your bundle under 500KB for optimal performance.
- GitHub: Report issues