Skip to content

Getting Started

A small Typescript SDK for searching anime and manga, listing episodes/chapters, and resolving direct stream/page URLs (with subtitle tracks). Nine providers, a handful of reusable embed extractors, a pluggable HTTP transport, and an optional HTTP server with a stream/subtitle proxy and a bring-your-own cache hook.

  • Node 20+ or Bun: native fetch and crypto.subtle are required
  • ffmpeg: only needed to run the live E2E test suite for anime streams
Terminal window
npm install anime-sdk
# -- or --
pnpm add anime-sdk
# -- or --
bun add anime-sdk

Every provider exposes the same three methods: searchfetchContentUnitsresolveStream.

import { HttpClient, GogoanimeProvider, MangadexProvider } from 'anime-sdk';
const client = new HttpClient({ timeoutMs: 10_000 });
// Anime example
const anime = new GogoanimeProvider(client);
const shows = await anime.search('Frieren');
const eps = await anime.fetchContentUnits(shows[0].id);
const stream = await anime.resolveStream(eps[0].id, 'sub');
// Manga example
const manga = new MangadexProvider(client);
const books = await manga.search('Frieren');
const chapters = await manga.fetchContentUnits(books[0].id);
const pages = await manga.resolveStream(chapters[0].id);

resolveStream returns ResolvedMediaStream: a discriminated union on type:

type ResolvedMediaStream =
| { type: 'video'; streams: IVideoPayload[] }
| { type: 'manga'; pages: IMangaPayload };

Manga providers return type: 'manga', while anime providers return type: 'video'. Always check result.type before accessing the payload.

Each IMangaPayload carries:

interface IMangaPayload {
imageUrls: string[]; // high-resolution image URLs for each page
headers?: Record<string, string>; // Referer required by some sites
}

Each IVideoPayload in the streams array has:

interface IVideoPayload {
sourceUrl: string; // direct URL or HLS manifest
isHLS: boolean; // true → feed to an HLS player
quality: '1080p' | '720p' | '480p' | '360p' | 'auto';
language?: ContentLanguage;
headers?: Record<string, string>; // Referer/User-Agent required by some CDNs
subtitles?: ISubtitleTrack[]; // external VTT tracks, when the provider has them
}
interface ISubtitleTrack {
url: string;
language: string; // BCP-47 (e.g. 'en', 'pt-BR')
label: string; // human-readable ("English", "Português")
format?: 'vtt' | 'srt' | 'ass';
}

fetchContentUnits returns one unified episode list: each IContentUnit advertises its translations via availableLanguages: ContentLanguage[]. The translation is picked at resolveStream time:

type ContentLanguage = 'sub' | 'dub' | 'raw';
const eps = await provider.fetchContentUnits(shows[0].id);
const ep = eps[0]; // ep.availableLanguages tells you what's playable
const result = await provider.resolveStream(ep.id, 'dub');

Providers that don’t support the requested language fall back to 'sub'.

Inspecting tracks without resolving the stream

Section titled “Inspecting tracks without resolving the stream”

Some providers expose a cheap metadata endpoint so a UI can show the subtitle/quality selector before playback. Implement (or call) fetchUnitTracks when available:

if (provider.fetchUnitTracks) {
const { subtitles, qualities } = await provider.fetchUnitTracks(ep.id, 'sub');
// subtitles: ISubtitleTrack[], qualities: IVideoPayload['quality'][]
}

Providers without a cheap path leave the method undefined: read IVideoPayload.subtitles straight off the resolved stream instead.

Pass sourceUrl directly to any HLS player or downloader. Many CDNs require the headers object to be forwarded:

hls.js
const hls = new Hls();
hls.loadSource(stream.sourceUrl);
// hls.js doesn't support custom headers on fetch: serve through your HTTP server instead
// ffmpeg
// ffmpeg -i "url" -headers "Referer: ..." output.mp4
// React Native / Video
<Video source={{ uri: stream.sourceUrl, headers: stream.headers }} />

The SDK includes built-in download utilities. HLS anime streams are downloaded by fetching segments and muxing with ffmpeg; direct MP4s stream straight to disk. Manga chapters are packaged as .zip archives.

import { downloadVideo, downloadMangaChapter } from 'anime-sdk';
// Anime: resolveStream → downloadVideo
const animeResult = await animeProvider.resolveStream(eps[0].id, 'sub');
if (animeResult.type === 'video') {
const { outputPath, fileSize } = await downloadVideo(animeResult.streams, './episode-1.mp4', {
onProgress: ({ phase, detail }) => console.log(`[${phase}] ${detail ?? ''}`),
});
console.log(`Saved ${fileSize} bytes → ${outputPath}`);
}
// Manga: resolveStream → downloadMangaChapter
const mangaResult = await mangaProvider.resolveStream(chapters[0].id);
if (mangaResult.type === 'manga') {
const { pageCount, outputPath } = await downloadMangaChapter(mangaResult.pages, './ch-1.zip', {
onProgress: ({ downloaded, total }) => console.log(`${downloaded}/${total}`),
});
console.log(`Packed ${pageCount} pages → ${outputPath}`);
}

Pass the full streams array to downloadVideo and it tries each candidate in order, falling back automatically on failure. See Downloads for the full API including batch downloads and low-level HLS helpers.

The three calls are identical across providers. Swap the import and constructor, nothing else changes:

import { AllmangaProvider } from 'anime-sdk';
const provider = new AllmangaProvider(new HttpClient());
// same: search → fetchContentUnits → resolveStream