TL;DR —
Range.AdvancedFilteris the only filter that hands you data, not a view. In one call it can copy a unique list or a criteria-matched set of rows to another location, without a loop and without deleting anything. Two ideas unlock it. First,Actionchooses betweenxlFilterInPlace(hides rows, like AutoFilter) andxlFilterCopy(copies the results to aCopyToRange— the powerful mode). Second, its "WHERE clause" is not code — it is a criteria range, a small block of cells whose header must match the source header exactly. AddUnique:=Trueand you get a distinct list in one line, with the original left untouched.
' Extract a distinct list of Region values from column B into column E. Non-destructive.
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("Sales")
ws.Range("B1:B1000").AdvancedFilter _
Action:=xlFilterCopy, _
CopyToRange:=ws.Range("E1"), _ ' E1 must hold the SAME header as B1
Unique:=True ' one of each value, source unchanged
AdvancedFilter is the tool people reach for last and should reach for first. When you need a
result as data — a distinct list, an export of the rows matching a condition, a de-duplicated copy
that leaves the original intact — the instinct is to write a For Each loop with an If and a
Dictionary. Excel already has a query engine for exactly this. This guide is built around its two
defining ideas: it can copy instead of hide, and its criteria live in cells.
What you'll learn
- The mental model — SQL-lite for a range: extract, don't just view
- In place vs copy —
xlFilterInPlaceversusxlFilterCopy - The criteria range — your WHERE clause lives in cells, not in code
Unique:=True— a distinct list without deleting anything- The header-match trap that returns empty output
AdvancedFiltervsAutoFiltervsRemoveDuplicates
The mental model: SQL-lite for a range
Think of AdvancedFilter as a tiny query engine bolted onto a range. AutoFilter answers "which
rows do I want to see?" and hides the rest in place. AdvancedFilter answers a bigger question:
"give me the rows that match — as a new set of data I can put somewhere and use." It can filter
in place too, but its reason to exist is the copy mode: it takes a source range, an optional set of
conditions, and writes the matching (or unique) rows to a destination you choose.
That reframing matters because it replaces a whole category of loops. "Get the unique customers," "pull every order over 1000 to a report sheet," "list the distinct product codes" — these are one-call extractions, not iteration. Hold the picture — AdvancedFilter produces data, AutoFilter produces a view — and you will know which one a task needs.
In place vs copy: xlFilterInPlace vs xlFilterCopy
The Action argument is the fork in the road:
xlFilterInPlacehides the non-matching rows right in the source range, exactly likeAutoFilter. The data does not move; you get a filtered view. To clear it afterwards you callws.ShowAllData.xlFilterCopyleaves the source alone and copies the matching rows toCopyToRange. This is the mode that makesAdvancedFilterspecial — it is non-destructive and produces a separate, usable block of data.
' Copy mode - the one you'll usually want.
ws.Range("A1").CurrentRegion.AdvancedFilter _
Action:=xlFilterCopy, _
CriteriaRange:=ws.Range("H1:H2"), _ ' the WHERE clause (see below)
CopyToRange:=ws.Range("K1"), _ ' where the results land
Unique:=False
If you pass xlFilterCopy you must provide CopyToRange; if you pass xlFilterInPlace you must
not (Excel errors either way if you mix them up). When in doubt, copy — it never touches your
original.
The criteria range: your WHERE clause lives in cells
This is the part that feels alien coming from other languages: AdvancedFilter does not take a
condition as a string or an expression. Its conditions live in a criteria range — a small block
of worksheet cells. The top row holds column headers that match the source, and the rows beneath
hold the conditions.
H I
1 Region Amount
2 West >1000
That criteria range says "Region is West AND Amount > 1000." Conditions on the same row are
AND; conditions stacked on separate rows are OR. So two rows —
H
1 Region
2 West
3 East
— means "Region is West OR East." You can build this block in cells your macro owns (a scratch area,
or a hidden sheet) and point CriteriaRange at it:
ws.Range("A1").CurrentRegion.AdvancedFilter _
Action:=xlFilterCopy, _
CriteriaRange:=ws.Range("H1:I2"), _
CopyToRange:=ws.Range("K1")
Omit CriteriaRange entirely and no condition is applied — which, combined with Unique:=True, is
exactly how you get a plain distinct list.
Unique:=True — a distinct list without deleting anything
Set Unique:=True and AdvancedFilter returns one of each distinct row (or value, for a single
column) — the same result as Remove Duplicates, except it copies the uniques out and leaves
the source intact. That single property is why AdvancedFilter is the safe way to dedupe:
' Distinct customer names into column K, original list untouched.
ws.Range("C1:C5000").AdvancedFilter _
Action:=xlFilterCopy, _
CopyToRange:=ws.Range("K1"), _
Unique:=True
Where RemoveDuplicates deletes in place with no undo, this hands you a fresh, deduplicated list
and never risks the original. When someone asks "how do I get unique values in VBA without wrecking
the data," this is the answer.
The header-match trap that returns empty output
The number-one reason AdvancedFilter "returns nothing" is a header mismatch. Both the
CriteriaRange header and the CopyToRange header must match the source headers exactly
— same spelling, same spacing, same case-insensitive text. A criteria header of "Reigon" (typo), or
a CopyToRange whose top cell is blank or mislabelled, produces empty or wrong output without
raising an error.
Two habits prevent it:
- Build the criteria and copy-to headers by copying the real header cells, never by retyping them.
- If you filter to another sheet, remember an old rule: classic
AdvancedFilterwants theCopyToRangeon the active sheet. The robust pattern is to run the filter from the sheet the results land on, or to filter into a scratch range on the source sheet and move the result afterwards.
When output comes back empty, check the headers before you check anything else.
AdvancedFilter vs AutoFilter vs RemoveDuplicates
They overlap enough to confuse and differ enough to matter:
AutoFilter— a view. Hides rows in place so the user sees a subset; nothing is copied or removed. Reach for it to show a filtered range.AdvancedFilter(copy mode) — a query. Extracts matching or unique rows to a new location, non-destructively. Reach for it when you need the result as data.RemoveDuplicates— a deletion. Shrinks the data in place, keeps the first of each, no undo. Reach for it only when you truly want the source itself smaller and you have a backup.
The line worth remembering: AutoFilter shows, RemoveDuplicates destroys, and AdvancedFilter is
the one that extracts — a distinct list or a criteria-matched copy — without touching the original.
When you catch yourself writing a loop to build a filtered or unique list, that is AdvancedFilter.
How ExcelMaster helps
AdvancedFilter is powerful precisely because it is fiddly: the in-place-vs-copy fork, the criteria
range that has to live in cells with headers that match to the letter, the copy-to destination and
its active-sheet rule, the Unique flag. Get any header wrong and it returns nothing, silently.
ExcelMaster lets you
describe the query instead. Say "pull every order over 1000 from the West region to a new sheet" or
"give me the distinct list of product codes," and it lays out the criteria range with headers copied
from the source, chooses copy mode with a valid CopyToRange, sets Unique when you want distinct
values, and leaves the original data intact. You keep the workbook and the code; you skip the
half-hour of wondering why a mistyped criteria header returned an empty result.
Frequently asked questions
How do I use Advanced Filter in VBA?
Call AdvancedFilter on the source range: choose Action:=xlFilterCopy to copy results elsewhere
(with a CopyToRange) or xlFilterInPlace to hide non-matching rows. Provide a CriteriaRange for
conditions, or set Unique:=True with no criteria to extract a distinct list. In copy mode the
source data is left unchanged.
How do I extract unique values with VBA Advanced Filter?
Use copy mode with Unique:=True and no criteria:
rng.AdvancedFilter Action:=xlFilterCopy, CopyToRange:=ws.Range("E1"), Unique:=True. It writes one
of each distinct value to the destination and leaves the source intact — unlike RemoveDuplicates,
which deletes in place. The CopyToRange header must match the source header.
Why does VBA Advanced Filter return nothing?
Almost always a header mismatch. The CriteriaRange and CopyToRange top-row headers must match the
source headers exactly (a typo or blank header returns empty output with no error). Copy the real
header cells rather than retyping them, and make sure a copy-mode filter has a valid CopyToRange.
What is the difference between AutoFilter and Advanced Filter in VBA?
AutoFilter produces a view — it hides non-matching rows in place and copies nothing.
AdvancedFilter produces data — in copy mode it extracts matching or unique rows to a new
location without altering the source, and it supports complex AND/OR criteria and unique extraction
that AutoFilter cannot.
How do criteria work in VBA Advanced Filter?
Criteria live in a range of cells, not in code. The top row holds headers that match the source;
conditions on the same row are combined with AND, and conditions on separate rows are combined with
OR. Point CriteriaRange at that block. Omit it to apply no condition (useful with Unique:=True).
Tested in
Tested in: Excel 365 (Windows 11), VBA 7.1 — last verified 2026-08-10.
Related guides: VBA AutoFilter · VBA Remove Duplicates · VBA Sort · VBA Find · VBA Range
