fullstackhero/dotnet-starter-kit · error · CustomException

Extension ' ' not allowed for category ' '.

Error message

Extension '{extension}' not allowed for category '{cmd.Category}'.

What it means

CustomException (BadRequest) thrown when the uploaded file's extension (from Path.GetExtension on cmd.FileName) is empty or not in the category's AllowedExtensions list (case-insensitive). The gate exists to keep unapproved file types out of storage before a presigned URL is minted.

Solutions

  1. Send a file whose extension is in the target category's AllowedExtensions config, or convert the file to an allowed type.
  2. If the type is legitimately needed, add that extension to the category's AllowedExtensions in configuration.
  3. Update the client file-picker accept filter to match the server's allow-list so users cannot select disallowed files.
  4. Validate extension client-side before calling the API to give instant feedback.

Example fix

// before
await requestUploadUrl({ category: "avatar", fileName: "photo.bmp", ... }); // .bmp not allowed
// after
await requestUploadUrl({ category: "avatar", fileName: "photo.png", ... }); // .png in AllowedExtensions
Defensive patterns

Strategy: validation

Validate before calling

const ext = fileName.slice(fileName.lastIndexOf(".")).toLowerCase();
if (!category.allowedExtensions.map(e => e.toLowerCase()).includes(ext)) throw new Error(`.${ext} not allowed`);

Try / catch

try { await requestUploadUrl(cmd); } catch (e) { if (e.status === 400 && e.message?.includes("not allowed for category")) { showToast("File type not supported for this upload"); } else throw e; }

Prevention

When it happens

Trigger: RequestUploadUrlCommand with cmd.FileName having no extension ('README'), a double extension not listed ('file.tar.gz' when only '.gz' allowed), a blocked type ('.exe' for the avatar category), or an extension present only in a different category's allow-list.

Common situations: Frontend letting users pick files without filtering by accept attribute; renaming files to hide types; configuration allow-list tightened without updating client UI; case handled fine (comparison is OrdinalIgnoreCase) but hidden characters or trailing dots in the filename cause mismatch.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/f75f8ac70585a9b1. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Files/Modules.Files/Features/v1/RequestUploadUrl/RequestUploadUrlCommandHandler.cs:48

        var tenantId = currentUser.GetTenant() ?? throw new UnauthorizedException("invalid tenant");
        var userId = currentUser.GetUserId();
        if (userId == Guid.Empty)
        {
            throw new UnauthorizedException("no current user");
        }

        // Category lookup + extension/size validation.
        if (!options.Value.Categories.TryGetValue(cmd.Category, out var category))
        {
            throw new CustomException($"Unknown category '{cmd.Category}'.", (IEnumerable<string>?)null, HttpStatusCode.BadRequest);
        }

        var extension = Path.GetExtension(cmd.FileName);
        if (string.IsNullOrWhiteSpace(extension) ||
            !category.AllowedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
        {
            throw new CustomException(
                $"Extension '{extension}' not allowed for category '{cmd.Category}'.",
                (IEnumerable<string>?)null,
                HttpStatusCode.BadRequest);
        }

        if (cmd.SizeBytes > category.MaxBytes)
        {
            throw new CustomException(
                $"File exceeds max size of {category.MaxBytes} bytes for category '{cmd.Category}'.",
                (IEnumerable<string>?)null,
                HttpStatusCode.BadRequest);
        }

        // Authorization: policy must exist and allow the attach.
        var policy = policies.Resolve(cmd.OwnerType)
            ?? throw new ForbiddenException($"No file access policy registered for owner type '{cmd.OwnerType}'.");
        if (!await policy.CanAttachAsync(cmd.OwnerId, userId.ToString(), cancellationToken).ConfigureAwait(false))
        {

View on GitHub (pinned to 3f2959e683)