Kareadita/Kavita · warning · KavitaException

collection-tag-title-required

Error message

collection-tag-title-required

What it means

Thrown by UpdateTag when the title, after trimming, is null or empty. The check 'string.IsNullOrEmpty(title)' fires after 'dto.Title.Trim()'. This is pure input validation — the collection exists (checked before) and ownership passed, but the submitted title is blank.

Source

Thrown at Kavita.Services/CollectionTagService.cs:41

        var collectionTag = await unitOfWork.CollectionTagRepository.GetCollectionAsync(tagId, ct: ct);
        if (collectionTag == null) return true;

        user.Collections.Remove(collectionTag);

        if (!unitOfWork.HasChanges()) return true;

        return await unitOfWork.CommitAsync(ct);
    }


    public async Task<bool> UpdateTag(AppUserCollectionDto dto, int userId, CancellationToken ct = default)
    {
        var existingTag = await unitOfWork.CollectionTagRepository.GetCollectionAsync(dto.Id, ct: ct);
        if (existingTag == null) throw new KavitaException("collection-doesnt-exist");
        if (existingTag.AppUserId != userId) throw new KavitaException("access-denied");

        var title = dto.Title.Trim();
        if (string.IsNullOrEmpty(title)) throw new KavitaException("collection-tag-title-required");

        // Ensure the title doesn't exist on the user's account already
        if (!title.Equals(existingTag.Title) && await unitOfWork.CollectionTagRepository.CollectionExists(dto.Title, userId, ct))
            throw new KavitaException("collection-tag-duplicate");

        existingTag.Items ??= [];
        if (existingTag.Source == ScrobbleProvider.Kavita)
        {
            existingTag.Title = title;
            existingTag.NormalizedTitle = dto.Title.ToNormalized();
        }

        var roles = await unitOfWork.UserRepository.GetRoles(userId, ct);
        if (roles.Contains(PolicyConstants.AdminRole) || roles.Contains(PolicyConstants.PromoteRole))
        {
            existingTag.Promoted = dto.Promoted;
        }
        existingTag.CoverImageLocked = dto.CoverImageLocked;

View on GitHub (pinned to 9c3e540000)

Solutions

  1. Add client-side validation to prevent empty title submissions (required attribute on the input field)
  2. Validate the title is non-empty in the controller before calling UpdateTag
  3. Ensure the DTO has [Required] data annotation on the Title property

Example fix

// Add DTO validation:
// public class AppUserCollectionDto
// {
//     [Required(ErrorMessage = "Title is required")]
//     [MinLength(1)]
//     public string Title { get; set; } = string.Empty;
// }
Defensive patterns

Strategy: validation

Validate before calling

// Validate title before calling UpdateTag:
// var trimmedTitle = dto.Title?.Trim();
// if (string.IsNullOrEmpty(trimmedTitle)) {
//     return BadRequest("Collection title is required");
// }

Try / catch

// try {
//     await collectionTagService.UpdateTag(dto, userId, ct);
// } catch (KavitaException ex) when (ex.Message == "collection-tag-title-required") {
//     return BadRequest(new { error = "A non-empty title is required." });
// }

Prevention

When it happens

Trigger: User submits an empty title or a title consisting only of whitespace characters; form validation on the client side failed to prevent an empty submission; API call constructed programmatically with an empty title field.

Common situations: Client-side validation was bypassed or is incomplete; user cleared the title field and submitted without the UI catching it; automated/scripted requests with malformed payloads.

Related errors


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