Serenity 10.2.0 Release Notes (2026-02-16)

These release notes detail the significant changes made in Serenity and StartSharp from version 10.1.0 to 10.2.0. For a complete list of changes, please refer to the Serenity Change Log.

New Serenity Developer Tools Extension for Chrome

We are excited to introduce the Serenity Developer Tools extension for Chrome. This browser extension is designed to help developers inspect and debug Serenity applications more efficiently.

Key features include:

  • Inspect Serenity grid state, columns, and data
  • View and debug widget registrations
  • Examine dialog and editor configurations
  • Scroll selected grid elements into view for easier debugging

The extension can be downloaded from the Chrome Web Store. For more information, see the README in the Serenity repository.

New DENY Special Permission

A new special permission key DENY has been introduced that denies access to everyone, including super admins. This joins the existing special permission keys * (public/unrestricted access) and ? (any logged-in user).

To make these easier to reference, a new SpecialPermissionKeys class has been added:

using Serenity;

// SpecialPermissionKeys contains:
//   "*"   - SpecialPermissionKeys.Public  (public/unrestricted access)
//   "?"   - SpecialPermissionKeys.LoggedIn (any authenticated user)
//   "DENY" - SpecialPermissionKeys.Deny    (deny all access, even super admins)

// Example: deny access to a page or service for everyone
[PageAuthorize(SpecialPermissionKeys.Deny)]
public class MyRestrictedPage : Controller
{
    // This page is inaccessible to everyone
}

The DENY permission is particularly useful when you have a page or service that should not be accessible in certain environments or configurations. For example, you can use it to conditionally disable features based on configuration:

if (featureDisabled)
{
    // Dynamically return the DENY permission to block access
    return SpecialPermissionKeys.Deny;
}

File Read Access Control (Experimental)

We have added new infrastructure for controlling file read access, currently used by the experimental SecureUploadFileResponder. This allows you to secure file downloads by checking entity-level permissions in addition to simple file-path-based permissions.

FileReadAccessAttribute

A new attribute that can be placed on row properties to specify file read access control:

public class MyFileRow
{
    // Restrict file access to users with a specific permission
    [FileReadPermission("MyModule:FileAccess")]
    public string FilePath { get; set; }

    // Allow public access to the file
    [FileReadPermission(SpecialPermissionKeys.Public)]
    public string PublicFilePath { get; set; }

    // Require bypass permission (default) for admin override
    [FileReadPermission("MyModule:RestrictedFile", AllowBypass = true)]
    public string RestrictedFilePath { get; set; }
}

FileReadAccessSettings

The FileReadAccessSettings class provides configuration options for file read access control in appsettings.json:

{
    "FileReadAccess": {
        "BypassPermission": "Administration:General",
        "DefaultPermission": "*",
        "PathPermissions": "^public/:*;^temporary/:*;^uploads/:MyModule:FileAccess",
        "ReturnForbidResult": false
    }
}

Key configuration options:

  • BypassPermission: A permission that bypasses all file read access checks (e.g., for administrators)
  • DefaultPermission: Permission to check when no FileReadAccessAttribute is present (default is "*")
  • PathPermissions: Regex-based path matching patterns with associated permissions; evaluated in order, first match wins. Format: "pattern:permission" entries separated by semicolons
  • MissingMetadataPermission: Permission to check when file metadata is missing or cannot be read
  • ReturnForbidResult: Whether to return 403 (Forbidden) instead of 404 (Not Found)

SecureUploadFileResponder

A new experimental SecureUploadFileResponder in Serenity.Pro.Extensions that checks for related entity access and optionally permissions while returning files from FilePage/Read endpoints. Instead of granting access to all files based on a single permission, the secure responder:

  1. Checks path-based permissions configured in FileReadAccessSettings.PathPermissions
  2. If the path doesn't match any configured pattern, it looks up the row that owns the file
  3. Checks entity-level access control (read permission of the row)
  4. Optionally validates FileReadAccessAttribute on the row property

To use the secure responder in Startup.cs:

// Register the SecureUploadFileResponder instead of the default file responder
services.AddSingleton<IFileResponder, SecureUploadFileResponder>();

This is currently experimental and may see further refinements in upcoming releases.

UploadPathHelper Improvements

TemporaryFilePrefix and IsTemporaryFile

Instead of hardcoding the "temporary/" prefix for temporary upload files, we have standardized it:

// Instead of checking filename.StartsWith("temporary/")
if (UploadPathHelper.IsTemporaryFile(filename))
{
    // File is a temporary upload
}

// Instead of "temporary/" + Guid.NewGuid()
var tempPath = UploadPathHelper.TemporaryFilePrefix + Guid.NewGuid().ToString("N");

Thumbnail Metadata Improvements

We have significantly improved the thumbnail handling system:

  • New thumbnail filename constants: ThumbBaseSuffix ("_t"), ThumbExtension (".jpg"), SizedThumbFormat ("_t{0}x{1}"), and MetaFileExtension (".meta") for consistency.
  • GetThumbnailName now supports sized thumbnails: You can pass optional width/height parameters to generate thumbnails with specific dimensions:
// Old behavior (default thumb with _t suffix)
var thumb = UploadPathHelper.GetThumbnailName("uploads/file.jpg");
// Returns: uploads/file_t.jpg

// New: sized thumbnail
var sizedThumb = UploadPathHelper.GetThumbnailName("uploads/file.jpg", 100, 200);
// Returns: uploads/file_t100x200.jpg
  • TryParseThumbSuffix now returns full base paths including the folder, not just the filename. This makes it easier to locate the primary file from a thumbnail path.
  • New FileMetadataKeys: Added IsThumbnail and PrimaryFileExtension metadata keys. Thumbnails are now tagged with metadata:
    • IsThumbnail = "true" — marks the file as a thumbnail
    • PrimaryFileExtension — stores the extension of the primary file (e.g., ".jpg", ".png")

This helps in reliably finding the primary file from a thumbnail, even for legacy thumbnails that lack metadata.

New Helper Methods in UploadStorageExtensions

  • GetPrimaryFileFromThumb: Given a thumbnail path, returns the primary file path by checking metadata and falling back to legacy conventions.
  • GetThumbnailFiles: Given a source file path, returns all associated thumbnail files. This is now used internally by DiskUploadStorage during copy/delete operations.

jQuery 4.0.0 Update

jQuery has been updated to version 4.0.0 (served via Serenity.Assets for compatibility).

Note: jQuery 4.0.0 includes several breaking changes. Most notably, $.isArray, $.isFunction, $.isNumeric, $.isPlainObject, and several other deprecated methods have been removed. If your project uses these methods directly, you should either:

  • Update your code to use native alternatives (e.g., Array.isArray() instead of $.isArray())
  • Pin jQuery to version 3.7.0 via npm if you encounter issues

The jQuery version served via Serenity.Assets is 4.0.0, but you can continue using 3.7.0 by installing it via npm and configuring your import map accordingly.

Removal of Unused Global Type Helpers

Several unused global type helpers (Action, Predicate, Func, IComparable, IComparer, IEquatable, IEqualityComparer, etc.) have been removed from corelib. These were legacy type aliases that sometimes clashed with other global type declarations.

If you were using any of these global types, you will need to define them locally or use the appropriate native TypeScript equivalents.

Additionally, ESNext.Disposable has been added to the domwise tsconfig.json to satisfy @preact/signals-core requirements.

ServerTypingsGenerator Improvements

The ServerTypingsGenerator now has improved package name resolution:

  • When encountering an assembly name like Company.Package, it now tries more package name patterns:
    • @company/package (scoped npm style)
    • company.package (dot-separated style)
  • It parses package.json files for assembly-to-package name mappings, making it more likely to find the correct npm package for a given assembly.
  • An AssemblyToPackageName dictionary is also available for manual mappings, but the automatic parsing covers most common cases.

CSharpSyntaxRules.EscapeIfKeyword

A new EscapeIfKeyword helper has been added to CSharpSyntaxRules to avoid generating C# identifiers that conflict with reserved keywords. This fixes issues when:

  • C# keywords like decimal, int, etc. are used as .cshtml file names or other identifier positions
  • Generator output now prefixes such names with @ and uppercases the first letter to conform to C# identifier syntax rules
// Previously generated code with keyword conflict:
public class decimal // Error: 'decimal' is a keyword

// Now correctly generated:
public class Decimal

initNProgress Helper

A new initNProgress function has been added to @serenity-is/corelib that automatically sets up NProgress (the top-of-page progress bar) to show during AJAX requests:

import { initNProgress } from "@serenity-is/corelib";

// Initialize NProgress to show during AJAX requests
// Automatically shows after 200ms delay on ajaxStart, hides on ajaxStop
initNProgress(nprogress);

This is now used in the StartSharp pro-theme.ts script init instead of manual NProgress setup, simplifying the initialization code:

// Before (in pro-theme.ts):
const NProgress = (globalThis as any).NProgress;
if (NProgress && NProgress.start && NProgress.done && typeof document !== 'undefined') {
    let npt: number;
    Fluent.on(document, "ajaxStart", function () {
        clearTimeout(npt);
        npt = setTimeout(NProgress.start, 200);
    });
    Fluent.on(document, "ajaxStop", function () {
        clearTimeout(npt);
        NProgress.done();
    });
}

// After:
import { initNProgress } from "@serenity-is/corelib";
if (!initNProgress()) {
    setTimeout(initNProgress, 0);
}

If NProgress is already available as a global (e.g., loaded via the global bundle), initNProgress() works without any arguments. Otherwise, you can pass the NProgress instance explicitly.

AutoColumnWidthMixin CSP Fix

The AutoColumnWidthMixin has been reworked to properly handle strict Content Security Policy (CSP) environments. Previously, it constructed column cell content using HTML strings via innerHTML, which could violate CSP script-src or style-src directives.

The fix changes the approach to build cell nodes directly using DOM APIs instead of HTML string concatenation:

// Before (using HTML strings - CSP issue):
sb.push('<div class="slick-cell autocolumnwidthcalc">');
sb.push(childHtml);
// ...
row.innerHTML = sb.join('');

// After (using DOM APIs - CSP safe):
const node = document.createElement('div');
node.classList.add('slick-cell', 'autocolumnwidthcalc');
applyFormatterResultToCellNode(ctx, fmtResult, node);
// ...
row.append(...cellNodes);

This also makes it compatible with enableHtmlRendering = false (the new default since 9.0.0).

Header Menu Modal Z-Index Fix

Fixed an issue where the header menu dropdown appeared under modal dialogs. Since Bootstrap modals have a z-index of 1055 by default, the header menu (which had no explicit z-index) would be hidden behind the modal overlay.

/* Fix: added z-index to header menu dropdown */
.s-header-menu.dropdown-menu {
    z-index: 9999;
}

Bug Fixes (10.1.1 through 10.1.6)

The following bug fixes from the 10.1.x patch releases are also included in 10.2.0:

PageAuthorizeAttribute Equality Fix (10.1.1)

Fixed a StackOverflowException in PageAuthorizeAttribute's default Equals implementation when permission keys are the same for the action and controller, causing Swagger to fail. Equals and GetHashCode are now properly overridden.

New GridUtils.addQuickSearch Method (10.1.2)

A new GridUtils.addQuickSearch method with named arguments has been introduced, replacing the deprecated addQuickSearchInput and addQuickSearchInputCustom methods:

import { GridUtils } from "@serenity-is/corelib";

GridUtils.addQuickSearch(grid, container, {
    field: "Name",
    placeholder: "Search...",
    onSearch: (value, done) => {
        // custom search logic
        done();
    }
});

Shift+Wheel for Horizontal Scrolling (10.1.2)

SleekGrid now handles Shift+Wheel for horizontal scrolling.

CheckTreeEditor/CheckLookupEditor Fix (10.1.2)

Fixed formatting issues with CheckTreeEditor/CheckLookupEditor and the quick search indicator.

Sortable(false) Fix (10.1.3)

Fixed an issue where a column with Sortable(false) was incorrectly included in the server-side generated sorted column list.

PropertyProcessor: DefaultSummaryType (10.1.4)

Added a DefaultSummaryType setting to PropertyProcessorOptions that allows overriding the default aggregate type for numeric columns:

services.Configure<PropertyProcessorOptions>(options =>
    options.DefaultSummaryType = SummaryType.None);

This sets the default to None but still allows users to choose an aggregate type via the column header menu.

Fixed an issue where the summary footer value was not shown the first time when no columns have summaries and one is set via the column header menu.

DateOnly Field Fix (10.1.5)

Fixed an issue with DateOnly field due to an extra quote in the format string. Also reverted Sergen to generate DateTime fields as before for SQL date columns, as DateOnlyField is not yet stable.

Header Filter Dropdown Fix (10.1.6)

Fixed an issue where header filter dropdown contents were not emptied before appending new search results.

Validation Rule Disposal Fix

Added a disposing listener to the target element during addValidationRule to resolve an issue where editors with custom validation rules were disposed in inline grid editors while validation callbacks were still executing. Also added null coalescing to DateEditor and DateTimeEditor get_value methods for this.domNode? to avoid similar issues.

MVC File Generation with C# Keywords

Fixed an issue with generated MVC files when C# keywords like decimal are used as .cshtml file names. The generator now properly escapes these via CSharpSyntaxRules.EscapeIfKeyword.

StartSharp Specific Changes

  • SecureUploadFileResponder is now integrated in Startup.cs (experimental)
  • initNProgress used in pro-theme.ts instead of manual NProgress setup
  • SpecialPermissionKeys used in OpenIdScopePermissionWrapper
  • Header menu z-index fixed for modal dialog compatibility
  • AutoColumnWidthMixin CSP fix applied
  • GridUtils.addQuickSearch used in relevant grids

Package Updates

Updated NPM packages:

  • @serenity-is/tsbuild to 10.1.6
  • @preact/signals-core to 1.12.2 / 1.13.0
  • @preact/signals to 2.6.2
  • preact to 10.28.3
  • @tiptap to 3.18.0 / 3.19.0
  • vitest to 4.0.18
  • esbuild to 0.27.3
  • jspdf to 4.1.0
  • jsdom to 28.0.0
  • @floating-ui/dom to 1.7.5
  • glob to 13.0.1

Updated NuGet packages:

  • System/ASP.NET Core packages to 10.0.3
  • Microsoft.Build to 18.3.3
  • FluentMigrator to 8.0.1
  • MailKit to 4.15.0
  • Microsoft.Data.SqlClient to 6.1.4
  • Oracle.ManagedDataAccess.Core to 23.26.100
  • coverlet.collector to 8.0.0
  • Selenium.WebDriver to 4.40.0
  • CSharpMinifier to 2.2.0

Upgrading to 10.2.0

  1. Update NuGet packages to 10.2.0
  2. Update npm packages:
    • @serenity-is/tsbuild to 10.1.6
  3. Review jQuery usage: If you use $.isArray, $.isFunction or other deprecated jQuery methods, update them to native alternatives or pin jQuery to 3.7.0 via npm
  4. Review global type helpers: If you were using the removed global type helpers (Action, Predicate, etc.), define them locally or use native TypeScript equivalents
  5. Consider using initNProgress: If you have custom NProgress setup code, you can replace it with initNProgress() from @serenity-is/corelib
  6. Review file access security: If you use custom file serving, consider the new SecureUploadFileResponder and FileReadAccessSettings (experimental)
  7. Replace "temporary/" hardcoding: Use UploadPathHelper.TemporaryFilePrefix and UploadPathHelper.IsTemporaryFile() instead
  8. Check for C# keyword identifiers: If you have .cshtml files or identifiers using C# keywords (like decimal.cshtml), they will now be properly escaped
  9. Review CSP compatibility: If you have a strict CSP, the AutoColumnWidthMixin fix ensures grid auto-column-width functionality works without unsafe-inline