Error Messages

You might encounter a number of different error messages while writing an MF2 message. Here's a guide to those errors, with an example for each one.

The presentation and wording of errors varies based on the implementation of MF2 that is being used. This guide is based on the error descriptions in the MF2 specification; for each one, the error name used by the JavaScript/TypeScript implementation (which is used by the playground) is also provided.

Ease of debugging can vary across implementations, as implementations of MF2 provide different levels of detail in their error messages. The error messages provided by the MF2 playground generally have a high level of detail, so if you're struggling to understand an error message from an implementation, try paste it into the playground - this can often provide more information.

Additionally, the MF2 language server can provide inline diagnostics for syntax errors in editors that support it, such as Visual Studio Code. These diagnostics can help you catch errors as you write messages. Many diagnostics also provide quick fixes to help you correct the error.

Syntax errors Jump to heading

JS name: parse-error

Syntax errors occur when a message has errors that prevent the message processor from providing any more detailed information.

{|Some text|

Here the error message "Placeholder is missing the closing brace." is provided by the MF2 playground.

Fixing the error Jump to heading

Look over the message carefully with an eye towards errors like missing curly braces.

Corrected message:

{|Some text|}

Data model errors Jump to heading

Data model errors can be detected by the message formatter without knowing any runtime data. It's helpful to know this distinction because both syntax and data model errors are non-recoverable: the message formatter won't try to provide fallback output for messages that contain these errors.

Variant key mismatch Jump to heading

JS name: key-mismatch

A "Variant Key Mismatch" error occurs when the number of keys in a variant in a matcher differs from the number of selector variables. Remember that all the variants have to have the same number of keys, which has to be the same as the number of selector variables.

.local $x = {1 :number}
.match $x
one * {{Too many keys}}
*   * {{Also too many keys}}

Fixing the error Jump to heading

Depending on what you intended, either change the number of selector variables to match the number of keys in the variants, or the other way around. Make sure that all variants have the same number of keys.

Corrected message:

.local $x = {1 :number}
.match $x
one {{Correct number of keys}}
*   {{Also correct number of keys}}

Missing fallback variant Jump to heading

JS name: missing-fallback

A "Missing Fallback Variant" error occurs when a matcher isn't exhaustive. To be exhaustive, it must have one variant whose keys are all * (the wildcard key, which matches anything).

.local $x = {1 :number}
.match $x
one {{x is singular}}

Fixing the error Jump to heading

Add a variant where all the keys are *.

.local $x = {1 :number}
.match $x
one {{x is singular}}
*   {{x is plural}}

Duplicate variant Jump to heading

JS name: duplicate-variant

A "Duplicate Variant" error occurs when more than one variant has the same list of keys. Key lists are ordered, so the key lists one * and * one would not be duplicates.

.local $x = {1 :number}
.match $x
one {{x is singular}}
one {{x is singular.}}
*   {{x is not singular}}

This message is ambiguous because it's not clear which variant to choose if $x is singular.

Fixing the error Jump to heading

Remove the extra variant(s):

.local $x = {1 :number}
.match $x
one {{x is singular}}
*   {{x is not singular}}

Missing selector annotation Jump to heading

JS name: missing-selector-annotation

.match $x
one {{x is singular}}
*   {{x is plural}}
{ "x": 5 }

Every variable used as a selector in a .match has to have an annotation. But the syntax doesn't allow the variable to be annotated directly. Instead, it has to be annotated by adding a .local or .input declaration for the variable.

Fixing the error Jump to heading

For each external variable in the .match, add an .input declaration for it. If there are any .local variables in the .match, make sure that they have a declaration with an annotation.

.input {$x :number}
.match $x
one {{x is singular}}
*   {{x is plural}}
{ "x": 5 }

When checking for selector annotations, annotations are transitive. That is, this works, even though $y has no explicit annotation:

.input {$x :number}
.local $y = {$x}
.match $y
one {{x is singular}}
*   {{x is plural}}
{ "x": 5 }

It works because $y is bound to a variable that is in turn bound to an expression with an annotation.

Duplicate Declaration Jump to heading

JS name: duplicate-declaration

MF2 only allows variables to be declared once. See the reference on variables. So this is an error:

.input {$x :number}
.local $x = {42}
.match $x
one {{x is singular}}
*   {{x is plural}}
{ "x": 5 }

Fixing the error Jump to heading

Give each variable a unique name:

.input {$x :number}
.local $y = {42}
.match $x
one {{x is singular}}
*   {{x is plural}}
{ "x": 5 }

(This message may seem a bit silly, since $y is never used, but it's just an example.)

Duplicate Option Name Jump to heading

JS name: duplicate-option-name

When giving option to a function, the same option name can't be repeated more than once.

.input {$x :number style=percent style=decimal}
{{{$x}}}
{ "x": 5 }

The meaning of this message is ambiguous (which style did you mean?), so the error reminds you to remove any repeated options.

Fixing the error Jump to heading

Remove the extra option names, leaving the ones with the value that you wanted to include:

.input {$x :number style=percent}
{{{$x}}}
{ "x": 5 }

Resolution errors Jump to heading

Resolution errors happen when a part of a message has unclear meaning due to missing or inconsistent runtime data.

Unresolved variable Jump to heading

JS error message: "Variable not available"

In MF2, variables have to be declared before they are used. This means either declaring them with a .local declaration, or providing them as an external input variable.

.input {$x :number}
{{{$x}}}

This is an error because $x is declared with an .input, but not provided as an external variable.

Fixing the error Jump to heading

Provide either an external variable, or a .local declaration, that gives meaning to the undeclared variable name.

.input {$x :number}
{{{$x}}}
{ "x": 5 }

Unknown function Jump to heading

JS error message: "Unknown function"

Functions used in annotations must either be built into the formatter, or provided in a custom function registry. There is no built-in function named num and we have not provided a custom function registry, so this example fails:

.local $x = {1 :num}
{{{$x}}}

Fixing the error Jump to heading

Check the spelling of the function name mentioned in the error, or provide a custom function with that name if you intended to do so.

.local $x = {1 :number}
{{{$x}}}

Bad selector Jump to heading

JS error message: "Selection error: bad-selector"

Some functions, like :number, produce a result that can be selected on in a .match. Other functions, like :datetime, don't:

.local $day = {|2024-05-01| :datetime}
.match $day
* {{The due date is {$day}}}

MF2 doesn't provide a way to select on dates. In general, depending on how a function is written, its result might not be usable as a selector in a .match. As far as the built-in functions, :number and :string produce results that can be selected on, while :datetime, :date, and :time don't. Although MF2 has no type system, it would be fair to say that numbers and strings are selectable, while dates and times are not.

Fixing the error Jump to heading

The fix depends on what you intended to do. In this case, maybe you just wanted to format the date, and the .match isn't necessary:

.local $day = {|2024-05-01| :datetime}
{{The due date is {$day}}}

If you are using a custom function, you should make sure it returns values that can be selected on. (The details are implementation-specific.)

Message function errors Jump to heading

The last category of errors are errors that are specific to a particular built-in or custom function. It helps to know that so you can narrow down the function that caused the error, then consult the documentation for that function in order to fix the error.

Bad operand Jump to heading

JS error message: Depends on the function; "Input is not numeric" for :number, "Input is not a date" for date functions

This category of errors appears when a function is passed an argument (operand) that it can't handle. Although MF2 is untyped, this is basically a runtime type error.

For example, the string "horse" can't be converted to a number or a date, so using it with a :number or :date annotation is an error:

.local $horse = {|horse| :number}
{{You have a {$horse}.}}
.local $horse = {|horse| :datetime}
{{You have a {$horse}.}}

Fixing the error Jump to heading

Read the documentation for the function so you know what kind of input it expects. For example, :number expects a string that can be parsed as a number. The date functions such as :datetime expect a string in ISO 8601 format (full details). Both functions may also accept implementation-specific types.

.input {$numHorses :number}
.match $numHorses
one {{You have a horse.}}
*   {{You have {$numHorses} horses.}}
{ "numHorses": 42 }

Bad option Jump to heading

JS error message: "Value [option name] is not valid for [function name]"

This category of errors is similar to "Bad Operand", but it means one of the option values was wrong, rather than the argument to the function.

.local $x = {1 :number minimumFractionDigits=foo}
{{{$x}}}

This fails because :number requires its minimumFractionDigits option to be a string that can be parsed as an integer (or possibly an implementation-specific numeric type).

In general, the reason for the error depends on which function you are using and what kinds of options it allows.

Fixing the error Jump to heading

For built-in functions, the default function registry is the authoritative source for what options are accepted by functions and what their values may be. For custom functions, you'll have to compare the function's implementation with how you are using it.

.local $x = {1 :number minimumFractionDigits=2}
{{{$x}}}

Bad variant key Jump to heading

This error was added so recently that the current JS implementation doesn't check for it yet, but in the future, this will be an error:

.local $answer = {42 :number}
.match $answer
1     {{The value is one.}}
horse {{The value is a horse.}}
*     {{The value is not one.}}

In general, custom selector functions define what kinds of keys they can match against. Just as the argument of :number has to be parseable as a number, and some of its option values has to be number, it can only match against numeric keys.

Fixing the error Jump to heading

In this case, the fix is to remove any variants with non-numeric keys:

.local $answer = {42 :number}
.match $answer
1     {{The value is one.}}
*     {{The value is not one.}}

In general, the fix depends on which selector function is being used, and what kinds of keys it can match against. As with the other errors in this section, if these errors come up when using custom functions, the solution depends on the details of how the function is implemented.

On this page