Power Fx Cheat Sheet
Quick-reference formulas for Canvas App developers. Copy, paste, ship.
104 formulas across 21 categories · Last verified July 2026
Format date
Text(Today(), "[$-en-US]mmmm d, yyyy")Relative time
If(
DateDiff(dt, Now(), TimeUnit.Minutes) < 60,
Text(DateDiff(dt, Now(), TimeUnit.Minutes)) & " min ago",
Text(DateDiff(dt, Now(), TimeUnit.Hours)) & " hr ago"
)Date range filter (last 30 days)Delegable
Filter(Items, Created >= DateAdd(Today(), -30, TimeUnit.Days))First / last day of month
// First day
Date(Year(Today()), Month(Today()), 1)
// Last day
DateAdd(Date(Year(Today()), Month(Today()) + 1, 1), -1, TimeUnit.Days)Business days between dates (approx.)
// Approximation: subtracts 2 days per whole week. It does NOT
// account for the start/end weekday or partial weeks - good enough
// for rough estimates, not for payroll/SLA math.
With(
{d1: startDate, d2: endDate},
DateDiff(d1, d2, TimeUnit.Days)
- 2 * Int(DateDiff(d1, d2, TimeUnit.Days) / 7)
)End of month (shortcut)
EOMonth(Today(), 0) // last day of current monthCurrency
Text(12500, "$#,###.00") // → "$12,500.00"Abbreviate large numbers
If(
v >= 1000000, Text(v / 1000000, "#.#") & "M",
If(v >= 1000, Text(v / 1000, "#.#") & "K", Text(v))
)Truncate with ellipsis
If(Len(txt) > 50, Left(txt, 47) & "...", txt)Extract initials
Upper(
Left(name, 1) &
If(Find(" ", name) > 0, Mid(name, Find(" ", name) + 1, 1), "")
)Proper case
Proper(text) // already lowercases the rest - no need to wrap in Lower()Add computed columnNot delegable
AddColumns(Items, "FullName", FirstName & " " & LastName)Group byNot delegable
GroupBy(Items, "Category", "GroupItems")Distinct valuesNot delegable
Distinct(Items, Category)Sort multi-columnDelegable
SortByColumns(Items, "Priority", SortOrder.Ascending, "Date", SortOrder.Descending)Remove duplicatesNot delegable
ForAll(Distinct(colItems, Title), LookUp(colItems, Title = Result))Generate a number table
Sequence(10) // table 1..10 (Value column)Bulk update (ForAll + Patch)
ForAll(
colChanges As c,
Patch(YourList, LookUp(YourList, ID = c.ID), { Status: c.Status })
)Delegation-safe searchDelegable
// On SharePoint, StartsWith delegates - Search(), EndsWith()
// and the "in" operator do NOT (they hit the 500-2000 row cap).
Filter(Items, StartsWith(Title, searchText))Row count (delegation-aware)DependsSharePointDataverseSQL
// Row counts are source-dependent:
// Dataverse / SQL - CountRows and CountIf both delegate
// SharePoint - neither delegates; cache first with ClearCollect
CountRows(Filter(Items, Status = "Active"))Cache with ClearCollectDelegable
ClearCollect(colLocal, Filter(DataSource, Status = "Active"))Concurrent loading
Concurrent(
ClearCollect(col1, Source1),
ClearCollect(col2, Source2),
ClearCollect(col3, Source3)
)Non-delegable row limit
// Non-delegable queries only return the first 500 rows
// (raise to 2000 max in Settings › General). Keep filters
// delegable, or cache the data with ClearCollect first.IfError with fallback valueDelegable
IfError(
LookUp(Items, ID = varId),
Notify("Not found", NotificationType.Error);
Blank()
)Validate before Patch
If(
IsBlank(txtTitle.Text),
Notify("Title is required", NotificationType.Warning),
IsBlank(drpStatus.Selected),
Notify("Select a status", NotificationType.Warning),
// All valid - save
Patch(YourSharePointList, Defaults(YourSharePointList),
{Title: txtTitle.Text, Status: drpStatus.Selected}
);
Notify("Saved", NotificationType.Success)
)Coalesce for defaults
Coalesce(varUser, "Unknown")Breakpoints
If(App.Width < 640, "mobile", If(App.Width < 1024, "tablet", "desktop"))Responsive padding
If(App.Width < 640, 12, 24)Responsive columns
If(App.Width < 640, 1, If(App.Width < 1024, 2, 4))Max-width centering
Min(Parent.Width - 48, 1200)Color theme
nfColors = {
pageBg: ColorValue("#F9FAFB"),
cardBg: ColorValue("#FFFFFF"),
primary: ColorValue("#2563EB"),
danger: ColorValue("#DC2626")
};Responsive check
nfIsMobile = App.Width < 640;User info
nfCurrentUser = {
Name: User().FullName,
Email: User().Email,
Initials: Upper(Left(User().FullName, 1))
};Global variable (whole app)
Set(gblUserEmail, User().Email)Screen (context) variable
UpdateContext({ locStep: 1 })Toggle a boolean
UpdateContext({ locPanelOpen: !locPanelOpen })Local value, no variable
With({ subtotal: qty * price }, subtotal * 1.08)Collection CRUD
Collect(colCart, { Item: "Widget", Qty: 2 }); // add row
RemoveIf(colCart, Qty <= 0); // delete matching
Patch(colCart, First(colCart), { Qty: 5 }); // update row
Clear(colCart) // emptySelected item's field
galItems.Selected.TitleSearch + filter combinedDelegable
Filter(
Items,
StartsWith(Title, txtSearch.Text),
Status.Value = drpStatus.Selected.Value
)Highlight the selected row (row Fill)
If(ThisItem = galItems.Selected, nfColors.primary, nfColors.cardBg)Count of shown rows
CountRows(galItems.AllItems) & " results"Sum a column across the gallery
Sum(galItems.AllItems, Amount)New / edit / submit
NewForm(frmItem); Navigate(scrEdit); // create mode
EditForm(frmItem); Navigate(scrEdit); // edit mode
SubmitForm(frmItem) // save (button OnSelect)On success (form property)
// frmItem.OnSuccess
Notify("Saved", NotificationType.Success);
Back()Validate before submit
If(frmItem.Valid, SubmitForm(frmItem), Notify("Fix required fields", NotificationType.Warning))Card default value
Parent.Default // in a data card control's DefaultReset a form
ResetForm(frmItem)Selected value
drpStatus.Selected.ValueChoice / lookup options
Choices(YourList.Status)Cascading dropdownDelegable
Filter(Cities, Country.Value = drpCountry.Selected.Value)Multi-select ComboBox → text
Concat(cmbTags.SelectedItems, Value, ", ")Preselect items
DefaultSelectedItems: Filter(Choices(YourList.Status), Value = "Active")Sum / averageDependsSharePointDataverseSQL
Sum(Items, Amount) // Average(Items, Score)Max / min of a columnDependsSharePointDataverseSQL
Max(Items, DueDate) // Min(Items, Price)Percent complete
Round(done / total * 100, 1) & "%"Rounding & modulo
RoundUp(x, 0) RoundDown(x, 2) Mod(n, 2)Percent of total per rowNot delegable
AddColumns(
Items,
"Share", Round(Amount / Sum(Items, Amount) * 100, 1)
)Split into rows
Split("red,green,blue", ",") // 1-col table (Value)Join a table to text
Concat(colTags, Value, ", ")Validate email (regex)
IsMatch(txtEmail.Text, Match.Email)Extract digits (regex)
Match("Invoice #10432", "\d+").FullMatch // "10432"Read JSON (ParseJSON)
Set(gblData, ParseJSON(txtJson.Text));
Text(gblData.customer.name) // coerce an untyped valueWrite JSON
JSON(colItems, JSONFormat.IndentFour)Hex → color
ColorValue("#2563EB")Darken / lighten
ColorFade(nfColors.primary, -0.2) // negative = darker, positive = lighterStatus color switch
Switch(
ThisItem.Status.Value,
"Active", Color.Green,
"Pending", Color.Orange,
"Closed", Color.Gray,
Color.Black
)Transparent fill
RGBA(0, 0, 0, 0)Inline SVG icon (no PCF, no upload)
"data:image/svg+xml;utf8," & EncodeUrl(
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='#2563EB' stroke-width='2'><path d='M5 12h14M12 5v14'/></svg>"
)SVG with a dynamic color (placeholder pattern)
// Don't concatenate the hex straight into the string - the Image can
// cache the data URI and skip re-rendering when only the color changes.
// Keep ONE base string with a token and Substitute the hex in instead.
// EncodeUrl(varHex) turns "#2563EB" into "%232563EB" so the # survives.
Substitute(
"data:image/svg+xml;utf8," & EncodeUrl(
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='__COLOR__'><circle cx='12' cy='12' r='10'/></svg>"
),
"__COLOR__",
EncodeUrl(varHex)
)Run a flow & capture its response
// Flow ends with "Respond to a PowerApp" returning a "status" text.
Set(varOut, 'Approve Request'.Run(varSelectedId, txtComment.Text));
Notify(varOut.status, NotificationType.Success)Send a collection as JSON
// Serialize a collection and hand it to the flow as one text param.
'Sync Cart'.Run(JSON(colCart, JSONFormat.IgnoreUnsupportedTypes))Parse a JSON response
// Flow returns a JSON string in "payload".
Set(varData, ParseJSON('Get Order'.Run(varId).payload));
Text(varData.customer.name); // coerce untyped → text
Value(varData.total) // coerce untyped → numberLoop a flow's array response into a collection
ClearCollect(colLines,
ForAll(
ParseJSON('Get Lines'.Run(varId).lines) As line,
{ Id: Value(line.id), Name: Text(line.name) }
)
)Detect connection
If(Connection.Connected, "Online", "Offline")Cache & restore (SaveData / LoadData)
// On successful load - persist to the device
SaveData(colOrders, "orders");
// App.OnStart - restore, ignoring first-run absence
LoadData(colOrders, "orders", true) // true = don't error if missingQueue writes while offline
If(Connection.Connected,
Patch(YourList, Defaults(YourList), varRecord),
Collect(colPending, varRecord); SaveData(colPending, "pending")
)Flush the queue when back online
// App.OnStart / a Refresh button, once Connection.Connected is true
ForAll(colPending As p, Patch(YourList, Defaults(YourList), p));
Clear(colPending); SaveData(colPending, "pending")
// SaveData is capped (~1 MB on web / limited on mobile) - keep it small.Auto-refresh every 30s
// Timer: Duration = 30000, AutoStart = true, Repeat = true
// OnTimerEnd:
ClearCollect(colData, YourList)Debounce a search box
// txtSearch.OnChange:
Reset(tmrDebounce)
// tmrDebounce: Duration = 400 (Repeat = false)
// OnTimerEnd:
ClearCollect(colResults, Filter(Items, StartsWith(Title, txtSearch.Text)))Countdown display
// tmrCountdown running; show remaining seconds
Text(RoundUp((tmrCountdown.Duration - tmrCountdown.Value) / 1000, 0)) & "s"Current user basics
User().FullName // "Jane Doe"
User().Email // "jane@contoso.com"
User().Image // profile photo URLManager (Office365Users)
Office365Users.ManagerV2(User().Email).displayNameSomeone's profile
Office365Users.UserProfileV2("jane@contoso.com").jobTitleProfile photo
Office365Users.UserPhotoV2(User().Email) // set as an Image.ImageMy direct reports
Office365Users.DirectReportsV2(User().Email).value // a tableRole check (is-in-a-group)
!IsBlank(LookUp(colAdmins, Email = Lower(User().Email)))Accessible label (screen readers)
AccessibleLabel = "Delete " & ThisItem.TitleTab order
TabIndex = 1 // 0 = default order · -1 = skip (decorative only)Announce a live region
// On a status label - read out when its text changes
Live = Live.Polite // Live.Assertive interrupts, use sparinglyMove focus to a control
SetFocus(txtEmail) // e.g. after a validation errorFocus the first field on a new screen
// scrForm.OnVisible
SetFocus(txtFirstName)Set() doesn't loop or accumulate
// WRONG: ForAll is NOT a sequential loop - iterations don't see each
// other's Set(), so this leaves varTotal wrong.
ForAll(colRows, Set(varTotal, varTotal + Amount))
// RIGHT: use an aggregate instead
Set(varTotal, Sum(colRows, Amount))Blank() breaks collection schema
// WRONG: emptying with Blank() strips the columns - later Collect/Patch
// silently loses fields or errors on the "missing" schema.
ClearCollect(colX, If(cond, SourceRows, Blank()))
// RIGHT: zero rows but keep the real schema
ClearCollect(colX, If(cond, SourceRows, Filter(SourceRows, false)))TemplateSize can't see ThisItem
// WRONG: Gallery.TemplateSize is evaluated once for the gallery, not
// per row - it cannot reference ThisItem.
TemplateSize = If(ThisItem.Expanded, 120, 60)
// RIGHT: keep TemplateSize constant; vary a per-row container's Height,
// which CAN read ThisItem.
TemplateSize = 60 // container Height: If(ThisItem.Expanded, 120, 60)AutoLayout needs an AutoLayout parent
// A layout (auto-layout) container only hugs its content when its
// PARENT is also a layout container. Drop one onto a plain screen or
// inside a Rectangle and it won't self-size - nest it in another
// layout container, or give it an explicit Height/Width.IsBlank vs IsEmpty
IsBlank(txtName.Text) // a VALUE is blank / empty string
IsEmpty(colCart) // a TABLE / collection has 0 rowsThe delegation warning is silent, not an error
// The blue-underline "delegation" warning doesn't stop the app -
// it just caps results at the data row limit (500, up to 2000).
// You get WRONG data, not an error. Keep filters delegable or
// ClearCollect the source first, then work locally.