MF2 is a two-level grammar: messages can be plain text (a simple message) or a pattern wrapped in {{}} with optional declarations and matchers. See the
official spec
for the full grammar.
Simple vs complex messages
| Syntax | Meaning | Example output |
Hello, world! |
Simple message — plain text is a valid message. |
Hello, world! |
{{Hello, {$name}!}} |
Quoted pattern — required when the message has declarations, matchers, or starts with { or . |
Hello, Aoife! |
.local $x = {1} {{Value: {$x}}} |
Complex message — declarations followed by a quoted pattern. |
Value: 1 |
Placeholders
| Syntax | Meaning | Example output |
{$name} |
Variable reference — binds to the bindings map/keyword. |
Aoife |
{|literal text|} |
Literal — explicit quoted value. Rare in practice. |
literal text |
{42} |
Number literal. |
42 |
{$x :number} |
Annotated variable — applies a function to the value. |
3 |
{$x :number minimumFractionDigits=2} |
Annotated with options. |
3.00 |
Built-in functions
| Syntax | Meaning | Example output |
:string |
Selects/formats a string value (default for strings). |
|
:number |
Formats a number. Options: minimumFractionDigits, maximumFractionDigits, minimumIntegerDigits, useGrouping, signDisplay, notation. |
1,234.50 |
:integer |
Formats an integer. |
1,234 |
:currency |
Formats as currency. Options: currency=ISO, currencyDisplay=code|symbol|name|narrowSymbol. |
$1,234.56 |
:date |
Formats a date. Options: dateStyle=short|medium|long|full. |
Apr 15, 2026 |
:time |
Formats a time. Options: timeStyle=short|medium|long|full. |
3:04 PM |
:datetime |
Formats a date and time. |
Apr 15, 2026, 3:04 PM |
Declarations
| Syntax | Meaning | Example output |
.local $x = {expr} |
Bind a local variable to an expression (placeholder). |
|
.input {$x :number} |
Re-annotate an incoming binding with a function/options. |
|
Selection / matchers
| Syntax | Meaning | Example output |
.match $count |
Select a variant based on one or more selectors. |
|
0 {{No items.}} |
Literal variant — matched when $count == 0. |
No items. |
one {{1 item}} |
Plural keyword variant (:number selector). |
1 item |
* {{N items.}} |
Default/wildcard variant. Required. |
N items. |
.match $a $b
hi hi {{both hi}}
* * {{anything}} |
Multi-selector — one row per combination. Wildcards * act as catch-alls. |
|
Markup
| Syntax | Meaning | Example output |
{#link}Click here{/link} |
Open / close markup — the formatter can emit structured output (HTML, etc.). |
|
{#img src=|photo.jpg| /} |
Self-closing markup with options. |
|
Escaping
| Syntax | Meaning | Example output |
\{ |
Literal open brace. |
{ |
\} |
Literal close brace. |
} |
\\ |
Literal backslash. |
\ |
\| |
Literal pipe inside |...| literals. |
| |
Common patterns
| Syntax | Meaning | Example output |
.match {$count :number}
one {{1 unread}}
* {{{$count} unread}} |
Plural message using CLDR plural categories. |
3 unread |
.match {$gender :string}
feminine {{She}}
masculine {{He}}
* {{They}} |
Gender/string selection. |
They |
{{Total: {$amount :currency currency=USD}}} |
Currency formatting with options. |
Total: $1,234.56 |
{{Today is {$d :date dateStyle=full}}} |
Long date. |
Today is Wednesday, April 15, 2026 |