Kareadita/Kavita · warning · KavitaException

theme-doesnt-exist

theme-doesnt-exist

Error message

theme-doesnt-exist

What it means

Database-lookup miss in SiteThemeService.GetContent: the repository's GetThemeDto(themeId) returned null, so no theme row matches the requested id. Kavita throws KavitaException('theme-doesnt-exist'); ThemeController maps it to HTTP 400 'Theme file missing or invalid'. The endpoint is anonymous (download-content).

Source

Thrown at Kavita.Services/SiteThemeService.cs:91

        .SetSize(1)
        .SetAbsoluteExpiration(TimeSpan.FromMinutes(30));

    private const string GithubBaseUrl = "https://api.github.com";

    /// <summary>
    /// Used for refreshing metadata around themes
    /// </summary>
    private const string GithubReadme = "https://raw.githubusercontent.com/Kareadita/Themes/main/README.md";

    /// <summary>
    /// Given a themeId, return the content inside that file
    /// </summary>
    /// <param name="themeId"></param>
    /// <param name="ct"></param>
    /// <returns></returns>
    public async Task<string> GetContent(int themeId, CancellationToken ct = default)
    {
        var theme = await unitOfWork.SiteThemeRepository.GetThemeDto(themeId) ?? throw new KavitaException("theme-doesnt-exist");
        var themeFile = directoryService.FileSystem.Path.Join(directoryService.SiteThemeDirectory, theme.FileName);
        if (string.IsNullOrEmpty(themeFile) || !directoryService.FileSystem.File.Exists(themeFile))
            throw new KavitaException("theme-doesnt-exist");

        return await directoryService.FileSystem.File.ReadAllTextAsync(themeFile, ct);
    }

    public async Task<List<DownloadableSiteThemeDto>> GetDownloadableThemes(CancellationToken ct = default)
    {
        const string cacheKey = "browse";
        // Avoid a duplicate Dark issue some users faced during migration
        var existingThemes = (await unitOfWork.SiteThemeRepository.GetThemeDtos())
            .GroupBy(k => k.Name)
            .ToDictionary(g => g.Key, g => g.First());

        if (cache.TryGetValue(cacheKey, out List<DownloadableSiteThemeDto>? themes) && themes != null)
        {
            foreach (var t in themes)

View on GitHub (pinned to 9c3e540000)

Solutions

  1. GET /api/theme to list valid theme ids and use one of those.
  2. If the theme was deleted, re-download it from /api/theme/browse and use the new id.
  3. Refresh the client theme state after theme add/delete operations so stale ids are never requested.

Example fix

// before
fetch(`/api/theme/download-content?themeId=${staleId}`)

// after
const themes = await fetch('/api/theme').then(r => r.json());
fetch(`/api/theme/download-content?themeId=${themes[0].id}`)
Defensive patterns

Strategy: validation

Validate before calling

// Verify the theme id exists before requesting its CSS.
const themes: SiteThemeDto[] = await api.get('/api/theme');
if (!themes.some(t => t.id === themeId)) {
  showError('Theme not found');
  return;
}
await api.get(`/api/theme/download-content?themeId=${themeId}`);

Type guard

function isKnownThemeId(id: number, themes: { id: number }[]): boolean {
  return themes.some(t => t.id === id);
}

Prevention

When it happens

Trigger: GET /api/theme/download-content?themeId=N where N does not exist in the site_themes table (deleted theme, stale id, id from a different install, typo).

Common situations: Frontend holds a themeId after the admin deleted that theme or after a DB reset/restore; user bookmarked a theme CSS URL with an old id; multi-instance confusion where ids differ.

Related errors


AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13). Data as JSON: /api/errors/7db3bc1afea78e83. Report an issue: GitHub.