Skip to content

Jellyfin API Client

A modern, fetch-based Jellyfin API client with full TypeScript types and zero dependencies. Works in Node.js, browsers, and edge runtimes. It powers Aurora and the other Coral modules.

Terminal window
pnpm add @get-coral/jellyfin
# or
npm install @get-coral/jellyfin
import { createClient, getLibraryItems, fromJellyfin } from '@get-coral/jellyfin'
const client = createClient({
url: 'http://192.168.1.10:8096',
apiKey: 'your-api-key',
userId: 'your-user-id',
})
const { Items } = await getLibraryItems(client, 'Movie', {
limit: 24,
sortBy: 'SortName',
watchStatus: 'unwatched',
})
const movies = Items.map(item => fromJellyfin(client, item))
const client = createClient({
url: string // Jellyfin server URL (trailing slash stripped automatically)
apiKey: string // Jellyfin API key
userId: string // User ID (UUID)
// Optional — for playback progress sync
username?: string
password?: string
// Optional — a ready Jellyfin access token (e.g. from authenticateUserByName).
// When set, playback auth uses it directly instead of re-authenticating.
accessToken?: string
// Optional — how this client identifies itself in Jellyfin's active sessions
clientName?: string // default: 'Coral'
deviceName?: string // default: 'Coral Web'
deviceId?: string // default: 'coral-web'
version?: string // default: '1.0.0'
})

The client is passed as the first argument to standalone functions, grouped roughly by area:

  • Items: getLibraryItems, getItem, getLatestMedia, getContinueWatching, getFavoriteItems, getWatchHistory, getMostPlayed, getSimilarItems, getFeaturedItem, searchItems, setFavorite, setPlayed, deleteItem, updateItem, remote image helpers
  • Shows: getEpisodesForSeries, getNextUpForSeries
  • Collections: getCollections, getCollectionItems, createCollection, addItemsToCollection, removeItemsFromCollection, searchCollectionItems
  • Playback: createPlaybackSession, syncPlaybackState
  • Authentication & sessions: authenticateUserByName, logoutUserSession
  • URL builders: imageUrl, personImageUrl, streamUrl, transcodeUrl, subtitleUrl
  • Mapper: fromJellyfin, fromJellyfinDetailed — normalise raw JellyfinItems into a UI-friendly MediaItem shape
  • Admin: getSystemInfo, getItemCounts, getActiveSessions, getUsers, getUserById, createUser, deleteUser, updateUserPolicy, getVirtualFolders, scanAllLibraries, scanLibrary

See the repository README for the full reference with options and return types.

Build sign-in flows on top of Jellyfin’s own accounts:

import { authenticateUserByName, logoutUserSession, createClient } from '@get-coral/jellyfin'
// Sign a user in with their Jellyfin username/password.
// Returns their identity and a real Jellyfin access token.
const { user, accessToken, sessionId } = await authenticateUserByName(client, 'alice', 'secret')
// Act as that user: playback and progress sync run under their token
const userClient = createClient({
url,
apiKey,
userId: user.Id,
accessToken,
deviceId: 'my-app-session-1234',
})
// End the session again (revokes the token)
await logoutUserSession(client, accessToken)

Give every session a unique deviceId — Jellyfin revokes the previous token when the same user re-authenticates with the same device id, so a shared id makes concurrent sign-ins invalidate each other. authenticateUserByName throws a JellyfinError with status 401 for invalid credentials or disabled users.

import { createPlaybackSession, syncPlaybackState } from '@get-coral/jellyfin'
const session = await createPlaybackSession(client, itemId)
await syncPlaybackState(client, {
itemId,
playSessionId: session.playSessionId,
positionTicks: 12_345,
isPaused: false,
})

Playback endpoints authenticate as a real Jellyfin session, using either the configured username/password or a ready accessToken.

All functions throw a JellyfinError on non-OK responses:

import { JellyfinError } from '@get-coral/jellyfin'
try {
const item = await getItem(client, 'bad-id')
} catch (err) {
if (err instanceof JellyfinError) {
console.error(err.message) // 'Jellyfin API error on ...: 404 Not Found'
console.error(err.status) // 404
}
}

Community contributions are welcome! See the Contributing guide.