By default, MainGrid and SubGrid render whatever column set the active Dataverse view returns. The GridColumn component lets you take explicit control: when one or more GridColumn children are declared, the grid renders only those columns, in the declared order, and the server-side projection is rewritten to fetch exactly the declared set. Use this to slim a grid down to the columns that matter for a specific page, reorder them, customise width / alignment, supply per-cell templates, or surface columns that aren't in the underlying view — without editing the Dataverse view itself.
Live Demo
A MainGrid over the contact table mixing a GridTemplateColumn with three regular GridColumn children. The first cell is synthetic — composes an initials avatar plus a bold name from raw firstname + lastname values pulled into the projection via DependsOn. The remaining three demonstrate per-cell ChildContent templates on bound columns: a mailto: link, a phone number with an inline icon, and a colour-coded age badge that reads the raw IntValue via GetValueOrDefault<T>() for the threshold comparison. Click the gear icon and flip Editable on to see the template column's EditChildContent in action — the Full Name cell swaps from the avatar render to two side-by-side TextEdit editors bound to firstname + lastname.
Blazor example
You must be logged in to see the contact records in the grid below.
All Contacts
All Contacts
Page size
102050100
Editable
Full Name
Email
Phone
Age
AMAbagail Miller
-
-
-
APAbdul Pollich
-
-
-
APAbel Parisian
-
-
-
AKAbigale Kuvalis
-
-
-
APAdell Paucek
-
-
-
ARAdella Roob
-
-
-
AWAdolf Weber
-
-
-
ATAdonis Torphy
-
-
-
AGAgustin Goyette
-
-
-
ARAgustin Rau
-
-
-
React example
Basic Usage
Drop GridColumn components inside a GridColumns wrapper under the grid. Declared column names override the FetchXML's <attribute> projection — the server fetches the declared columns even if they aren't in the active view's column selection. The TableName parameter is optional in declared mode: when omitted, the grid's own TableName is used (no need to repeat it on every column).
Declaring any GridColumn children switches the grid into replace mode: declared columns are the entire rendered column set AND the entire fetched column set. View columns not declared here are dropped from both the render and the server's projection. If you want most view columns with a tweak or two, drop the wrapper entirely or build a custom view via CustomViewDefinitions.
Required Parameters
Each GridColumn declaration takes:
ColumnName — Required. Logical name of the column to project. Supports dot-notation for aliased linked-entity columns (primarycontactid.emailaddress1); the alias must already exist as a <link-entity> in the active view's FetchXML.
TableName — Optional. Logical name of the table whose metadata describes this column. When omitted, falls back to the grid's TableName — handy for keeping consumer markup terse on single-table grids.
Column Width
Use Width to override the view's stored width. Accepts any CSS length — pixels ("150px"), percentages ("20%"), or "auto". Without an explicit width, the grid falls back to the metadata-derived default and the table's natural distribution.
Use Align to override horizontal alignment for both the header and body cells. Numeric and money columns default to Align.End (right-aligned) via the metadata-driven fallback; boolean columns default to Align.Center. Set it explicitly when you want a non-default alignment, or to align a custom-templated cell consistently.
<!-- Numeric and money columns already end-align via the
metadata-driven fallback. Set Align explicitly when you
want a non-default alignment. -->
<GridColumns>
<GridColumn ColumnName="firstname" />
<GridColumn ColumnName="creditlimit" Align="Align.End" />
<GridColumn ColumnName="donotemail" Align="Align.Center" />
</GridColumns>
Linked-Entity Columns
Aliased linked-entity columns use dot-notation matching how the view's FetchXML aliases them. The alias must already be defined as a <link-entity> in the active view. Set TableName to the source table the alias points to so metadata resolution lands on the right entity. The server returns these columns with their parent-table-qualified localized display name (e.g. "Email (Primary Contact)") — no client-side humanisation required.
<MainGrid TableName="account">
<GridColumns>
<GridColumn ColumnName="name" />
<!-- Aliased linked-entity column: the dot-notation matches
how the column appears in the view's FetchXML alias.
TableName must point at the LINK-ENTITY's table so
metadata resolves against the right entity. -->
<GridColumn ColumnName="primarycontactid.emailaddress1"
TableName="contact" />
</GridColumns>
</MainGrid>
Per-Cell Templates
Supply ChildContent to render each cell with custom markup. The template receives GridRowContext — its PrimaryRecord property exposes the row's full TableRecord (typed column values, formatted values, permissions, all of it). Useful for conditional formatting (red text over a threshold), icons, badges, links, or anything else you can build from the row's record.
<GridColumns>
<GridColumn ColumnName="fullname" />
<GridColumn ColumnName="creditlimit">
<ChildContent>
@{
var raw = context.PrimaryRecord
.GetValueOrDefault<MoneyValue>("creditlimit")?.Value;
var over = raw is decimal d && d >= 100_000m;
}
<span style="color: @(over ? "var(--error-fill-rest)" : "inherit")">
@(context.PrimaryRecord.FormattedValues["creditlimit"] ?? "-")
</span>
</ChildContent>
</GridColumn>
</GridColumns>
Parent Record in SubGrids
When the grid is mounted as a SubGrid, GridRowContext.ParentRecord exposes the surrounding RecordContext's record (the SubGrid's parent). Reach for it inside a ChildContent template when conditional formatting needs to compare a row's column against the parent's — e.g. highlight contacts whose industrycode doesn't match the parent account's, or label rows owned by the parent's owner. null on a standalone MainGrid where there's no parent record.
<SubGrid RelationshipName="contact_customer_accounts">
<GridColumns>
<GridColumn ColumnName="fullname" TableName="contact" />
<GridColumn ColumnName="industrycode" TableName="contact">
<ChildContent>
@{
var rowIndustry = context.PrimaryRecord.FormattedValues
.GetValueOrDefault("industrycode");
var parentIndustry = context.ParentRecord?.FormattedValues
.GetValueOrDefault("industrycode");
var mismatch = parentIndustry is not null
&& rowIndustry is not null
&& rowIndustry != parentIndustry;
}
<span style="color: @(mismatch ? "var(--error-fill-rest)" : "inherit")">
@(rowIndustry ?? "-")
</span>
</ChildContent>
</GridColumn>
</GridColumns>
</SubGrid>
React parity
The React side surfaces the same surface as cellRenderer's parentRecord prop (CellRendererProps.parentRecord).
GridTemplateColumn
Use GridTemplateColumn when a column doesn't map to a single Dataverse column — combine multiple raw values into one cell, render a status badge keyed on several fields, or emit a pure-decoration actions cell with no data dependency at all. Unlike GridColumn, a template column has no ColumnName: declare the columns the renderer reads via DependsOn and the framework merges them into the FetchXML projection, so the data lands in ctx.Row.PrimaryRecord when your template runs. The ChildContent receives a GridTemplateColumnContext (use the Context="ctx" attribute to name it) that carries the row, the optional SubGrid parent, and a pre-built DependsOnMetadata lookup keyed by the column names you declared.
<MainGrid TableName="contact">
<Columns>
<GridColumns>
<!-- Combine firstname + lastname into a single "Full Name"
cell. DependsOn tells the framework which columns the
template reads so they're projected into the fetch.
SortBy points the header sort at lastname. -->
<GridTemplateColumn Title="Full Name"
DependsOn="@(new[] { "firstname", "lastname" })"
SortBy="lastname">
<ChildContent Context="ctx">
@{
var first = ctx.Row.PrimaryRecord
.GetValueOrDefault<StringValue>("firstname")?.Value ?? "";
var last = ctx.Row.PrimaryRecord
.GetValueOrDefault<StringValue>("lastname")?.Value ?? "";
}
<strong>@first @last</strong>
</ChildContent>
</GridTemplateColumn>
<GridColumn ColumnName="emailaddress1" />
</GridColumns>
</Columns>
</MainGrid>
Template column vs. GridColumn ChildContent
GridColumn ChildContent still customises a bound column — sorting, edit dispatch, and the column header all key off its single ColumnName. Reach for GridTemplateColumn only when there's no single underlying column to bind to (composite cells, decorative action cells, status badges combining multiple fields). Mixing the two is fine — they interleave in declared order across the grid's column set.
Decorative columns (no data dependency)
Skip DependsOn entirely when the cell renders pure-decoration content like inline buttons or icons. The framework still mounts the column at the right position in the rendered table; nothing is added to the server projection for that column.
<!-- Pure-decoration template column: no DependsOn, the renderer
doesn't read from the row. Useful for action buttons / icons. -->
<GridTemplateColumn Title="" Width="80px">
<ChildContent Context="ctx">
<FluentButton IconStart="@(new Icons.Regular.Size16.MoreHorizontal())"
OnClick="@(() => OpenContextMenu(ctx.Row.PrimaryRecord.Id))" />
</ChildContent>
</GridTemplateColumn>
Inline-Edit Templates
Pair a GridTemplateColumn's read-mode ChildContent with an EditChildContent to make the column editable when the grid's Editable toggle is on. The edit template receives a GridTemplateColumnEditContext — same row context as the read template (editCtx.Row.PrimaryRecord, the optional editCtx.Row.ParentRecord in a SubGrid, the editCtx.DependsOnMetadata lookup) plus an imperative editCtx.SetValue(columnName, value) helper.
EditChildContent fires when the grid is editable (AllowEdit="true" on the grid plus the user has flipped the Editable switch in the toolbar gear menu), the row isn't pending-delete, and the column itself declares an edit template. Decorative template columns that omit EditChildContent stay read-only even in edit mode — useful for actions columns that should always render the same buttons.
Pattern 1 — Drop-in standard editors
The most common case: the cell composes from multiple bound columns, and you want to let the user edit each one. Drop the matching <TextEdit> / <NumberEdit> / <ColumnEdit> editors inside EditChildContent — the framework cascades the row's primary record and matching EditContextValidator into that scope, so the editors self-register for dirty-tracking and validation exactly like they do under a bound <GridColumn>'s ChildContent. No extra wiring required.
<GridTemplateColumn Title="Full Name"
DependsOn="@(new[] { "firstname", "lastname" })"
SortBy="lastname">
<!-- Read-only template (existing). -->
<ChildContent Context="ctx">
<strong>
@(ctx.Row.PrimaryRecord.GetValueOrDefault<StringValue>("firstname")?.Value)
@(ctx.Row.PrimaryRecord.GetValueOrDefault<StringValue>("lastname")?.Value)
</strong>
</ChildContent>
<!-- Edit template: fires when the grid's Editable toggle is on
AND the row is mutable. Drop two `<TextEdit>` editors side
by side and they auto-wire to dirty-tracking and validation
exactly like they do under a bound `<GridColumn>`. -->
<EditChildContent Context="editCtx">
<FluentStack Orientation="Orientation.Horizontal" HorizontalGap="6"
VerticalAlignment="VerticalAlignment.Center">
<TextEdit ColumnName="firstname"
DisplayLabelWhenAvailable="false"
DisplayTooltipWhenAvailable="false" />
<TextEdit ColumnName="lastname"
DisplayLabelWhenAvailable="false"
DisplayTooltipWhenAvailable="false" />
</FluentStack>
</EditChildContent>
</GridTemplateColumn>
Pattern 2 — Custom widget with SetValue
When the editor surface doesn't map 1:1 to a Dataverse column — a single "First Last" text box that parses into two columns, a slider tied to multiple percent fields, a custom date-range picker writing start + end — render your own input and call editCtx.SetValue(columnName, value) for each affected column. The framework routes the write to the correct AliasedTableRecord (handling dot-notation link-entity column names) and fires the record's ValueChanged event so dirty-tracking and the row's pending-update queue pick the change up.
<GridTemplateColumn Title="Full Name"
DependsOn="@(new[] { "firstname", "lastname" })"
SortBy="lastname">
<ChildContent Context="ctx">
@{
var first = ctx.Row.PrimaryRecord.GetValueOrDefault<StringValue>("firstname")?.Value;
var last = ctx.Row.PrimaryRecord.GetValueOrDefault<StringValue>("lastname")?.Value;
}
<strong>@first @last</strong>
</ChildContent>
<EditChildContent Context="editCtx">
@{
var combined =
(editCtx.Row.PrimaryRecord.GetValueOrDefault<StringValue>("firstname")?.Value ?? "")
+ " "
+ (editCtx.Row.PrimaryRecord.GetValueOrDefault<StringValue>("lastname")?.Value ?? "");
}
<FluentTextField Value="@combined.Trim()" ValueChanged="@(next =>
{
// Split on the first space — anything after lands in lastname.
var parts = next.Split(' ', 2);
editCtx.SetValue("firstname", new StringValue { Value = parts[0] });
editCtx.SetValue("lastname", new StringValue { Value = parts.Length > 1 ? parts[1] : null });
})" />
</EditChildContent>
</GridTemplateColumn>
Validation on the imperative path
Standard editors dropped into EditChildContent register with the row's EditContextValidator automatically and participate in the framework's validate-before-save gate. The editCtx.SetValue path bypasses that pipeline — the framework can't validate values that didn't come from a registered editor. If your custom widget needs to enforce constraints, run them inside the ValueChanged callback before calling SetValue, or fall back to the drop-in editor pattern when possible.
In a SubGrid
Everything that applies to MainGrid works identically on SubGrid. Pair declared columns with toolbar buttons in the Buttons render fragment for a focused inline-edit experience scoped to the parent record's related collection — and combine with @@context.ParentRecord in cell templates for conditional formatting that keys off the SubGrid's parent.
ColumnName — Required. Logical name of the column; supports dot-notation for aliased linked-entity columns.
TableName — Optional. Logical name of the table whose metadata describes this column. Inherits the grid's TableName when omitted.
Width — Optional CSS length for the column's width.
Align — Optional horizontal alignment override (Align.Start, Align.Center, Align.End). Falls back to the metadata-driven default when omitted.
Title — Optional header label override. Falls back to the column's localized display name.
HeaderTooltip — Optional header tooltip — useful for explaining computed or aliased columns. (Named to avoid the case-insensitive collision with the inherited Tooltip bool parameter on TemplateColumn.)
Visible — Optional. false suppresses the column from the rendered table and from the server projection even when other declared columns are present. Defaults to true.
IsDefaultSortColumn / InitialSortDirection — Optional. Mark a declared column as the grid's initial sort. Sort direction defaults to ascending.
ChildContent — Optional per-cell template receiving GridRowContext. Renders inside each body cell when supplied; access the row via @@context.PrimaryRecord and (in a SubGrid) the parent via @@context.ParentRecord.
GridTemplateColumn Parameters
Id — Optional stable identifier. When omitted, the framework auto-generates one as __template-{index} based on the template column's position among the other templates. Pass an explicit id when your declarations may reorder dynamically.
Title — Required header label. No bound column metadata to derive a default from.
DependsOn — Optional. Logical Dataverse column names the renderer reads from the row. The framework merges these into the FetchXML projection so the data lands in ctx.Row.PrimaryRecord. Omitted / empty for decorative cells.
SortBy — Optional. Logical column name to sort by when the header is clicked. Omitted = header is not sortable.
Width — Optional CSS length for the column's width.
Align — Optional horizontal alignment override. Defaults to Align.Start (no metadata-driven default since there's no underlying column type).
Tooltip — Optional header tooltip text.
Visible — Optional. false suppresses the column from the rendered table AND drops its DependsOn entries from the server projection. Defaults to true.
ChildContent — Required per-cell template receiving GridTemplateColumnContext. Read the row via ctx.Row.PrimaryRecord (and in a SubGrid the parent via ctx.Row.ParentRecord); use ctx.DependsOnMetadata[name] for per-column metadata when the renderer needs it.
EditChildContent — Optional edit-mode template receiving GridTemplateColumnEditContext. Invoked instead of ChildContent when the grid is in inline-edit mode and the row is mutable. Same row context as the read template plus editCtx.SetValue(name, value) for imperative writes. See Inline-Edit Templates above for the two authoring patterns.
Blazor
GridColumns Class
Parameters
Name
Type
Default
Description
ChildContent
RenderFragment?
Consumer-declared GridColumn children. Rendered untouched — the surrounding grid's GridColumnRegistry cascading value reaches them and they self-register on init.
Name:ChildContent
Type:RenderFragment?
Description:Consumer-declared GridColumn children. Rendered untouched — the surrounding grid's GridColumnRegistry cascading value reaches them and they self-register on init.
ReactBlazor
GridColumn Class
Parameters
Name
Type
Default
Description
Align
Align
ChildContent
RenderFragment<GridRowContext>
ColumnName*
string
The logical name of the column to display; supports dot-notation for aliased link-entity columns.
ColumnOptions
RenderFragment?
Filtered
bool?
HeaderCellItemTemplate
RenderFragment<ColumnBase<GridRowContext>>?
HeaderCellTitleTemplate
RenderFragment<ColumnBase<GridRowContext>>?
HeaderTooltip
string?
HierarchicalToggle
bool
Index
int
InitialSortDirection
SortDirection
IsDefaultSortColumn
bool
MinWidth
string
PlaceholderTemplate
RenderFragment<PlaceholderContext>?
Sortable
bool?
SortBy
IGridSort<GridRowContext>?
TableName
string?
The logical name of the table that owns this column, used to look up column metadata for formatting and alignment. Optional in consumer-declared mode — when omitted, the parent FluentGridBase's table name is used (avoids repeating the table on every column).
Title
string?
Tooltip
bool
TooltipText
Func<GridRowContext, string>?
Visible
bool
Consumer-declared mode only: when false, the column is suppressed from the rendered table even if other declared columns reference it. Defaults to true.
Width
string?
Name:Align
Type:Align
Name:ChildContent
Type:RenderFragment<GridRowContext>
Name:ColumnName*
Type:string
Description:The logical name of the column to display; supports dot-notation for aliased link-entity columns.
Name:ColumnOptions
Type:RenderFragment?
Name:Filtered
Type:bool?
Name:HeaderCellItemTemplate
Type:RenderFragment<ColumnBase<GridRowContext>>?
Name:HeaderCellTitleTemplate
Type:RenderFragment<ColumnBase<GridRowContext>>?
Name:HeaderTooltip
Type:string?
Name:HierarchicalToggle
Type:bool
Name:Index
Type:int
Name:InitialSortDirection
Type:SortDirection
Name:IsDefaultSortColumn
Type:bool
Name:MinWidth
Type:string
Name:PlaceholderTemplate
Type:RenderFragment<PlaceholderContext>?
Name:Sortable
Type:bool?
Name:SortBy
Type:IGridSort<GridRowContext>?
Name:TableName
Type:string?
Description:The logical name of the table that owns this column, used to look up column metadata for formatting and alignment. Optional in consumer-declared mode — when omitted, the parent FluentGridBase's table name is used (avoids repeating the table on every column).
Name:Title
Type:string?
Name:Tooltip
Type:bool
Name:TooltipText
Type:Func<GridRowContext, string>?
Name:Visible
Type:bool
Description:Consumer-declared mode only: when false, the column is suppressed from the rendered table even if other declared columns reference it. Defaults to true.
Name:Width
Type:string?
ReactBlazor
GridTemplateColumn Class
Parameters
Name
Type
Default
Description
Align
Align?
Horizontal cell alignment override. When omitted, the cell defaults to start-aligned (no type-driven default since there's no underlying column type to dispatch off).
ChildContent*
RenderFragment<GridTemplateColumnContext>
Cell template — required. Receives a GridTemplateColumnContext carrying the row, optional SubGrid parent record (via GridRowContext.ParentRecord) and the pre-built GridTemplateColumnContext.DependsOnMetadata lookup.
DependsOn
IReadOnlyList<string>?
Logical names of the Dataverse columns the template reads from the row. The framework merges these into the FetchXML <attribute> projection so the data lands in GridRowContext.PrimaryRecord for the cell renderer to consume. null / empty = decorative column (no data dependency — actions, icons, etc.).
EditChildContent
RenderFragment<GridTemplateColumnEditContext>?
Optional editor template — when supplied AND the grid is in inline-edit mode AND the row is mutable (not pending-delete, not a pending-create row whose target metadata forbids create), this is rendered instead of GridTemplateColumn.ChildContent. Receives a GridTemplateColumnEditContext with the same row data as the read context plus a GridTemplateColumnEditContext.SetValue(System.String,PowerPortalsPro.Common.Models.ColumnValueBase) helper.
SortBy
string?
When set, clicking the header sorts the grid by this Dataverse column name. When omitted, the header is not sortable (most template columns derive from multiple sources and have no natural sort target).
Title*
string
Header label. Required — there's no bound column metadata to derive a default from.
Tooltip
string?
Optional header tooltip. Falls through to a plain header with no tooltip when omitted.
Visible
bool
True
When false, the column is suppressed from the rendered table AND its GridTemplateColumn.DependsOn columns are dropped from the server projection. Defaults to true.
Width
string?
Column width. Use a CSS length like '150px'. When omitted, the table distributes remaining space — same default as GridColumn.
Name:Align
Type:Align?
Description:Horizontal cell alignment override. When omitted, the cell defaults to start-aligned (no type-driven default since there's no underlying column type to dispatch off).
Name:ChildContent*
Type:RenderFragment<GridTemplateColumnContext>
Description:Cell template — required. Receives a GridTemplateColumnContext carrying the row, optional SubGrid parent record (via GridRowContext.ParentRecord) and the pre-built GridTemplateColumnContext.DependsOnMetadata lookup.
Name:DependsOn
Type:IReadOnlyList<string>?
Description:Logical names of the Dataverse columns the template reads from the row. The framework merges these into the FetchXML <attribute> projection so the data lands in GridRowContext.PrimaryRecord for the cell renderer to consume. null / empty = decorative column (no data dependency — actions, icons, etc.).
Description:Optional editor template — when supplied AND the grid is in inline-edit mode AND the row is mutable (not pending-delete, not a pending-create row whose target metadata forbids create), this is rendered instead of GridTemplateColumn.ChildContent. Receives a GridTemplateColumnEditContext with the same row data as the read context plus a GridTemplateColumnEditContext.SetValue(System.String,PowerPortalsPro.Common.Models.ColumnValueBase) helper.
Name:SortBy
Type:string?
Description:When set, clicking the header sorts the grid by this Dataverse column name. When omitted, the header is not sortable (most template columns derive from multiple sources and have no natural sort target).
Name:Title*
Type:string
Description:Header label. Required — there's no bound column metadata to derive a default from.
Name:Tooltip
Type:string?
Description:Optional header tooltip. Falls through to a plain header with no tooltip when omitted.
Name:Visible
Type:bool
Default:True
Description:When false, the column is suppressed from the rendered table AND its GridTemplateColumn.DependsOn columns are dropped from the server projection. Defaults to true.
Name:Width
Type:string?
Description:Column width. Use a CSS length like '150px'. When omitted, the table distributes remaining space — same default as GridColumn.
React
Start Center End Stretch Baseline
Auto Ascending Descending
Start Center End Stretch Baseline
Apply begins with filter on the following columns:
Email Phone
Apply begins with filter on the following columns: