Skip to main content

AutoFilter

An autofilter turns a block of data into something the user can filter and sort from the Excel UI. XLibur does more than write the dropdown arrows into the file: it evaluates the filter conditions and marks the rows that fail them as hidden, so the workbook you save already shows the filtered result.

The shape is always the same: get an IXLAutoFilter, pick a column, apply a condition.

Define an autofilter

Call SetAutoFilter() on a range. The first row of the range becomes the header row:

using XLibur.Excel;

using var workbook = new XLWorkbook();
var ws = workbook.Worksheets.Add("Data");

ws.Cell("A1").SetValue("Product")
.CellBelow().SetValue("Widget")
.CellBelow().SetValue("Gadget")
.CellBelow().SetValue("Doohickey");

ws.Cell("B1").SetValue("Units")
.CellBelow().SetValue(120)
.CellBelow().SetValue(45)
.CellBelow().SetValue(310);

var autoFilter = ws.RangeUsed()!.SetAutoFilter();

You can also filter a fixed range, or reach the sheet's filter afterwards through ws.AutoFilter:

ws.Range("A1:D100").SetAutoFilter();

var filter = ws.AutoFilter; // the sheet's single autofilter
Console.WriteLine(filter.IsEnabled);
Console.WriteLine(filter.Range.RangeAddress.ToString());

Tables have their own autofilter, independent of the sheet's:

var table = ws.Range("A1:D100").CreateTable("Sales");
var tableFilter = table.AutoFilter;
note

A worksheet has at most one sheet-level autofilter. Calling SetAutoFilter() on a second range replaces the first. Each table, however, carries its own — so use tables when a sheet holds several independently filtered blocks.

Enabling and clearing

ws.AutoFilter.IsEnabled = false; // hide the arrows, show every row
ws.AutoFilter.IsEnabled = true;

ws.AutoFilter.Clear(); // remove all filters and unhide every row
ws.AutoFilter.Column(2).Clear(); // clear one column's filters only

Addressing columns

Filter columns are numbered relative to the autofilter range, starting at 1:

ws.AutoFilter.Column(1); // first column of the range
ws.AutoFilter.Column("A"); // same thing — "A" is the range's first column

For a filter on C5:H100, Column(1) is sheet column C.

Value filters

The everyday case: a checkbox list of allowed values. Each AddFilter adds one permitted value; a row is visible if its cell matches any of them.

var autoFilter = ws.RangeUsed()!.SetAutoFilter();

autoFilter.Column(1).AddFilter("Widget")
.AddFilter("Gadget");

autoFilter.Column(2).AddFilter(120)
.AddFilter(310);
warning

Value filters compare the cell's formatted string against the filter value converted to a string using the current culture. A cell holding 2.5 formatted as 2.50 will not match AddFilter(2.5), and results differ between locales. When filtering numbers, prefer the custom comparison filters below.

Custom filters

Comparison-based conditions. Each returns a connector so you can add a second condition with .Or or .And — Excel allows exactly two per column:

var autoFilter = ws.RangeUsed()!.SetAutoFilter();

// Numeric comparisons
autoFilter.Column(2).EqualTo(3).Or.GreaterThan(4);
autoFilter.Column(2).EqualOrGreaterThan(100).And.LessThan(500);

// Range comparisons (no connector — these stand alone)
autoFilter.Column(2).Between(100, 500);
autoFilter.Column(2).NotBetween(100, 500);

Text conditions:

autoFilter.Column(1).BeginsWith("J");
autoFilter.Column(1).EndsWith("son");
autoFilter.Column(1).Contains("dget");
autoFilter.Column(1).NotContains("test");
autoFilter.Column(1).NotBeginsWith("_");

// Combined
autoFilter.Column(1).BeginsWith("A").Or.BeginsWith("B");

The full comparison set: EqualTo, NotEqualTo, GreaterThan, LessThan, EqualOrGreaterThan, EqualOrLessThan, Between, NotBetween, BeginsWith, NotBeginsWith, EndsWith, NotEndsWith, Contains, NotContains.

Top and bottom filters

Show only the highest or lowest values, by count or by percentile:

autoFilter.Column(2).Top(10); // top 10 items
autoFilter.Column(2).Top(10, XLTopBottomType.Percent); // top 10%
autoFilter.Column(2).Bottom(5); // bottom 5 items
autoFilter.Column(2).Bottom(50, XLTopBottomType.Percent); // bottom half

Dynamic filters

Computed against the column's own data:

autoFilter.Column(2).AboveAverage();
autoFilter.Column(2).BelowAverage();

Date group filters

Filter dates at a chosen granularity. XLDateTimeGrouping selects how much of the date is compared — everything more precise than the grouping level is ignored:

var target = new DateTime(2018, 1, 4);

autoFilter.Column(1).AddDateGroupFilter(target, XLDateTimeGrouping.Day); // that exact day
autoFilter.Column(1).AddDateGroupFilter(target, XLDateTimeGrouping.Month); // all of January 2018
autoFilter.Column(1).AddDateGroupFilter(target, XLDateTimeGrouping.Year); // all of 2018

Grouping levels: Year, Month, Day, Hour, Minute, Second.

Like value filters, these accumulate — call it more than once to allow several periods:

autoFilter.Column(1)
.AddDateGroupFilter(new DateTime(2018, 1, 1), XLDateTimeGrouping.Month)
.AddDateGroupFilter(new DateTime(2018, 3, 1), XLDateTimeGrouping.Month);

Colour filters

Filter by fill colour or font colour:

autoFilter.Column(3).ColorFilter(XLColor.Yellow); // cells with a yellow fill
autoFilter.Column(3).FontColorFilter(XLColor.Red); // cells with red text

One filter type per column

A column carries a single filter type. Applying a different type replaces what was there — AddFilter after Top(10) discards the top-10 rule rather than combining with it:

autoFilter.Column(2).Top(10);
autoFilter.Column(2).AddFilter(5); // the Top(10) filter is gone

Different columns combine with AND: a row must satisfy every column's filter to stay visible.

autoFilter.Column(1).BeginsWith("J"); // name starts with J
autoFilter.Column(2).GreaterThan(100); // AND units > 100

Sorting

Sort orders the rows of the autofilter range by one column. It also records the sort state in the file, so Excel shows the sort indicator on that column:

ws.AutoFilter.Sort(); // column 1, ascending
ws.AutoFilter.Sort(2); // by column 2
ws.AutoFilter.Sort(2, XLSortOrder.Descending);
ws.AutoFilter.Sort(2, XLSortOrder.Ascending, matchCase: true, ignoreBlanks: false);
ParameterEffect
columnToSortBy1-based column within the autofilter range
sortOrderAscending (default) or Descending
matchCaseCase-sensitive text comparison; default false
ignoreBlankstrue (default) puts blanks last regardless of order; false sorts them as empty strings

Reading the sort state back:

if (ws.AutoFilter.Sorted)
{
Console.WriteLine($"Sorted by column {ws.AutoFilter.SortColumn} {ws.AutoFilter.SortOrder}");
}

Inspecting the result

Because XLibur applies the filters, you can read which rows survived:

foreach (var row in ws.AutoFilter.VisibleRows)
{
Console.WriteLine(row.Cell(1).GetString());
}

Console.WriteLine($"{ws.AutoFilter.HiddenRows.Count()} rows filtered out");

Reapplying after edits

Filters are re-evaluated automatically whenever the filter configuration changes. They are not re-evaluated when you change cell values or delete rows afterwards — call Reapply() then:

ws.Cell("B3").Value = 999;
ws.AutoFilter.Reapply();

Every filter method also takes a reapply flag. Setting it to false on all but the last call avoids re-filtering the range once per condition:

var column = ws.AutoFilter.Column(2);
column.AddFilter(1, reapply: false);
column.AddFilter(2, reapply: false);
column.AddFilter(3, reapply: true); // evaluate once, at the end

Autofilters and column widths

AdjustToContents() sizes columns to their content but does not account for the space the filter dropdown arrow needs, so the arrow can overlap the header text. Add a little padding to filtered columns:

ws.Columns().AdjustToContents();

foreach (var column in ws.AutoFilter.Range.Columns())
{
var sheetColumn = ws.Column(column.RangeAddress.FirstAddress.ColumnNumber);
sheetColumn.Width += 3;
}

A worked example

using XLibur.Excel;

using var workbook = new XLWorkbook();
var ws = workbook.Worksheets.Add("Orders");

ws.Cell("A1").Value = "Customer";
ws.Cell("B1").Value = "Region";
ws.Cell("C1").Value = "Amount";
ws.Cell("D1").Value = "Ordered";

var orders = new[]
{
("Acme", "North", 1200m, new DateTime(2026, 1, 12)),
("Globex", "South", 380m, new DateTime(2026, 1, 19)),
("Initech", "North", 4500m, new DateTime(2026, 2, 3)),
("Umbrella", "East", 910m, new DateTime(2026, 2, 21)),
("Soylent", "North", 2750m, new DateTime(2026, 3, 8)),
};

var row = 2;
foreach (var (customer, region, amount, ordered) in orders)
{
ws.Cell(row, 1).Value = customer;
ws.Cell(row, 2).Value = region;
ws.Cell(row, 3).Value = amount;
ws.Cell(row, 4).Value = ordered;
row++;
}

ws.Range($"C2:C{row - 1}").Style.NumberFormat.Format = "$ #,##0.00";
ws.Range($"D2:D{row - 1}").Style.DateFormat.Format = "yyyy-MM-dd";

var filter = ws.RangeUsed()!.SetAutoFilter();

filter.Column(2).AddFilter("North"); // North region only
filter.Column(3).GreaterThan(1000); // AND over $1,000

ws.AutoFilter.Sort(3, XLSortOrder.Descending);

ws.Range("A1:D1").Style.Font.Bold = true;
ws.Columns().AdjustToContents();

workbook.SaveAs("FilteredOrders.xlsx");

Where to next

  • Tables — table-scoped autofilters and totals rows
  • Cells and Ranges — range sorting outside an autofilter