Regex Reference
Regular expressions in Power Fx — the IsMatch, Match, and MatchAll functions behind validation and text extraction in canvas apps, model-driven apps, Copilot Studio, and Power Pages. Match options, the Match enum, a full syntax cheat sheet, the cross-platform subset Power Fx actually allows, plus copy-paste validation and parsing recipes.
Where regex lives in Power Platform
Regex in the Power Platform is a Power Fx feature. Three functions take a pattern — IsMatch (validate), Match (extract the first hit), and MatchAll (extract every hit). They run anywhere Power Fx runs: canvas apps, model-driven custom pages, Copilot Studio, Power Pages, Dataverse low-code (formula columns and low-code plug-ins), and the Power Platform CLI. The full reference is IsMatch, Match, and MatchAll.
Cloud flows have no regex
Workflow Definition Language (the cloud-flow expression language) has no regular-expression function. Use indexOf / split / slice, or offload to Office Scripts / an Azure Function — see the Expressions guide. Everything on this page is Power Fx only.
The pattern must be a constant
The pattern *and* the options must be authoring-time constants — no variables, data sources, or other run-time values. You can still build a constant with &, string interpolation, Concatenate, Char, or UniChar over literal arguments. This is why an unsupported construct surfaces as a design-time error, not a run-time failure.
Power Fx runs on both JavaScript and .NET, so its regex dialect is a deliberately limited subset chosen to behave identically on every host. Patterns that work in other engines may be blocked or need a tweak — the Power Fx regex constraints section lists the differences. Full detail: Regular expressions in Power Fx.
IsMatch, Match & MatchAll
| Function | Returns | Default scope |
|---|---|---|
| IsMatch( Text, Pattern [, Options] ) | true / false — does the text match the pattern? | Complete in Power Apps (whole string); Contains elsewhere |
| Match( Text, Pattern [, Options] ) | A record for the first match, or blank if none | Contains — match anywhere in the text |
| MatchAll( Text, Pattern [, Options] ) | A table with one record per match, or an empty table | Contains — like the global "g" flag |
IsMatch( txtEmail.Text, Match.Email ) // true / false
Match( "Order 1042 shipped", "\d+" ).FullMatch // "1042"
MatchAll( "a1 b2 c3", "\d" ) // 3-row table: 1, 2, 3Just splitting? Use Split
If you only reach for MatchAll to break a string apart, the Split function is simpler and faster. Keep MatchAll for when you need the matched value, its position, or named submatches.
What Match and MatchAll return
Match returns a single record; MatchAll returns a table of those records. Each record carries the full match, its position, and any submatches you captured.
| Column | Type | Notes |
|---|---|---|
| FullMatch | Text | The entire substring that matched the pattern. |
| StartMatch | Number | 1-based start position of the match (first character is 1). |
| <named submatch> | Text | One column per (?<name>…) group — read it as .name. |
| SubMatches | Table | Single-column (Value) table of numbered groups — only when MatchOptions.NumberedSubMatches is set. |
With(
{ m: Match( "SKU-7781", "SKU-(?<id>\d+)" ) },
m.FullMatch // "SKU-7781"
// m.StartMatch -> 1
// m.id -> "7781"
)Test for no match
Match returns blank when nothing matches — test it with IsBlank(). MatchAll returns an empty table — test it with IsEmpty(). Reading .FullMatch off a blank Match result is itself blank, not an error.
Match options
Pass one or more MatchOptions as the third argument, combined with &. Each has an inline equivalent you can place at the very start of the pattern instead (for example (?i) for IgnoreCase).
| MatchOption | Effect | Equivalent / note |
|---|---|---|
| MatchOptions.Complete | Whole string must match, start to end. | Adds ^…$. Default for IsMatch in Power Apps. |
| MatchOptions.Contains | Pattern may appear anywhere in the text. | Default for Match / MatchAll (and IsMatch outside Power Apps). |
| MatchOptions.BeginsWith | Pattern must match from the start. | Adds ^ to the front. |
| MatchOptions.EndsWith | Pattern must match the end. | Adds $ to the end. |
| MatchOptions.IgnoreCase | Case-insensitive (culture-invariant). | "i" modifier / (?i). |
| MatchOptions.Multiline | ^ and $ match at line breaks too. | "m" modifier / (?m). |
| MatchOptions.DotAll | . also matches newline characters. | "s" modifier / (?s). Not in classic Power Apps. |
| MatchOptions.FreeSpacing | Ignore whitespace + allow # comments in the pattern. | "x" modifier / (?x). Not in classic Power Apps. |
| MatchOptions.NumberedSubMatches | Numbered capture groups + \1 backrefs; disables named captures. | Default in classic Power Apps. |
IsMatch( "HELLO world", "hello", MatchOptions.Contains & MatchOptions.IgnoreCase ) // true
// Inline form — same as above, options at the very start of the pattern:
IsMatch( "HELLO world", "(?i)hello", MatchOptions.Contains ) // trueIgnoreCase is culture-invariant
Case-insensitive matching ignores culture (the industry norm, matching JavaScript and Perl). If you need a culture-aware match — e.g. Turkish I/i — spell it out with a character class like [Ii] instead of relying on IgnoreCase.
Predefined Match enum patterns
The Match enum gives readable names for common pieces. Concatenate them with & and mix in your own literals — "A" & Match.MultipleDigits matches an "A" followed by one or more digits. Reach for these before hand-writing the equivalent regex.
| Match enum | Matches | Regex |
|---|---|---|
| Match.Any | Any character | . |
| Match.Digit | A single digit | \d |
| Match.MultipleDigits | One or more digits | \d+ |
| Match.OptionalDigits | Zero or more digits | \d* |
| Match.Letter | A single letter | \p{L} |
| Match.MultipleLetters | One or more letters | \p{L}+ |
| Match.OptionalLetters | Zero or more letters | \p{L}* |
| Match.Space | A single whitespace character | \s |
| Match.MultipleSpaces | One or more whitespace chars | \s+ |
| Match.OptionalSpaces | Zero or more whitespace chars | \s* |
| Match.NonSpace | A single non-whitespace character | \S |
| Match.MultipleNonSpaces | One or more non-whitespace chars | \S+ |
| Match.OptionalNonSpaces | Zero or more non-whitespace chars | \S* |
| Match.Email | A basic email address (has @ and a dotted domain) | see note |
| Match.Comma | A comma | , |
| Match.Period | A period / dot | \. |
| Match.Hyphen | A hyphen | - |
| Match.Tab | A tab character | \t |
| Match.LeftParen | A left parenthesis | \( |
| Match.RightParen | A right parenthesis | \) |
Match.Email is a form check, not full validation
Match.Email is a quick test that input *looks* like local@host.tld. It deliberately doesn't enforce every RFC: it accepts some invalid forms (e.g. an underscore in the host) and rejects some valid ones (quoted addresses, IP-literal hosts). For a text-input check use it with MatchOptions.Complete. Run Text( Match.Email ) to see the exact regex your host uses.
Characters & escapes
| Token | Matches |
|---|---|
| abc | The literal characters a, b, c, in order. |
| \. \? \* \+ \( \) \[ \] \^ \$ \| \\ \{ \} | An escaped metacharacter, taken literally. |
| \t | Tab — same as Char(9). |
| \r | Carriage return — Char(13). |
| \n | Newline — Char(10). |
| \f | Form feed — Char(12). |
| \x20 | Hex character code, exactly two hex digits. |
| \u2028 | Unicode code unit, exactly four hex digits. |
| \u{1F47B} | Unicode code point, up to eight hex digits (0–10FFFF). |
Reserved characters
These are special and must be escaped with a backslash to match literally: . ? * + ( ) [ ] ^ $ | \ { }. Octal escapes (\044) and \v are not supported — they're ambiguous across engines; use \x0b for a vertical tab and \x / \u for character codes.
Character classes
| Token | Matches |
|---|---|
| . | Any character except \r and \n (unless MatchOptions.DotAll). |
| [abc] | Any one of a, b, or c. |
| [a-z0-9] | Any character in the given ranges. |
| [^a-z] | Any character NOT in the set (negated class). |
| \d \D | A digit (0–9 and \p{Nd}) / a non-digit. |
| \w \W | A word character ([\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}\p{Pc}\p{Lm}]) / a non-word character. |
| \s \S | A whitespace character / a non-whitespace character. |
| \p{L} \P{L} | A Unicode category (here: any letter) / anything not in it. |
Bracket rules are stricter than other engines
Inside [...]: escape a literal hyphen ([\-a], not [-a]), escape a leading bracket ([\[a]), and escape curly braces ([\{\}]). Empty [] isn't allowed, and classes can't be nested, subtracted, or intersected. The negated shorthands \D \W \S \P{} cannot appear inside a negated class [^…].
Unicode categories available to \p{} / \P{} include letters (L, Lu, Ll, Lt, Lm, Lo), marks (M, Mn, Mc, Me), numbers (N, Nd, Nl, No), punctuation (P, Pc, Pd, Ps, Pe, Pi, Pf, Po), symbols (S, Sm, Sc, Sk, So), separators (Z, Zs, Zl, Zp), and control/format (Cc, Cf).
Anchors & assertions
Assertions match a *position*, not characters — they don't consume any input.
| Token | Asserts |
|---|---|
| ^ | Start of the text (or of a line with MatchOptions.Multiline). |
| $ | End of the text (or of a line with MatchOptions.Multiline). |
| \b \B | A word boundary / a non-boundary (Unicode letter definition). |
| (?=abc) | Positive lookahead — what follows matches abc. |
| (?!abc) | Negative lookahead — what follows does NOT match abc. |
| (?<=abc) | Positive lookbehind — what precedes matches abc. |
| (?<!abc) | Negative lookbehind — what precedes does NOT match abc. |
Look-around is limited
A lookahead or lookbehind can't contain a submatch or an unlimited quantifier (*, +, {n,}), and can't be quantified from the outside. This is why the classic (?=.*\d) password idiom can fail on the Power Fx 1.0 engine — see the strong-password recipe for a portable alternative.
Quantifiers
| Token | Repeats | Greed |
|---|---|---|
| ? | Zero or one | Greedy |
| * | Zero or more | Greedy |
| + | One or more | Greedy |
| {n} | Exactly n | — |
| {n,} | At least n | Greedy |
| {n,m} | Between n and m | Greedy |
| ?? *? +? | Lazy zero/one, zero/more, one/more | Lazy (smallest match) |
| {n,}? {n,m}? | Lazy at-least-n / between n and m | Lazy (smallest match) |
Greedy vs lazy
Greedy quantifiers grab as much as possible then back off; add ? to make them lazy and grab as little as possible. On <a><b>, <.+> matches the whole string, while <.+?> matches just <a>. Possessive quantifiers (a++) and atomic groups are not supported.
Groups, alternation & submatches
| Token | Meaning |
|---|---|
| (abc) | Group elements so a quantifier applies — (abc)+ matches abcabc. |
| a|b | Alternation — matches a or b (often inside a group). |
| (?:abc) | Non-capturing group (this is the default behaviour anyway). |
| (?<name>abc) | Named submatch — read it back as the column .name. |
| \k<name> | Backreference to a named submatch. |
| (abc) … \1 | Numbered capture + backreference — only with MatchOptions.NumberedSubMatches. |
| (?# comment) | Inline comment, ignored by the engine. |
By default in the current Power Fx engine, plain (...) groups are non-capturing ("explicit capture") — capture only what you need with a named submatch (?<name>...), and reference it with \k<name>. Classic Power Apps defaults to MatchOptions.NumberedSubMatches, where (...) captures and you use \1-style backreferences. Named and numbered captures can't be mixed in one pattern.
With(
{ d: Match( "2026-06-16", "(?<y>\d{4})-(?<m>\d{2})-(?<day>\d{2})" ) },
// d.y = "2026", d.m = "06", d.day = "16"
DateValue( d.y & "-" & d.m & "-" & d.day )
)Power Fx regex constraints
Because one pattern is compiled to both JavaScript and .NET, Power Fx restricts regex to a curated subset that behaves the same on every host. Anything outside it errors at authoring time — which is also why the pattern has to be a constant. Keep these in mind:
- Constant only — the pattern and options can't come from a variable, data source, or user input; build them from literals with
&/Concatenate/Char/UniChar. - Named vs numbered captures can't mix — named is the default in the current engine (plain groups don't capture); numbered is the default in classic Power Apps. Pick one.
- Negated shorthands
\D \W \S \P{}can't be used inside a negated class[^…]. - A possibly-empty submatch can't be quantified —
(?<x>a*)+errors, but(?<x>a+)+is fine. The same applies to optional or alternated submatches that could match nothing. - No possessive quantifiers or atomic groups, no self-referencing captures (
(a\1)), no two groups sharing a name, and no backreference into a look-around or a possibly-empty submatch. - Surrogate pairs (U+10000 and above) aren't supported inside character classes, and
\p{}category data is only guaranteed for the Basic Multilingual Plane. - `\v` and octal escapes are disallowed — use
\x0band\x/\ucodes.
Classic Power Apps vs Power Fx 1.0
Classic Power Apps uses an earlier regex engine: MatchOptions.DotAll and FreeSpacing aren't available, NumberedSubMatches is the default, surrogate pairs aren't treated as one character, and Match.Email / Match.Hyphen are defined differently. The newer engine described here arrives under a "Power Fx 1.0 compatibility" switch — test on the engine your app actually uses.
There is no regex replace
Power Fx has no regex-based replace. Substitute() replaces literal text only. To transform with a pattern, Match / MatchAll the pieces and rebuild the string, or offload the substitution to Office Scripts, a custom connector, or an Azure Function.
Validation recipes
IsMatch defaults to Complete in Power Apps, so each pattern below validates the whole input — pair it with a text input control. Every pattern is a constant. Patterns are shown as you'd type them in the formula bar.
| Validates | Pattern | Notes |
|---|---|---|
| Match.Email | Prefer the built-in enum; quick form check. | |
| Email (explicit) | ^[\w.%+\-]+@[\w.\-]+\.[A-Za-z]{2,}$ | Hyphen escaped inside the class, per Power Fx rules. |
| US phone | ^\(?\d{3}\)?[\-. ]?\d{3}[\-. ]?\d{4}$ | Allows (123) 456-7890, 123.456.7890, 1234567890. |
| US ZIP | ^\d{5}(-\d{4})?$ | Five digits, optional +4. |
| US SSN | ^\d{3}-\d{2}-\d{4}$ | 3-2-4 digits. |
| GUID | ^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$ | Standard 8-4-4-4-12 hex form. |
| IPv4 | ^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$ | Each octet 0–255. |
| URL (http/https) | ^https?://\S+$ | Loose check — protocol + non-space rest. |
| Hex colour | ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ | #abc or #aabbcc. |
| Currency | ^\d+(\.\d{2})?$ | Digits, optional two decimals. |
| Time (24h) | ^([01]\d|2[0-3]):[0-5]\d$ | 00:00–23:59. |
| ISO 8601 date | ^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$ | YYYY-MM-DD (range not month-aware). |
| Username | ^\w{3,16}$ | 3–16 word characters (letters, digits, _). |
And(
Len( txtPwd.Text ) >= 8,
IsMatch( txtPwd.Text, "\d", MatchOptions.Contains ), // a digit
IsMatch( txtPwd.Text, "[a-z]", MatchOptions.Contains ), // a lower-case letter
IsMatch( txtPwd.Text, "[A-Z]", MatchOptions.Contains ), // an upper-case letter
IsMatch( txtPwd.Text, "[^\w\s]", MatchOptions.Contains ) // a symbol (not word / space)
)IsMatch(
txtPwd.Text,
"(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^\w\s]).{8,}",
MatchOptions.Complete
)Extraction & parsing recipes
Match and MatchAll default to Contains, so they find a pattern anywhere. Capture the parts you want with named submatches and read them by column name.
Match( "Invoice 4471 — due", "\d+" ).FullMatch // "4471"// MatchAll returns a table of records:
MatchAll( "loving #powerfx and #lowcode", "#\w+" )
// Flatten to "#powerfx, #lowcode":
Concat( MatchAll( txtPost.Text, "#\w+" ), FullMatch, ", " )MatchAll(
"env=prod;region=eus;tier=1",
"(?<key>\w+)=(?<val>[^;]+)"
)
// each row: { key: "env", val: "prod", FullMatch: "env=prod", StartMatch: 1 }With(
{ m: Match( "555-1234", "(\d{3})-(\d{4})", MatchOptions.NumberedSubMatches ) },
Index( m.SubMatches, 1 ).Value & " / " & Index( m.SubMatches, 2 ).Value
) // "555 / 1234"StartMatch pairs with Mid
StartMatch is 1-based, so it drops straight into Mid( text, Match(...).StartMatch, ... ) when you need the text around a hit rather than the hit itself.
Using regex in a Power App
Inline field validation message
If(
IsBlank( txtEmail.Text ) || IsMatch( txtEmail.Text, Match.Email ),
"",
"Enter a valid email address"
)Enable Submit only when the form is valid
And(
!IsBlank( txtName.Text ),
IsMatch( txtEmail.Text, Match.Email ),
IsMatch( txtPhone.Text, "^\d{3}-\d{3}-\d{4}$" )
)Colour the border by validity
// Text input BorderColor
If( IsMatch( Self.Text, "^\d{5}$" ), Color.Green, Color.Red )You can't build the pattern at run time
Because the pattern must be a constant, you can't assemble a regex from a variable or from user input. For *dynamic* search-as-you-type, use Find, StartsWith, in, or Search instead — regex is for fixed, known patterns.
Power Pages: validate on the server too
The same functions work in model-driven apps and Power Pages, but client-side Power Fx on a public Power Pages site can be bypassed. Treat IsMatch as a UX nicety and re-validate on the server (Dataverse plug-in or a flow) before trusting the input.
Common gotchas
- Default scope differs —
IsMatchisComplete(whole string) in Power Apps;Match/MatchAllareContains. Add^…$or the explicit option when it matters. - Pattern must be a constant — no variables or data sources; that's why unsupported syntax errors at authoring time, not run time.
- Named vs numbered captures can't mix — current engine captures only named groups by default; classic Power Apps numbers all groups (
\1). Don't combine the two. - `\D \W \S \P{}` can't go inside `[^…]` — and literal hyphens, leading brackets, and braces must be escaped inside a class.
- `.` skips newlines unless
MatchOptions.DotAll— andDotAll/FreeSpacingaren't available in classic Power Apps. - Look-around can't hold an unlimited quantifier or be quantified — the
(?=.*\d)idiom may fail; preferAnd()of severalIsMatch(…, Contains)checks. - No regex replace —
Substitute()is literal only; rebuild viaMatch/MatchAllor offload. - `Match.Email` is a form check, not full RFC validation — and
IgnoreCaseis culture-invariant. - Just splitting? Use `Split` — it's simpler and faster than
MatchAll.