Umbraco Forms - File Upload

If a visitor tries to upload a file that is too big then Umbraco crashes with a BadHttpRequestException: Request body too large.

Is there some way to give the visitor an error message instead of crashing?

Hi @russellshome ,

To stop the crash and show the visitor a friendly error message, you can use these options:

Option 1: Catch the crash and redirect to an error page

You can intercept Kestrel’s crash globally using an IExceptionHandler and redirect the visitor to a friendly Umbraco content page explaining the file was too big.

1. Create the handler:

using Microsoft.AspNetCore.Diagnostics;

public class PayloadTooLargeExceptionHandler : IExceptionHandler
{
    public async ValueTask<bool> TryHandleAsync(HttpContext context, Exception exception, CancellationToken cancellationToken)
    {
        if (exception is BadHttpRequestException ex && ex.StatusCode == StatusCodes.Status413PayloadTooLarge)
        {
            // Redirect the visitor to a friendly error page instead of crashing
            context.Response.Redirect("/file-too-large-error"); 
            return true; 
        }
        return false; 
    }
}

2. Register it in Program.cs:

// Add this before builder.CreateUmbracoBuilder()
builder.Services.AddExceptionHandler<PayloadTooLargeExceptionHandler>();

WebApplication app = builder.Build();

// Add this before app.BootUmbracoAsync()
app.UseExceptionHandler(opt => { });

Option 2: Show the error message on the same form

If you want the error to appear directly next to the upload button without redirecting them, you have to tell Kestrel to allow the file through so your code can check it.
1. Increase the server limit in appsettings.json:

"Umbraco": {
  "CMS": {
    "Runtime": {
      "MaxRequestLength": 51200 // e.g., 50 MB
    }
  }
}

2. Set actual limit in your SurfaceController:

[HttpPost]
public IActionResult HandleUpload(IFormFile uploadedFile)
{
    long myLimit = 10 * 1024 * 1024; // 10 MB

    if (uploadedFile != null && uploadedFile.Length > myLimit)
    {
        ModelState.AddModelError("File", "Please keep your file under 10MB.");
        return CurrentUmbracoPage(); // Returns them to the form with the error message
    }

    // Process file...
    return RedirectToCurrentUmbracoPage();
}

If you are hosting on IIS or Umbraco Cloud, IIS has its own upload limit that triggers before Kestrel even sees the file.

If you go with Option 2 (raising the limits), updating appsettings.json isn’t quite enough on its own. You also need to add/update a web.config file at the root of your project to tell IIS to let the file through.

The IIS setting uses bytes instead of kilobytes (e.g., 50 MB = 52428800):

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <security>
      <requestFiltering>
        <requestLimits maxAllowedContentLength="52428800" />
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>

You can also add a quick JavaScript to show the error if file size is too big:

document.querySelector('input[type="file"]').addEventListener('change', function() {
    if (this.files[0].size > 10485760) { // 10 MB in bytes
        alert("This file is too big! Please keep it under 10MB.");
        this.value = ''; // Clears the file so they can't submit
    }
});

This prevents the upload entirely, saves bandwidth, and gives them an instant error message without needing to talk to the server at all. (You still need the server-side code as a backup, but this stops 99% of crashes at the browser level!).

Hope this help!

Regards,
Shekhar

I’m using Umbraco Forms. I was expecting that there would maybe be a setting or something that I was missing.

It seems a bit poor for Umbraco Forms to crash just because a file is too big.

If it were just a few settings & lines of code as you are saying then why can’t that be baked into Umbraco Forms?

A proper javascript would be good but not one that assumes that there is only ever one file upload, element in a form, that every change event to that element will have 1 or more files selected, that only a single file is ever going to be selected, that arbitrarily limits to 10MB without any consideration of what the upload limit actually is…

You’re absolutely right on that. I apologize I completely missed you were using Umbraco Forms.

While looking into this, I found an existing discussion Feature Request: Add Max File Size Validation to Upload Field · umbraco/Umbraco.Forms.Issues · Discussion #1537 · GitHub which is asking for the similar thing but no resolution on that. I have checked releases as well but found nothing on this.

What I understand is that when a file exceeds Kestrel’s underlying MaxRequestBodySize, Kestrel violently severs the network connection to protect the server’s memory. It drops the request before Umbraco Forms even knows it was made. Because Forms is just an add-on package, it doesn’t have the authority to rewrite the server’s global base security limits to let the file through.

To bridge this gap, we have to raise those configurations at the server level so the network request isn’t killed. However, as you rightly pointed out, we need a robust server-side validation pipeline to gracefully return errors back to the user experience.

The cleanest approach on modern Umbraco Forms is creating a Custom Field Type, inspired by the community concepts found on Skrift.io’s Extending Umbraco Forms Guide. This bakes your logic straight into the .NET Form submission pipeline without relying strictly on frontend JavaScript overrides: Extending Umbraco Forms by Richard Terris | Issue 25 of Skrift Magazine

However, there are two quick things to note if you go this route:

1. You still have to raise the server limits. As the author notes at the bottom of the article, Kestrel/IIS checks the file size before Umbraco Forms runs. If a user uploads a 50MB file and your server limit is 28MB, Kestrel will still crash the site before your custom Field Type ever gets triggered. You still need to raise MaxRequestLength in your appsettings.json to a generous ceiling so the file makes it through the door. Once it passes Kestrel, your custom Field Type will catch it and return a native Umbraco Forms validation error.

2. The Skrift code is for Umbraco 7. Because that article is from 2017, it uses old .NET Framework code (HttpPostedFileBase) which won’t compile in Umbraco 13/17. The code will need to be rewritten for the latest versions.

Let me try to rewrite the code for Umbraco 17, I will add the code once I am able to test it.

For setting your upper server limits to allow large files through to the validation step, check out the official Umbraco Maximum Upload Size Documentation.

I was able to create and test it on Umbraco 18. Here’s the complete steps to create the Custom Field Type:

Step 1: Create the Custom Field Type Class:

Create a new file in your project (e.g., CustomFileUpload.cs). This class inherits the default file-saving logic from Umbraco Forms but overrides the validation to strictly enforce a 5 MB limit.


using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Security;
using Umbraco.Forms.Core.Configuration;
using Umbraco.Forms.Core.Enums;
using Umbraco.Forms.Core.Models;
using Umbraco.Forms.Core.Providers.FieldTypes;
using Umbraco.Forms.Core.Services;
using static Umbraco.Cms.Core.Constants;

namespace MyTestUmbracoProjectSQLLite01
{
    public class CustomFileUpload : FileUpload
    {
        // 5 MB limit (5 * 1024 * 1024)
        private readonly long _maxSizeBytes = 5242880;

        // Accept the dependencies required by the base FileUpload class and pass them down
        public CustomFileUpload(
            IOptions<SecuritySettings> securitySettings,
            IHostEnvironment hostEnvironment,
            MediaFileManager mediaFileManager,
            IDataProtectionProvider dataProtectionProvider,
            IFileStreamSecurityValidator fileStreamSecurityValidator,
            IPlaceholderParsingService placeholderParsingService)
            : base(securitySettings, hostEnvironment, mediaFileManager, dataProtectionProvider, fileStreamSecurityValidator, placeholderParsingService)
        {
            Id = new Guid("d8d89001-57ce-4b52-9005-c0c84f6651cb");
            Name = "Custom File Upload (Max 5MB)";
            Description = "Renders an upload field with a strict 5MB size limit.";
            Icon = "icon-cloud-upload";
            DataType = FieldDataType.String;

            // This tells Umbraco Forms which Razor view to look for
            FieldTypeViewName = "FieldType.CustomFileUpload.cshtml";
        }

        public override IEnumerable<string> ValidateField(Form form, Field field, IEnumerable<object> postedValues, Microsoft.AspNetCore.Http.HttpContext context, IPlaceholderParsingService placeholderParsingService, IFieldTypeStorage fieldTypeStorage)
        {
            // 1. Run the base validation (Checks if the field is mandatory, etc.)
            var errors = new List<string>(base.ValidateField(form, field, postedValues, context, placeholderParsingService, fieldTypeStorage));

            // 2. Run our custom size validation
            if (context?.Request.HasFormContentType == true && context.Request.Form.Files.Count > 0)
            {
                var fieldIdString = field.Id.ToString();

                // 3. Loop through files uploaded specifically to this field
                foreach (var file in context.Request.Form.Files)
                {
                    if (file.Name.Contains(fieldIdString, StringComparison.OrdinalIgnoreCase))
                    {
                        if (file.Length > _maxSizeBytes)
                        {
                            errors.Add($"The file '{file.FileName}' is too large. Please keep it under 5MB.");
                        }
                    }
                }
            }

            return errors;
        }
    }
}

Step 2: Register it using an IComposer

In Umbraco 17, you register custom Forms components into the dependency injection container using the builder.FormsFields().Add() extension method.

Create a file named CustomFormsComposer.cs:

using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Forms.Core.Providers.Extensions;

namespace MyTestUmbracoProjectSQLLite01.Composer
{

    public class CustomFormsComposer : IComposer
    {
        public void Compose(IUmbracoBuilder builder)
        {
            // Registers your custom field type so it appears in the backoffice
            builder.FormsFields().Add<CustomFileUpload>();
        }
    }
}

Step 3: Create the Frontend Razor View

Because we set FieldTypeViewName to "FieldType.CustomFileUpload.cshtml", Umbraco Forms will look for this file when rendering your form on the website.

In your solution, navigate to Views\Partials\Forms\Themes\default\Fieldtypes\ and create a file named FieldType.CustomFileUpload.cshtml.

Paste in a standard HTML file input mapped to the Umbraco Forms FieldViewModel:

@using System.Linq;
@model Umbraco.Forms.Web.Models.FieldViewModel

<input type="file"
       name="@Model.Id"
       id="@Model.Id"
       @if (Model.Mandatory)
{
    <text> required="required" data-val="true" data-val-required="@Model.RequiredErrorMessage" </text>
}
/>

@*
   Safely evaluate if the field contains any previously uploaded values without triggering method group errors.
   Model.Values?.Any() == true gracefully handles null checks and evaluates directly to a boolean.
*@
@if (Model.Values?.Any() == true)
{
    var uploadedFile = Model.Values.FirstOrDefault()?.ToString();

    if (!string.IsNullOrEmpty(uploadedFile))
    {
        <input type="hidden" name="@{
        @Model.Id
    }
    _file" value="@uploadedFile" />
    <span class="help-block">Currently uploaded: @uploadedFile</span>
}
}

Step 4: Test it out!

  1. Build and run your project (dotnet run).

  2. Remember that Kestrel must allow the file through first, so verify your appsettings.json has MaxRequestLength set to a generous limit like 51200 (50MB).

  3. Log into the Umbraco Backoffice.

  4. Go to the Forms section and create a new form.

  5. Add a new question, and you will see “Custom File Upload (Max 5MB)” right next to the default fields in the picker!

  6. Add the form to a page on your site and try uploading a 6MB file. You will see your custom C# validation message successfully block the submission natively in the UI.

Here’s the View code I am using for the frontend:

@using Umbraco.Forms.Web
@using Umbraco.Extensions;
@Html.RenderUmbracoFormDependencies(Url)
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@{

    // 1. Get the Textstring value
    var headingText = Model.Value<string>("text");

    // 2. Get the Form Picker GUID
    // The Form Picker stores the unique GUID of the selected Umbraco Form
    var formId = Model.Value<Guid?>("fp");
}

<style>
    /* Responsive container to center content and add breathing room */
    .home-container {
        max-width: 900px;
        margin: 0 auto;
        padding: 4rem 1.5rem;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    }

    /* Modern, bold, responsive heading */
    .home-heading {
        font-size: clamp(2rem, 5vw, 3.5rem); /* Scales smoothly based on screen size */
        font-weight: 800;
        color: #111827;
        text-align: center;
        margin-bottom: 3rem;
        letter-spacing: -0.02em;
    }

    /* Card-style wrapper for the form to make it pop */
    .form-wrapper {
        background-color: #ffffff;
        padding: 3rem;
        border-radius: 16px;
        box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
        border: 1px solid #f3f4f6;
    }

    /* Mobile adjustments */
    @@media (max-width: 640px) {
        .home-container {
            padding: 2rem 1rem;
        }

        .form-wrapper {
            padding: 1.5rem;
            border-radius: 12px;
        }
    }
</style>

<div class="home-container">

    <!-- Render the Textstring property -->
    @if (!string.IsNullOrWhiteSpace(headingText))
    {
        <h1 class="home-heading">@headingText</h1>
    }

    <!-- Render the Umbraco Form -->
    <div class="form-wrapper">
        @if (formId.HasValue && formId.Value != Guid.Empty)
        {
            @*
               Standard way to render an Umbraco Form in v9/v10+ using ViewComponents.
               Note: You need to have Umbraco Forms installed for this to work.
            *@
            @await Component.InvokeAsync("RenderForm", new { formId = formId.Value, theme = "default" })
        }
    </div>

</div>

Two things to Note:

Right now, you have built a system with two layers of defense (the “Generous Server, Strict Application” pattern we discussed earlier):

  1. The Server Limit (50 MB): Configured in appsettings.json (Kestrel) and web.config (IIS). This is the Outer Gate.

  2. The Umbraco Forms Limit (5 MB): Configured in your new CustomFileUpload.cs file. This is the Inside Bouncer.

Scenario A: User uploads a 15 MB file

  • The Outer Gate says: “15 MB is less than 50 MB. Come on in.”

  • The Inside Bouncer says: “Wait, 15 MB is over my 5 MB limit!”

  • Result: The user stays on the page and sees your friendly Umbraco Forms validation error. (Perfect!)

Scenario B: User uploads a 60 MB file

  • The Outer Gate says: “60 MB is over my 50 MB limit! EMERGENCY SHUTDOWN!”

  • Kestrel instantly kills the network connection. The file never reaches Umbraco Forms, and your custom C# code never executes.

  • Result: The site throws the BadHttpRequestException and the user gets an ugly browser crash screen.

How do you prevent the 50MB crash?

To stop that crash from happening on massive files, you have to implement one (or both) of the safety nets we covered earlier:

1. The Client-Side JavaScript (Highly Recommended) We can have a script that listens to the change event on the document. By adding this script to your site, the browser checks the file size before the user can even click the “Submit” button. If they select a 60 MB file, the JavaScript pops up an alert and clears the input, meaning the massive file is never sent to the server in the first place. Here’s a sample code you can try on FieldType.CustomFileUpload.cshtml


@* Component-Specific Client-Side Validation *@
<script>
    document.addEventListener('DOMContentLoaded', function() {

        // Target this specific file input by its unique Umbraco Form ID
        var fileInput = document.getElementById('@Model.Id');

        if (fileInput) {
            fileInput.addEventListener('change', function() {

                // Set to 5 MB (5 * 1024 * 1024) to match your C# backend limit
                var maxSizeBytes = 5242880;
                var totalSize = 0;

                // Ensure files were actually selected
                if (!this.files || this.files.length === 0) return;

                // Loop through selected files (handles the 'multiple' attribute safely)
                for (var i = 0; i < this.files.length; i++) {
                    totalSize += this.files[i].size;
                }

                // If the file exceeds 5MB, block it instantly in the browser
                if (totalSize > maxSizeBytes) {
                    var limitInMB = (maxSizeBytes / (1024 * 1024)).toFixed(1);

                    alert('The selected file is too large. Please keep it under ' + limitInMB + ' MB.');

                    // Clear the input so the massive file is never sent to Kestrel
                    this.value = '';
                }
            });
        }
    });
</script>

2. The Global Exception Handler (The Last Resort) In case a malicious bot bypasses your JavaScript and forces a 2 GB upload directly to your server, you need the IExceptionHandler in your Program.cs. This catches Kestrel’s “Emergency Shutdown” exception globally and gently redirects the user to a static error page, ensuring your website never looks broken to the public.

Hope it helps!