
You've connected your data, built a gallery, and your app displays records. Now your user types "Smith" into the search box you dropped in, hits nothing, and stares at a full, unfiltered list. That moment — when search silently fails — is one of the most common frustrations in Canvas App development, and it's entirely avoidable once you understand what's actually happening between a text input, a Filter formula, and a gallery.
This lesson is about building search and filtering that works the way users expect: responsive, accurate, and fast enough to feel live. We'll build a practitioner-grade search toolbar for a realistic scenario — a Service Desk Ticket Browser — that includes a free-text search box, dropdown category filters, a date-range slicer, and a clear-filters button. By the end, you'll understand exactly why each formula is written the way it is, what delegation means for your filter logic, and how to avoid the silent failures that make apps feel broken.
What you'll learn:
Filter and Search functions and when to use eachYou should be comfortable with the basics of Canvas App construction — adding screens, inserting controls, and connecting a data source. If you're just getting started, work through Build Your First Canvas App in Power Apps first. You should also have a working understanding of how galleries and data sources connect; the Power Apps Controls: Galleries, Forms, and Data Tables - Advanced Architecture and Performance lesson covers that in depth.
For this lesson, we'll use a SharePoint list as our data source — a common enterprise setup. The patterns here translate directly to Dataverse and SQL Server with minor formula adjustments.
Before writing a single formula, let's define the data we're working with. Our SharePoint list is called ServiceTickets and has these columns:
| Column Name | Type | Example Value |
|---|---|---|
| Title | Single line of text | "Laptop screen flickering" |
| TicketNumber | Single line of text | "SD-2024-0045" |
| Category | Choice | "Hardware", "Software", "Network", "Access" |
| Priority | Choice | "Critical", "High", "Medium", "Low" |
| Status | Choice | "Open", "In Progress", "Resolved", "Closed" |
| AssignedTo | Person | "Priya Nair" |
| CreatedDate | Date/Time | 2024-03-15 |
| Description | Multiple lines | "Screen flickers after waking from sleep..." |
This is a realistic dataset — hundreds or thousands of rows, multiple filterable dimensions, and a free-text search need. A support manager needs to find all open hardware tickets assigned to a specific technician this week. That's the problem we're solving.
Practitioners often confuse Filter and Search. They're different functions with different superpowers and different limitations, and choosing between them matters.
Search is for free-text, user-facing search boxes. It scans one or more text columns for a substring match and is case-insensitive by default:
Search(ServiceTickets, TextInput_Search.Text, "Title", "Description", "TicketNumber")
The third argument onward specifies which columns to search — and critically, they must be quoted strings (column names as text), not column references. This trips up many beginners.
Filter is for structured, logical conditions — equality checks, comparisons, And/Or combinations:
Filter(ServiceTickets, Status.Value = Dropdown_Status.Selected.Value)
You'll almost always combine both. The winning pattern is to nest Search inside Filter, or chain conditions together:
Filter(
Search(ServiceTickets, TextInput_Search.Text, "Title", "TicketNumber"),
Category.Value = Dropdown_Category.Selected.Value
)
Key insight
Search delegates to SharePoint and Dataverse, meaning the filtering happens on the server and only matching records come down to the app. Filter with simple equality checks also delegates. But Filter with functions like Left(), Mid(), Len(), or IsBlank() on text fields does not delegate — meaning Power Apps pulls the first 500 (or 2000) records and filters them in memory, silently missing anything beyond that limit.
Understanding delegation is what separates a gallery that "mostly works" from one you can trust in production. We'll come back to this in detail — and for a thorough treatment, see Power Apps Performance Optimization: Delegation, Data Row Limits, and Reducing App Load Times.
Open your Canvas App and navigate to (or create) your browse screen. We'll name this screen Screen_Browse.
In the left panel, go to Data and add your ServiceTickets SharePoint list. If you haven't set up a SharePoint connection yet, Connecting Power Apps to SharePoint, Excel, and Dataverse: A Complete Integration Guide walks through the full connection process.
Add a horizontal container at the top of the screen (Insert > Layout > Horizontal Container). Set its height to 72 and its fill to a neutral color like RGBA(248, 248, 250, 1). Inside this container, insert the following controls from left to right:
TextInput_SearchDropdown_Category Dropdown_StatusDatePicker_FromDatePicker_ToButton_ClearFiltersBelow the toolbar, insert a Vertical Gallery and name it Gallery_Tickets. Set its data source temporarily to ServiceTickets — we'll update the Items formula shortly.
Tip
Use a container for your toolbar rather than free-floating controls. It keeps everything aligned, makes responsive layout adjustments easier, and means you can move the entire toolbar as a unit. The Power Apps Design Patterns: Responsive Layouts and Themes lesson covers container-based layout in detail if you want to go deep on this.
Select TextInput_Search and set these properties:
"Search tickets by title, number, or description..."trueTextMode.SingleLineThe DelayOutput property is critical for live search. When set to true, the input waits 200ms after the user stops typing before updating its .Text value. Without it, every single keystroke triggers a re-evaluation of the gallery's Items formula — which means a network call to SharePoint for every letter typed. With DelayOutput, the gallery refreshes once when the user pauses, making the experience feel snappy rather than jittery.
For Dropdown_Category, set:
Items: ["All", "Hardware", "Software", "Network", "Access"]
For Dropdown_Status, set:
Items: ["All", "Open", "In Progress", "Resolved", "Closed"]
We include "All" as the first item intentionally — it's our sentinel value that means "don't filter on this dimension." You'll see how this plays out in the filter formula.
This is the heart of the lesson. Select Gallery_Tickets and click on its Items property in the formula bar. We'll build the formula in layers so you can see exactly what each piece does.
Start simple:
Search(
ServiceTickets,
TextInput_Search.Text,
"Title",
"TicketNumber",
"Description"
)
Test this in preview. Type "laptop" in the search box — after a brief pause, the gallery should filter to only tickets containing "laptop" in those three fields. If it doesn't update at all, check that DelayOutput is true and that you've spelled the column names exactly as they appear in SharePoint (they're case-sensitive in this context).
Now wrap the Search in a Filter:
Filter(
Search(
ServiceTickets,
TextInput_Search.Text,
"Title",
"TicketNumber",
"Description"
),
Dropdown_Category.Selected.Value = "All" ||
Category.Value = Dropdown_Category.Selected.Value
)
The || (Or) operator implements the "All" sentinel pattern. When the dropdown is set to "All", the first condition is true, so the whole expression is true for every record — effectively no filter. When a specific category is selected, the second condition does the actual filtering.
Warning
If your SharePoint Category column is a Choice column, you must use .Value to get the text value: Category.Value. If you try to compare Category = Dropdown_Category.Selected.Value, you'll get a type mismatch error because Category is a record (with .Value, .Id, etc.) not a string.
Extend the Filter with an And condition:
Filter(
Search(
ServiceTickets,
TextInput_Search.Text,
"Title",
"TicketNumber",
"Description"
),
(Dropdown_Category.Selected.Value = "All" ||
Category.Value = Dropdown_Category.Selected.Value)
&&
(Dropdown_Status.Selected.Value = "All" ||
Status.Value = Dropdown_Status.Selected.Value)
)
Each filter dimension is wrapped in its own parenthetical group, connected by && (And). This keeps the logic readable as it grows. Test by selecting "Hardware" in the category dropdown and "Open" in the status dropdown — you should see only open hardware tickets.
Date filtering adds one more layer of complexity. We want to show tickets created between DatePicker_From.SelectedDate and DatePicker_To.SelectedDate, but we also need to handle the case where either DatePicker has no date selected yet.
Filter(
Search(
ServiceTickets,
TextInput_Search.Text,
"Title",
"TicketNumber",
"Description"
),
(Dropdown_Category.Selected.Value = "All" ||
Category.Value = Dropdown_Category.Selected.Value)
&&
(Dropdown_Status.Selected.Value = "All" ||
Status.Value = Dropdown_Status.Selected.Value)
&&
(IsBlank(DatePicker_From.SelectedDate) ||
CreatedDate >= DatePicker_From.SelectedDate)
&&
(IsBlank(DatePicker_To.SelectedDate) ||
CreatedDate <= DatePicker_To.SelectedDate)
)
The IsBlank() guard on each DatePicker means "if no date is chosen, don't apply this bound." This is exactly how users expect date filters to work — both bounds are optional.
Warning
IsBlank() on a DatePicker against SharePoint does not delegate — it runs in memory. For large datasets, this means your date filter will only see the first 2000 records. If you're working with a high-volume ticket system, see the delegation deep dive at Canvas App Delegation Deep Dive: Rewriting Non-Delegable Queries with Named Formulas, Explicit Column Selection, and Server-Side Filtering Patterns for server-side date filter strategies.
Here's the full, production-ready formula with formatting for readability:
Filter(
Search(
ServiceTickets,
TextInput_Search.Text,
"Title",
"TicketNumber",
"Description"
),
// Category filter
(Dropdown_Category.Selected.Value = "All" ||
Category.Value = Dropdown_Category.Selected.Value)
&&
// Status filter
(Dropdown_Status.Selected.Value = "All" ||
Status.Value = Dropdown_Status.Selected.Value)
&&
// Date from
(IsBlank(DatePicker_From.SelectedDate) ||
CreatedDate >= DatePicker_From.SelectedDate)
&&
// Date to
(IsBlank(DatePicker_To.SelectedDate) ||
CreatedDate <= DatePicker_To.SelectedDate)
)
Notice the comments (lines starting with //). Power Apps formula language supports inline comments, and you should use them. Six months from now, when you revisit this formula, you'll thank yourself for the annotation.
A search toolbar without a "Clear" button is half-built. Users explore data by applying filters, and they need a clean escape hatch. Select Button_ClearFilters and set its Text property to "Clear Filters".
Now set its OnSelect property:
Reset(TextInput_Search);
Reset(Dropdown_Category);
Reset(Dropdown_Status);
Reset(DatePicker_From);
Reset(DatePicker_To)
The Reset() function returns a control to its default state — an empty text input, the first item in a dropdown (which we set to "All"), and no date in a DatePicker. String them together with semicolons and all five controls reset with a single button press.
Tip
To make it obvious when the Clear button is needed, make it visible only when at least one filter is active. Set the Visible property of Button_ClearFilters to:
TextInput_Search.Text <> "" ||
Dropdown_Category.Selected.Value <> "All" ||
Dropdown_Status.Selected.Value <> "All" ||
!IsBlank(DatePicker_From.SelectedDate) ||
!IsBlank(DatePicker_To.SelectedDate)
The button disappears when nothing is filtered, and reappears the moment any filter is active. It's a small UX touch that signals clearly whether the current view is filtered.
One of the most underappreciated patterns in practitioner-grade apps is showing users what filters are currently active. Add a Label below the toolbar and name it Label_FilterSummary. Set its Text property:
"Showing " & CountRows(Gallery_Tickets.AllItems) & " of " & CountRows(ServiceTickets) & " tickets"
This shows something like "Showing 14 of 847 tickets" — instantly communicating both what matched and the full dataset size. Users immediately understand they're looking at a filtered subset.
For a richer summary, you could also concatenate the active filter values:
"Filters: " &
If(TextInput_Search.Text <> "", "Search: """ & TextInput_Search.Text & """ ", "") &
If(Dropdown_Category.Selected.Value <> "All", "Category: " & Dropdown_Category.Selected.Value & " ", "") &
If(Dropdown_Status.Selected.Value <> "All", "Status: " & Dropdown_Status.Selected.Value, "")
This builds a plain-text summary of active filters inline, like "Filters: Search: "laptop" Category: Hardware Status: Open". It's self-documenting for users who've drilled deep into a filter combination.
We've mentioned delegation several times. Let's be concrete about what happens when delegation fails, because the failure mode is insidious.
Imagine your SharePoint list has 5,000 tickets. Power Apps has a default data row limit of 500 (configurable to 2000 in app settings). When you use a non-delegable formula, Power Apps silently fetches the first 500 records and filters them on the client. Your filter formula "works" — it returns results — but it's only searching a 10% sample of your data. A user searches for a ticket they know exists and it doesn't appear. They assume the app is broken. They're right.
Delegable operations for SharePoint:
Filter with equality checks (=) on text, choice, and boolean columnsFilter with <, >, <=, >= on date and number columnsSearch on text columnsStartsWith on text columnsSortByColumnsNon-delegable operations (run in memory on limited rows):
Search on non-text columnsFilter with IsBlank(), IsEmpty(), Len(), Left(), Mid()Filter with in for text containment (use Search instead)The yellow warning triangle that appears next to formulas in the formula bar is Power Apps telling you delegation is broken. Don't dismiss it — investigate it.
For our Service Desk app, the text search via Search() delegates fine. The Category and Status equality checks delegate fine. The IsBlank() date guards don't delegate. This is an acceptable tradeoff for many ticket systems where most search queries are either text-driven or use specific category/status filters, and date filtering is a secondary refinement. But if your users primarily filter by date ranges, you need a different approach — either collecting all records into a local collection with ClearCollect on app start, or using a server-side query via a custom connector.
Key insight
The right question isn't "does my filter formula work?" — it's "does my filter formula work on my full dataset?" Always test with production-scale data before shipping. A filter that runs fine on 200 dev records will silently fail on 50,000 production records.
For formulas and delegation strategies in depth, Master Power Apps Formulas: Navigate, Filter, Lookup, and Patch for Professional Apps covers the full picture.
A search toolbar without sort is frustrating. Users who've filtered to 50 tickets still want to order them by priority or date. Add two more controls to your toolbar:
Dropdown_SortColumn with Items: ["CreatedDate", "Priority", "Title", "Status"]Toggle_SortAsc with text "Asc / Desc"Now update the Gallery Items formula to wrap the whole thing in SortByColumns:
SortByColumns(
Filter(
Search(
ServiceTickets,
TextInput_Search.Text,
"Title",
"TicketNumber",
"Description"
),
(Dropdown_Category.Selected.Value = "All" ||
Category.Value = Dropdown_Category.Selected.Value)
&&
(Dropdown_Status.Selected.Value = "All" ||
Status.Value = Dropdown_Status.Selected.Value)
&&
(IsBlank(DatePicker_From.SelectedDate) ||
CreatedDate >= DatePicker_From.SelectedDate)
&&
(IsBlank(DatePicker_To.SelectedDate) ||
CreatedDate <= DatePicker_To.SelectedDate)
),
Dropdown_SortColumn.Selected.Value,
If(Toggle_SortAsc.Value, SortOrder.Ascending, SortOrder.Descending)
)
SortByColumns takes the data source, the column name as a string, and a SortOrder enum. The toggle drives the sort direction. This delegates to SharePoint, so it's efficient even on large datasets.
Don't forget to add Reset(Dropdown_SortColumn) and Reset(Toggle_SortAsc) to your Clear Filters button's OnSelect.
A browse screen isn't complete without navigation to a detail view. When a user taps a ticket in the gallery, they should land on a detail screen pre-loaded with that record.
Set the OnSelect property of the gallery template (click inside the gallery once to select the template, not the gallery itself):
Navigate(Screen_TicketDetail, ScreenTransition.Fade, {SelectedTicket: ThisItem})
This passes the selected record as a context variable named SelectedTicket to the detail screen. On Screen_TicketDetail, use SelectedTicket.Title, SelectedTicket.Description, etc., to populate your display controls.
For a full treatment of navigation patterns and passing context between screens, see Building Multi-Screen Apps with Navigation and Variables in Power Apps.
Build the Service Desk Ticket Browser described in this lesson from scratch. Here's your implementation checklist:
Setup:
ServiceTickets with the columns defined at the top of this lesson (or use a reasonable approximation with an existing list you have access to)Toolbar:
TextInput_Search with DelayOutput: true and appropriate HintTextDropdown_Category with "All" as the first itemDropdown_Status with "All" as the first itemDatePicker_From and DatePicker_ToButton_ClearFilters with the Reset chain in OnSelectGallery:
Gallery_Tickets.Items to the full combined formula with sortThisItem.AssignedTo.DisplayName)Filter Summary:
Label_FilterSummary showing record count with and without filterDelegation verification:
Stretch goal: Add a Priority multi-select filter. Power Apps doesn't have a native multi-select dropdown, but you can simulate one using a Checkbox group or a gallery of checkboxes. Store selected priorities in a collection and filter with Priority.Value in ColSelectedPriorities.
Check 1: Is DelayOutput set to true on the TextInput? With it false, the .Text property updates on every keystroke and can appear to lag or stutter.
Check 2: Is the gallery's Items formula referencing the correct TextInput name? A typo like TextInput_Srch instead of TextInput_Search means the formula evaluates to a constant empty string.
Check 3: Did you accidentally set AutoStart or a timer that's resetting your input? Check if any other formula resets TextInput_Search.
Most likely cause: The column names in Search() are wrong. They must exactly match the internal column name in SharePoint, which may differ from the display name. A SharePoint column with display name "Ticket Number" might have an internal name of TicketNumber or Ticket_x0020_Number. Check in SharePoint's column settings or use Power Apps' formula bar autocomplete to verify the exact name.
Second cause: The column type isn't searchable. Search() only works on text columns. If TicketNumber is a Number type column in SharePoint, Search() won't scan it. Change the column type or add a separate text shadow column.
You're likely comparing a Choice record to a string. Use .Value:
// Wrong:
Category = Dropdown_Category.Selected.Value
// Right:
Category.Value = Dropdown_Category.Selected.Value
This happens when Power Apps caches the formula evaluation. Usually clicking Clear once more resolves it. If it's persistent, try wrapping your Reset chain in a Select(Self) after the resets, or add a context variable that the gallery formula also depends on (changing the variable forces re-evaluation).
This is a delegation warning. The specific condition causing it is non-delegable. Hover over the triangle for details. Options: (1) accept the limitation if the dataset is small, (2) restructure the formula to use delegable operations, or (3) pre-load data into a collection and filter the collection (which eliminates delegation concerns since collections are local). See Power Apps Collections and Local Data Management: Mastering ClearCollect, Patch, and In-Memory Data Operations for the collection-based approach.
If your SharePoint date column includes time components (DateTime, not Date), comparison with DatePicker_From.SelectedDate might miss records on the boundary date because the picker returns midnight (00:00:00) while your records may have timestamps like 14:32:00. Fix the upper bound:
CreatedDate <= DateAdd(DatePicker_To.SelectedDate, 1, TimeUnit.Days)
This sets the upper bound to midnight at the start of the day after the selected date, capturing all records on the selected end date regardless of their time component.
Tip
When debugging filter formula issues, temporarily set the gallery's Items to just the Search function alone, then add filter conditions one at a time. Isolating each condition tells you exactly which one is causing unexpected behavior. The Debugging Canvas Apps: Using the Power Apps Monitor Tool and Formula Errors to Fix Issues Fast lesson shows how to use the Monitor tool to watch exactly what data requests your app sends to SharePoint.
You've built a practitioner-grade search and filter toolbar — not just a text box wired to a gallery, but a complete, multi-dimensional browsing interface with text search, category and status filtering, date ranges, sort controls, a contextual clear button, and a live record count. More importantly, you understand why each piece works: delegation boundaries, the Search vs Filter distinction, the "All" sentinel pattern for dropdowns, the DelayOutput optimization, and the IsBlank guards on DatePickers.
The mental model from this lesson scales directly to more complex scenarios. Whether you're browsing employee records, inventory items, project tasks, or customer accounts, the same layered Filter + Search + SortByColumns pattern applies.
Where to go next:
If you're hitting delegation limits on large datasets, the Canvas App Delegation Deep Dive shows you how to rewrite non-delegable queries and handle server-side filtering properly.
If your browse screen needs to support role-based views — where managers see all tickets and technicians see only their own — Implementing Role-Based Screen Access and Dynamic UI in Canvas Apps Using Azure AD Group Membership shows how to inject user context into your filter formula.
If you're managing state across multiple screens — for example, persisting the filter state when a user navigates to detail and returns — Canvas App State Management at Scale: Global Variables, Named Formulas, and Context Isolation for Enterprise Apps covers exactly that pattern.
And if you want to validate your implementation with automated tests — a smart move before shipping any production app — Power Apps Canvas App Automated Testing: Building Test Suites with Test Studio and Power Automate for CI/CD Pipelines will show you how.