Kareadita/Kavita · warning · KavitaException
external-source-doesnt-exist
Error message
external-source-doesnt-exist
What it means
Thrown by StreamService.CreateSideNavStreamFromExternalSource when AppUserExternalSourceRepository.GetById(externalSourceId) returns null — the external source does not exist (or does not belong to the user). The message is translated to 'External Source doesn't exist'. StreamController.AddSideNavFromExternalSource does not catch KavitaException, so it surfaces as HTTP 500 via ExceptionMiddleware.
Source
Thrown at Kavita.Services/StreamService.cs:198
Order = createdStream.Order,
SmartFilterEncoded = smartFilter.Filter,
StreamType = createdStream.StreamType
};
await eventHub.SendMessageToAsync(MessageFactory.SideNavUpdate, MessageFactory.SideNavUpdateEvent(userId),
userId, ct);
return ret;
}
public async Task<SideNavStreamDto> CreateSideNavStreamFromExternalSource(int userId, int externalSourceId,
CancellationToken ct = default)
{
var user = await unitOfWork.UserRepository.GetUserByIdAsync(userId, AppUserIncludes.SideNavStreams, ct);
if (user == null) throw new KavitaException(await localizationService.TranslateAsync(userId, "no-user"));
var externalSource = await unitOfWork.AppUserExternalSourceRepository.GetById(externalSourceId, ct);
if (externalSource == null) throw new KavitaException(await localizationService.TranslateAsync(userId, "external-source-doesnt-exist"));
var stream = user?.SideNavStreams.FirstOrDefault(d => d.ExternalSourceId == externalSourceId);
if (stream != null) throw new KavitaException(await localizationService.TranslateAsync(userId, "external-source-already-in-use"));
var maxOrder = user!.SideNavStreams.Max(d => d.Order);
var createdStream = new AppUserSideNavStream()
{
Name = externalSource.Name,
IsProvided = false,
StreamType = SideNavStreamType.ExternalSource,
Visible = true,
Order = maxOrder + 1,
ExternalSourceId = externalSource.Id
};
user.SideNavStreams.Add(createdStream);
unitOfWork.UserRepository.Update(user);
await unitOfWork.CommitAsync(ct);View on GitHub (pinned to 9c3e540000)
Solutions
- Use an externalSourceId from the user's current external sources (GET /api/stream/external-sources).
- Refresh the external sources list before offering 'add to side nav'.
- Recreate the external source if it was deleted, then add the side-nav stream.
Defensive patterns
Strategy: validation
Validate before calling
const sources = await api.get('/api/stream/external-sources');
if (!sources.some(s => s.id === externalSourceId)) {
showError('External Source not found');
return;
}
await api.post(`/api/stream/add-sidenav-stream-from-external-source?externalSourceId=${externalSourceId}`); Type guard
function isKnownExternalSourceId(id: number, sources: { id: number }[]): boolean {
return sources.some(s => s.id === id);
} Prevention
- Offer 'add to side nav' only for external sources in the user's current list.
- Refresh external sources after delete operations.
- Validate deep-link ids before calling.
When it happens
Trigger: POST /api/stream/add-sidenav-stream-from-external-source?externalSourceId=N with a deleted, invalid, or other-user external source id.
Common situations: Stale externalSourceId cached in the UI after the source was deleted; deep link with an old id; cross-user id confusion.
Related errors
- smart-filter-doesnt-exist
- dashboard-stream-doesnt-exist
- external-source-already-in-use
- sidenav-stream-doesnt-exist
- library-doesnt-exist
AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13).
Data as JSON: /api/errors/775f2ba97cc70af2.
Report an issue: GitHub.