Jellyfin Database Schema and Relationship Management: A Complete EF Core Breakdown

Jellyfin stores all persistent data in a single Entity Framework Core DbContext named JellyfinDbContext, using a normalized relational schema centered on the BaseItemEntity table with explicit junction tables and navigation properties to handle many-to-many relationships, hierarchical ancestry, and media metadata.

Jellyfin's media server architecture relies on a robust Entity Framework Core (EF Core) database layer to manage everything from video metadata to user playback states. At the heart of this system lies the JellyfinDbContext, which orchestrates a carefully designed schema that supports SQLite, MySQL, and PostgreSQL backends. Understanding how the Jellyfin database schema structures relationships—particularly the central role of BaseItemEntity and the strategic use of junction tables—is essential for developers building plugins or contributing to the core platform.

Core Tables in the Jellyfin Database Schema

BaseItemEntity: The Universal Media Hub

Every movie, episode, music track, and photo in Jellyfin derives from a single row in the BaseItemEntity table. This entity acts as the polymorphic root for all media objects, storing common attributes while relying on auxiliary tables for specialized data.

Key columns in BaseItemEntity include:

  • Id (Primary Key): The unique GUID for every item
  • Type: Discriminator indicating whether the row represents a Movie, Episode, Audio, etc.
  • Path: Filesystem location of the media file
  • ParentId, SeriesId, SeasonId: Foreign keys establishing hierarchical containment
  • OwnerId: Reference to the creating user

According to the source code in src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/BaseItemEntity.cs, this entity also declares navigation properties like DirectParent, DirectChildren, and LinkedChildEntities that EF Core uses to materialize relationships at runtime.

Supporting Entities and Junction Tables

The schema surrounds BaseItemEntity with specialized tables for users, devices, and metadata:

Entity Purpose Key Relationship
User Authentication and policy storage One-to-many with UserData
Device Registered client applications Tracks app versions and last activity
MediaStreamInfo Audio, video, and subtitle streams Many-to-one via ItemId FK
ImageInfo Cached thumbnails and posters Many-to-one via ItemId
ItemValue Arbitrary metadata (tags, genres) Many-to-many via ItemValueMap
People Cast and crew members Many-to-many via PeopleBaseItemMap
UserData Playback position and play counts Composite key on UserId + ItemId
AncestorId Pre-computed hierarchy paths Facilitates fast descendant queries

The many-to-many relationships use explicit junction entities rather than EF Core's implicit join tables, giving the codebase precise control over foreign key naming and composite primary keys.

How Jellyfin Manages Database Relationships

Each entity class defines navigation properties that EF Core maps to foreign key constraints. In BaseItemEntity, the relationships appear as:

public Guid? ParentId { get; set; }
public BaseItemEntity? DirectParent { get; set; }
public ICollection<BaseItemEntity>? DirectChildren { get; set; }
public ICollection<PeopleBaseItemMap>? Peoples { get; set; }
public ICollection<ItemValueMap>? ItemValues { get; set; }
public ICollection<LinkedChildEntity>? LinkedChildEntities { get; set; }

These properties enable both lazy loading during entity access and eager loading via Include() statements in LINQ queries. The ParentId column creates self-referential hierarchies for folders and seasons, while LinkedChildEntity handles non-hierarchical collections like playlists.

Explicit Junction Tables for Many-to-Many Relations

Jellyfin avoids implicit many-to-many mappings in favor of explicit junction entities located in the Entities folder:

Each junction entity contains two required foreign key properties (e.g., ItemId and ItemValueId) and navigation properties pointing to both parent entities. EF Core automatically generates composite primary keys for these tables, ensuring referential integrity without surrogate IDs.

Hierarchical Performance via AncestorId

To prevent expensive recursive joins when querying deep folder structures, Jellyfin employs the AncestorId table as a denormalized cache. This table stores every direct ancestor-descendant pair, allowing the server to retrieve complete directory trees with a single indexed query.

The DescendantQueryHelper class in src/Jellyfin.Database/Jellyfin.Database.Implementations/DescendantQueryHelper.cs maintains this ancestry data. When a library scan adds new items, the helper populates AncestorId rows that map each item to all its parent folders, series, and seasons.

EF Core Configuration and Schema Definition

ModelConfiguration Classes

Rather than cluttering the DbContext with fluent API calls, Jellyfin organizes schema configuration into dedicated classes under the ModelConfiguration namespace. Files like BaseItemEntityConfiguration.cs define table names, indexes, and relationship constraints using the IEntityTypeConfiguration<T> interface.

These configurations are applied automatically in JellyfinDbContext.OnModelCreating:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    jellyfinDatabaseProvider.OnModelCreating(modelBuilder);
    base.OnModelCreating(modelBuilder);
    modelBuilder.ApplyConfigurationsFromAssembly(typeof(JellyfinDbContext).Assembly);
}

This centralized approach keeps entity classes clean while allowing provider-specific tweaks for SQLite, MySQL, and PostgreSQL optimization.

Querying the Jellyfin Database Schema

Developers interacting with the schema through EF Core can leverage navigation properties for efficient data retrieval. Below are common query patterns against the JellyfinDbContext:

Eager Loading Media with Metadata

Load a movie together with its poster images and custom genre tags:

using (var ctx = dbContextFactory.CreateDbContext())
{
    var movie = await ctx.BaseItems
        .Where(i => i.Type == "Movie" && i.Name == "Inception")
        .Include(i => i.Images)
        .Include(i => i.ItemValues)
            .ThenInclude(m => m.ItemValue)
        .FirstOrDefaultAsync();
}

Hierarchical Episode Retrieval

Fetch all episodes for a specific season using the hierarchical foreign keys:

var episodes = await ctx.BaseItems
    .Where(i => i.SeasonId == seasonId && i.Type == "Episode")
    .OrderBy(i => i.IndexNumber)
    .ToListAsync();

Resolving Many-to-Many Relationships

Find all actors linked to a specific movie through the junction table:

var actors = await ctx.PeopleBaseItemMap
    .Where(m => m.ItemId == movie.Id)
    .Select(m => m.People)
    .ToListAsync();

Ancestry Chain Queries

Retrieve the full parent hierarchy for a playlist item using the pre-computed ancestry table:

var ancestors = await ctx.AncestorIds
    .Where(a => a.ItemId == playlistItemId)
    .Select(a => a.ParentItem)
    .ToListAsync();

Summary

  • Centralized Architecture: The JellyfinDbContext manages a unified schema supporting SQLite, MySQL, and PostgreSQL through EF Core.
  • BaseItemEntity Pattern: All media items inherit from a single table with discriminators, minimizing schema duplication while preserving type safety.
  • Explicit Junction Tables: Many-to-many relationships use dedicated entities like ItemValueMap and PeopleBaseItemMap for precise constraint control.
  • Hierarchical Optimization: The AncestorId table and DescendantQueryHelper eliminate recursive SQL joins for folder and series traversal.
  • Configuration Separation: Schema definitions reside in the ModelConfiguration folder, applied automatically via ApplyConfigurationsFromAssembly.

Frequently Asked Questions

What database backends does Jellyfin support?

Jellyfin supports SQLite as the default embedded option, alongside MySQL/MariaDB and PostgreSQL for larger deployments. The JellyfinDbContext uses provider-specific configuration classes to handle dialect differences in indexing and key constraints, allowing seamless migration between backends without changing entity code.

How does Jellyfin handle hierarchical folder structures?

The server uses a combination of self-referential foreign keys (ParentId) and a denormalized AncestorId table. While ParentId defines the immediate container, AncestorId pre-calculates the entire ancestry chain during library scans. This design enables constant-time queries for "all descendants of a folder" without recursive Common Table Expressions (CTEs).

What is the purpose of the LinkedChildEntity table?

LinkedChildEntity manages non-hierarchical relationships where items appear in collections, playlists, or box sets without being physically contained within those folders. Unlike ParentId relationships which imply ownership, linked children maintain their original location while gaining secondary membership in curated groups.

Are media files stored in the database?

No. The database stores only metadata, paths, and relationships. The Path column in BaseItemEntity references files on disk or network storage. Media streams themselves are served directly from the filesystem, while the MediaStreamInfo table caches technical details like codec, bitrate, and subtitle language for quick browsing.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →