Custom Functions
How to create reusable custom functions in M to modularize your code, reduce duplication, and build shareable function libraries.
One of the most powerful features of the M language is the ability to define your own functions. Custom functions let you encapsulate logic into reusable building blocks, making your queries cleaner, easier to maintain, and shareable across projects.
Defining a Custom Function
A function in M is defined with the => (goes-to) syntax. The parameters go on the left, the body on the right:
let
AddTax = (amount as number, rate as number) as number =>
amount * (1 + rate),
Result = AddTax(100, 0.08)
// Result: 108
in
ResultYou can assign a function to a variable just like any other value. Once defined, call it by name with parentheses.
Single-Parameter Shorthand
For functions that take a single parameter, you can use the each keyword as shorthand. These are equivalent:
(row) => row[UnitPrice] * row[Quantity]
each [UnitPrice] * [Quantity]This is why each appears so often in Table.AddColumn, Table.SelectRows, and List.Transform — those functions all expect a single-parameter function as an argument.
Optional Parameters
Use optional to make parameters non-required. Check for missing values with the null-coalescing pattern:
let
TrimAndClean = (text as text, optional lower as logical) as text =>
let
ShouldLower = if lower = null then false else lower,
Trimmed = Text.Trim(text),
Result = if ShouldLower then Text.Lower(Trimmed) else Trimmed
in
Result
in
TrimAndClean(" Hello World ") // "Hello World"
// TrimAndClean(" Hello World ", true) // "hello world"Adding Type Information
Typed parameters and return types make your functions self-documenting and help catch mistakes early. You can also add a Documentation.Name and Documentation.Description via metadata so the function shows up with a friendly name in the Power Query UI:
let
PadLeft = (text as text, length as number, optional char as text) as text =>
let
PadChar = if char = null then "0" else char,
Padding = Text.Repeat(PadChar, length - Text.Length(text))
in
Padding & text,
Typed = Value.ReplaceType(
PadLeft,
type function (
text as text,
length as number,
optional char as text
) as text
meta [
Documentation.Name = "PadLeft",
Documentation.Description = "Pads a text value on the left to a specified length."
]
)
in
TypedRecursive Functions
M supports recursive functions using the @ operator to reference the function within its own body:
let
Factorial = (n as number) as number =>
if n <= 1 then 1 else n * @Factorial(n - 1)
in
Factorial(5)
// Result: 120The @ prefix tells the engine to look up the function by name in the current scope, which is necessary because the function has not finished being defined at the point where it references itself.
Modularizing Your Queries
Instead of repeating the same transformation logic across multiple queries, extract it into a function and invoke it wherever needed:
// Reusable cleaning function
let
CleanCurrency = (text as text) as number =>
Number.FromText(
Text.Remove(text, {"$", ",", " "})
)
in
CleanCurrencyOnce saved as its own query (e.g., CleanCurrency), you can call it from any other query:
Table.TransformColumns(
Sales,
{"RawPrice", CleanCurrency, type number}
)This pattern keeps individual queries focused on their purpose and avoids duplicating logic that would need to be updated in multiple places.
Building a Function Library
Custom functions become even more powerful when packaged as a reusable library. A common pattern is to store functions in a record, making them accessible as a namespace:
let
MyLibrary = [
TrimAndClean = (text as text) as text =>
Text.Trim(Text.Clean(text)),
ExtractDigits = (text as text) as text =>
Text.Select(text, {"0".."9"}),
ParseCurrency = (text as text) as number =>
Number.FromText(Text.Remove(text, {"$", ",", " "}))
]
in
MyLibraryEach function is then callable as MyLibraryTrimAndClean.
For a real-world example of this pattern at scale, see Imke Feldmann's ImkeF/M repository on GitHub — a collection of over 100 custom M functions organized by category (Table, Text, Date, List, Statistics, and more). Functions like Table.SolveParentChild and Text.RemoveHtmlTags demonstrate how custom functions can solve complex problems that have no built-in equivalent, and how packaging them in a shared repository makes them reusable across projects and teams.
Best Practices
- Name functions clearly. Use verb-noun naming like
CleanCurrencyorParseDateso the intent is obvious at the call site. - Type your parameters and return values. This catches errors early and documents expected inputs.
- Use
optionalparameters with defaults rather than creating multiple similar functions. - Extract repeated logic into a shared function as soon as you find yourself copying the same transformation between queries.
- Add documentation metadata via
Value.ReplaceTypeso your functions show descriptions in the Power Query editor.