Kareadita/Kavita · warning · KavitaException

cant-delete-default-profile

Error message

cant-delete-default-profile

What it means

Thrown by DeleteReadingProfile when the resolved profile has Kind == ReadingProfileKind.Default. The Default profile is system-managed and cannot be removed; only User (and Implicit-promoted) profiles are deletable. The 'cant-delete-default-profile' localization key is surfaced. This is a business-rule guard, not a data fault.

Source

Thrown at Kavita.Services/Reading/ReadingProfileService.cs:181

        newProfile.Name = $"Implicit Profile for {seriesId}";
        newProfile.NormalizedName = newProfile.Name.ToNormalized();
        if (activeDeviceId != null)
        {
            newProfile.DeviceIds.Add(activeDeviceId.Value);
        }

        user.ReadingProfiles.Add(newProfile);
        await unitOfWork.CommitAsync();

        return mapper.Map<UserReadingProfileDto>(newProfile);
    }

    public async Task DeleteReadingProfile(int userId, int profileId)
    {
        var profile = await unitOfWork.AppUserReadingProfileRepository.GetUserProfile(userId, profileId);
        if (profile == null) throw new KavitaException("profile-doesnt-exist");

        if (profile.Kind == ReadingProfileKind.Default) throw new KavitaException("cant-delete-default-profile");

        unitOfWork.AppUserReadingProfileRepository.Remove(profile);
        await unitOfWork.CommitAsync();
    }

    public async Task SetSeriesProfiles(int userId, List<int> profileIds, int seriesId)
    {
        var profiles = await unitOfWork.AppUserReadingProfileRepository.GetProfilesForUser(userId);

        var selectedProfiles = profiles
            .Where(rp => profileIds.Contains(rp.Id))
            .ToList();
        if (selectedProfiles.Count != profileIds.Count) throw new KavitaException("profile-doesnt-exist");

        DeviceOverlapGuard(selectedProfiles);

        var allDeviceIds = selectedProfiles.SelectMany(p => p.DeviceIds).Distinct().ToList();

View on GitHub (pinned to 9c3e540000)

Solutions

  1. Do not attempt to delete the Default profile; it is permanent.
  2. Disable/hide the delete control when profile.Kind == Default in the UI.
  3. If custom settings are wanted, edit the Default profile or create a new User profile instead.
Defensive patterns

Strategy: validation

Validate before calling

var profile = await unitOfWork.AppUserReadingProfileRepository.GetUserProfile(userId, profileId);
if (profile?.Kind == ReadingProfileKind.Default) return BadRequest("cant-delete-default-profile");
await profileService.DeleteReadingProfile(userId, profileId);

Type guard

public static bool IsDeletable(AppUserReadingProfile p) => p.Kind != ReadingProfileKind.Default;

Try / catch

try { await profileService.DeleteReadingProfile(userId, profileId); }
catch (KavitaException ex) when (ex.Message == "cant-delete-default-profile") { return BadRequest(ex.Message); }

Prevention

When it happens

Trigger: The UI or API client issues a delete against the user's Default reading profile.

Common situations: Misconfigured UI offering delete on the default profile; scripted/admin call targeting the default id; user misunderstanding that the default is permanent.

Related errors


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