Blazor-ApexGrid is a Blazor data grid component that you configure entirely in C#. You declare <ApexGrid> in Razor, hand it a collection of your own model, and describe the columns as GridColumn<TItem> objects rather than a JavaScript configuration object. It installs as a single NuGet package that carries the grid engine inside it, so there is no npm, no bundler and no script tag.

dotnet add package Blazor-ApexGrid
@using Blazor_ApexGrid.Components
@using Blazor_ApexGrid.Models
@rendermode InteractiveServer

<ApexGrid TItem="Person"
          Data="people"
          Columns="columns"
          Height="360px" />

@code {
    private List<GridColumn<Person>> columns = new()
    {
        new() { Key = "name", HeaderText = "Name", Sort = true, Filter = true },
        new() { Key = "age", HeaderText = "Age", Type = GridDataType.Number, Sort = true },
        new() { Key = "role", HeaderText = "Role", Filter = true }
    };

    private List<Person> people = new()
    {
        new() { Name = "Ada Lovelace", Age = 36, Role = "Mathematician" },
        new() { Name = "Alan Turing", Age = 41, Role = "Computer Scientist" }
    };

    public class Person
    {
        public string Name { get; set; } = "";
        public int Age { get; set; }
        public string Role { get; set; } = "";
    }
}

That is a complete, working grid with sorting and filtering on. Version 1.0.0 multi-targets .NET 8, 9 and 10, and runs in Blazor Server and Blazor WebAssembly.

CustomerTextMRRCurrencySeatsNumberActiveBooleanRenewalDateGrowthPercent
Acme Inc$8,420182Mar 1, 2027+12%
Linear$7,280140Nov 15, 2026+8%
Vercel$5,94096-Sep 30, 2026-3%
Notion$4,820210Jan 20, 2027+21%
Stripe$4,26064Dec 5, 2026+5%
Figma$3,840120-Aug 18, 2026-6%
Retool$3,21045Feb 28, 2027+14%
Column types inferred automatically from the data

Columns Are Objects, Not Strings

A column is a GridColumn<TItem> with typed properties: Key binds it to a row field, Type picks the renderer and editor from thirteen values, and Sort, Filter, Editable, Pinned and Resizable are real booleans and enums. Leave Type off and the grid infers it from the data, as it does for the columns here.


Sort, Filter and Search

Turn them on per column with Sort and Filter, then drive them from C# when you need to: SortAsync takes GridSortExpression values with a SortingDirection enum, FilterAsync takes GridFilterExpression values, and SetQuickFilterAsync wires a search box across every column. Multi-column and tri-state sorting are one configuration object away.

Marcus VegaAcme Inc$8,420Enterprise
Sara PatelLinear$7,280Pro
Priya NairVercel$5,940Enterprise
Yuki TanakaNotion$4,820Pro
Olivia ReedStripe$4,260Pro
Eli MorganFigma$3,840Team
Theo ParkWebflow$2,980Team
Iris HaleFramer$2,450Starter
8 of 8 rows · sorted by mrr

TaskOwnerDueEffortDone
Ava MorganJul 2, 20265-
API integrationLiam ChenJul 9, 20268-
QA passNoah PatelJul 14, 20263
Docs updateEmma DavisJul 18, 20262-
Release prepAva MorganJul 25, 20265-
Click any cell to edit · editors match the column type

Inline Editing with Undo and Redo

Editing opens the editor that matches the column type, and committed edits land on an undo stack. EditMode chooses whether a cell commits on its own or the whole row commits as a batch, EditTrigger chooses click or double click, and OnHistoryChanged tells your own toolbar when undo and redo became available.


Virtualized Rows

Only the rows in the viewport are in the DOM, so the row count stops being the thing that decides whether the page is usable. This is also why the grid needs a bounded Height: virtualization has to know what the viewport is before it can window anything.

#AccountCustomerRegionValueStatus
1CUS-255SAiden FisherNorth America$1,041Active
2CUS-255THugo TanLATAM$1,178Trial
3CUS-255UOmar LoweEurope$1,315Past due
4CUS-255VBella EngelMEA$1,452Churned
5CUS-255WIvy SaitoAPAC$1,589Active
6CUS-255XPia KleinNorth America$1,726Trial
7CUS-255YCarlos DempseyLATAM$1,863Past due
8CUS-255ZJonas RaoEurope$2,000Churned
9CUS-2560Quinn JenkinsMEA$2,137Active
10CUS-2561Dahlia ChoAPAC$2,274Trial
11CUS-2562Kai PowellNorth America$2,411Past due
12CUS-2563Rafa IversonLATAM$2,548Churned
13CUS-2564Elena BrennanEurope$2,685Active
14CUS-2565Luna OrtizMEA$2,822Trial
15CUS-2566Sage HayesAPAC$2,959Past due
16CUS-2567Finn AbbottNorth America$3,096Churned
17CUS-2568Mira NakaiLATAM$3,233Active
18CUS-2569Tariq GomezEurope$3,370Trial
50,000 rows · only 18 in the DOM · scroll to explore

One Package, No Toolchain

The grid engine and its dependencies are bundled as a self-contained ES module inside the NuGet package, served from _content/Blazor-ApexGrid/. No npm, CDN or script tag.

Typed From Top to Bottom

ApexGrid<TItem> with GridColumn<TItem> columns and typed configuration classes, so a wrong option name or value is a build error.

Thirteen Column Types

Number, String, Boolean, Select, Rating, Date, Image, Currency, Avatar, Badge, Progress, Sparkline and Status, each with a matching renderer and editor.

Editing with History

Cell or row editing, click or double click, and an undo stack that defaults to 100 committed edits.

Tree Data and Expansion

Nested rows derived from a path field on a flat collection, plus row expansion with an HTML detail template.

State Persistence

GetStateAsync and SetStateAsync move a whole view, sort and filters and pins and widths, in and out as JSON.

29 Typed Events

Every grid event is an EventCallback with a typed payload that hands back your own TItem rather than a dictionary.

Server and WebAssembly

Async IJSRuntime interop imported after first render, so neither hosting model is a special case. Multi-targets .NET 8, 9 and 10.

Add a Data Grid to a Blazor App

Two steps, and neither involves your build. Start with the package:

dotnet add package Blazor-ApexGrid

Then add the namespaces to _Imports.razor. Note the underscores: the NuGet package is Blazor-ApexGrid but the C# namespace is Blazor_ApexGrid, because a hyphen is not legal in an identifier.

@using Blazor_ApexGrid.Components
@using Blazor_ApexGrid.Models

There is no third step. Nothing is registered in Program.cs, and nothing is copied into wwwroot. The grid engine and its styles are a static web asset served from _content/Blazor-ApexGrid/, which the framework wires up for you.

Why Does My Blazor Grid Render Blank?

There are two causes, and they look identical.

The render mode. From .NET 8, Blazor Web Apps render statically by default, and a component that needs JavaScript interop has to opt into interactivity. Set the render mode on the page or the component:

@rendermode InteractiveServer

InteractiveWebAssembly and InteractiveAuto work equally well. There is no error and nothing in the console, which is what makes this expensive to diagnose.

The height. The grid virtualizes rows, so it needs a bounded height to know what a viewport is. Height defaults to 400px and takes any CSS value, but if you pass a percentage whose parent has no height of its own, it resolves to zero and the grid renders as nothing.

Declare Columns as C# Objects

A column is a GridColumn<TItem>. Key binds it to a row property under that property's serialized name, which is camelCase: a Name property is "name" and a GrossValue property is "grossValue". That one convention also governs the tree PathKey and the {field} tokens in a detail template.

Type selects the built-in renderer and editor from thirteen values: Number, String, Boolean, Select, Rating, Date, Image, Currency, Avatar, Badge, Progress, Sparkline and Status. It is nullable and omitted when null, so leaving it off lets the grid infer the type from your data, and setting it is how you overrule that.

For anything the C# model does not cover, AdditionalConfig is a [JsonExtensionData] escape hatch that passes keys through to the engine untouched, including function-valued renderers supplied as JavaScript function strings. It is an escape hatch by design: the keys are not checked by the compiler.

Edit Cells with Undo and Redo

Editing is opt-in twice, which is deliberate: once on the grid through GridEditingConfiguration, and once per column through Editable = true. Turning it on at the grid alone does nothing, so there is no way to make every column editable by accident.

<ApexGrid TItem="Person" @ref="grid"
          Data="people" Columns="columns"
          Editing="editing"
          OnCellValueChanged="OnEdited"
          OnHistoryChanged="OnHistory" />

@code {
    private ApexGrid<Person>? grid;

    private readonly GridEditingConfiguration editing = new()
    {
        Enabled = true,
        Mode = EditMode.Cell,
        Trigger = EditTrigger.DoubleClick,
        History = new() { Enabled = true }
    };

    private void OnEdited(GridCellValueChangedEventArgs<Person> e)
        => Console.WriteLine($"{e.Key} on row {e.RowIndex} is now {e.Value}");

    private void OnHistory(GridHistoryChangedEventArgs e)
        => (canUndo, canRedo) = (e.CanUndo, e.CanRedo);
}

With History enabled, committed edits go on an undo stack that defaults to 100 entries. Drive it from your own toolbar with UndoAsync, RedoAsync, CanUndoAsync and CanRedoAsync, and keep the buttons in the right state from OnHistoryChanged.

Nested Rows Without a Nested Model

Tree data does not ask you to reshape your query. The collection stays flat and the grid derives the hierarchy from a path array on each row, so the only thing you supply is which field holds it.

<ApexGrid TItem="Employee"
          Data="employees"
          Columns="columns"
          Tree="tree" />

@code {
    private readonly GridTreeConfiguration tree = new()
    {
        Enabled = true,
        PathKey = "path",
        DefaultExpanded = true
    };

    public class Employee
    {
        public string Name { get; set; } = "";
        public string Title { get; set; } = "";
        // ["Adrian"], ["Adrian", "Bryan"], ["Adrian", "Bryan", "Cara"]
        public string[] Path { get; set; } = [];
    }
}

DefaultExpanded takes false, true or a depth number, and GroupColumnKey chooses which column carries the chevron and the indentation. There are no callbacks to implement.

Row expansion is the separate case where each row opens a detail panel rather than child rows. Set DetailTemplateHtml on a GridExpansionConfiguration to an HTML string whose {field} tokens are replaced with that row's values, HTML-escaped on the way in.

Save and Restore a View

A user who sorts three columns, hides two more and pins the first has built something worth keeping. GetStateAsync captures it as JSON you can put anywhere: a database column, local storage, a saved-views table.

// Capture the whole view: sort, filters, paging, column order, pins, widths.
var snapshot = await grid.GetStateAsync();
await SaveForUserAsync(snapshot);

// Restore it later. Only the slices present in the snapshot are applied.
await grid.SetStateAsync(snapshot);

// A machine-readable description of the grid: columns, available
// operations and current state.
var schema = await grid.GetSchemaAsync();

SetStateAsync applies only the slices a snapshot actually contains, so a partial snapshot is valid and you can persist just the parts you care about.

The rest of the grid is drivable the same way, through the component reference rather than by rebuilding parameters:

await grid.SortAsync(new GridSortExpression
{
    Key = "name",
    Direction = SortingDirection.Ascending
});

await grid.FilterAsync(new GridFilterExpression
{
    Key = "role",
    Condition = "contains",
    SearchTerm = "Engineer"
});

await grid.SetQuickFilterAsync(searchBox);
await grid.NextPageAsync();
await grid.PinColumnAsync("name", PinPosition.Start);

What This Package Does Not Cover

Blazor-ApexGrid 1.0.0 bundles the community ApexGrid core. That covers everything on this page, and it leaves the enterprise analytics tier outside the C# surface: row grouping, column aggregations, pivoting, the server-side row model, the Excel-style set filter, the advanced filter builder, the columns tool panel, range selection with a status bar, integrated charts, cell formulas, the AI toolkit, the context menu and XLSX export.

If your application is a reporting surface built on grouping and pivoting, that is the honest answer before you start rather than after. Those features are real and supported in the JavaScript grid today, and reaching them from .NET means driving apex-grid-enterprise through your own interop rather than this package.

Two smaller boundaries worth knowing. Cell and header templates are JavaScript function strings passed through AdditionalConfig, not Razor render fragments, so a template cannot contain Blazor components.

And the grid decides whether to re-push your data by comparing the collection's identity and its count. Adding or removing rows changes the count, so the grid redraws on the next render, and reassigning the collection changes its identity, so that works too. Editing a property on a row already in the list changes neither, and that edit will not appear. Call RefreshAsync when you have changed what is inside a row rather than which rows there are.

Blazor Data Grid FAQ

What is Blazor-ApexGrid?

Blazor-ApexGrid is a Blazor wrapper for the ApexGrid data grid that exposes it as a typed ApexGrid<TItem> Razor component. Columns are C# objects, configuration is C# classes and enums, and all 29 grid events arrive as strongly typed EventCallbacks. The grid engine is bundled inside the NuGet package as a self-contained ES module, so there is no script tag, CDN reference or npm step.

How do I install the Blazor data grid?

Run dotnet add package Blazor-ApexGrid, then add @using Blazor_ApexGrid.Components and @using Blazor_ApexGrid.Models to your _Imports.razor. There is no service to register and no asset to copy: the JavaScript and styles are served automatically from _content/Blazor-ApexGrid/.

Which .NET versions does Blazor-ApexGrid support?

Version 1.0.0 multi-targets .NET 8, .NET 9 and .NET 10, so a .NET 8 LTS project can adopt it without moving framework.

Does the Blazor data grid work in Blazor Server?

Yes. Every interop call goes through IJSRuntime and IJSObjectReference, and the module is imported in OnAfterRenderAsync rather than during prerendering, so Blazor Server, Blazor WebAssembly and InteractiveAuto all work without a special case.

Why does my Blazor grid render blank?

Usually one of two things. First, the render mode: from .NET 8 a Blazor Web App renders statically by default, so a component that needs JavaScript interop must opt into InteractiveServer, InteractiveWebAssembly or InteractiveAuto. Second, height: the grid virtualizes rows, which needs a bounded height, so the Height parameter must resolve to a real value rather than collapsing to zero.

How do column keys map to my C# model?

A column Key is the row property under its serialized name, which is camelCase. A Name property is referenced as "name" and a GrossValue property as "grossValue". The same applies to the tree PathKey and to the {field} tokens in a detail template.

Do I need a license key for the Blazor data grid?

The package exposes no license key property, and the grid runs with no key. ApexGrid is dual licensed: the Community License is free for individuals, non-profits, educators and organizations under 2 million USD in annual revenue, and a Commercial License applies above that threshold. Embedding it in a product used by other people needs the OEM License at any revenue.

Does the Blazor package include row grouping, pivoting or spreadsheet formulas?

No. Blazor-ApexGrid 1.0.0 bundles the community ApexGrid core, so the enterprise analytics tier is not reachable from the C# surface. That tier covers row grouping, column aggregations, pivoting, the server-side row model, the Excel-style set filter, the advanced filter builder, the columns tool panel, range selection, integrated charts, cell formulas, the AI toolkit, the context menu and XLSX export.

Can I save and restore the grid state?

Yes. GetStateAsync returns a JSON-safe snapshot of sort, filters, paging, column order, pins and widths, which you can persist anywhere, and SetStateAsync restores it. Only the slices present in a snapshot are applied, so a partial snapshot is valid. GetSchemaAsync returns a machine-readable description of the grid, which is useful for driving a view editor or an AI layer.

Ready to build a grid in C#?

One dotnet add package, two using directives, and an interactive render mode. The running sample carries every feature on this page.

See the live sample