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
- GET /api/theme to list valid theme ids and use one of those.
- If the theme was deleted, re-download it from /api/theme/browse and use the new id.
- 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
- Refresh the theme list after add/delete and only request ids from it.
- Do not hard-code theme ids; they are per-install.
- Treat a missing-id response as a signal to reload the theme list.
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
- library-doesnt-exist
- Source directory does not exist or could not be found:
- The directory doesn't exist
- Source file not found
- profile-does-not-exist
AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13).
Data as JSON: /api/errors/7db3bc1afea78e83.
Report an issue: GitHub.