https://github.com/serverpod/whiskers
A templating library that implements the Mustache template specification with some extensions.
https://github.com/serverpod/whiskers
Last synced: 17 days ago
JSON representation
A templating library that implements the Mustache template specification with some extensions.
- Host: GitHub
- URL: https://github.com/serverpod/whiskers
- Owner: serverpod
- License: bsd-2-clause
- Created: 2026-04-08T18:07:51.000Z (2 months ago)
- Default Branch: main
- Last Pushed: 2026-04-15T17:49:37.000Z (2 months ago)
- Last Synced: 2026-05-26T06:11:56.138Z (25 days ago)
- Language: Dart
- Size: 73.2 KB
- Stars: 1
- Watchers: 0
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# Whiskers - Mustache templates with extensions
A Dart library for parsing and rendering [Mustache templates](https://mustache.github.io/). It is derived from [mustache_template](https://pub.dev/packages/mustache_template) and adds an optional `onMissingVariable` callback to `renderString` and `render`. This makes it possible to extend templates with custom behavior. It is used by the web server in [Serverpod](https://serverpod.dev) for cache busting.

See the [mustache manual](https://mustache.github.io/mustache.5.html) for detailed usage information.
This library passes all [mustache specification](https://github.com/mustache/spec/tree/master/specs) tests.
## Example usage
```dart
import 'package:whiskers/whiskers.dart';
main() {
var source = '''
{{# names }}
{{ lastname }}, {{ firstname }}
{{/ names }}
{{^ names }}
No names.
{{/ names }}
{{! I am a comment. }}
''';
var template = Template(source, name: 'template-filename.html');
var output = template.renderString({'names': [
{'firstname': 'Greg', 'lastname': 'Lowe'},
{'firstname': 'Bob', 'lastname': 'Johnson'}
]});
print(output);
}
```
A template is parsed when it is created. After parsing, it can be rendered any
number of times with different values. A `TemplateException` is thrown if there
is a problem parsing or rendering the template.
The `Template` constructor allows passing a `name`. This name is used in error
messages. When working with multiple templates, it is helpful to pass a name so
that error messages clearly identify which template caused the error.
By default, all output from `{{variable}}` tags is HTML-escaped. This behavior
can be changed by passing `htmlEscapeValues: false` to the `Template`
constructor. You can also use a `{{{triple mustache}}}` tag or an unescaped
variable tag like `{{&unescaped}}`; output from those tags is not escaped.
## Handling missing variables
```dart
import 'package:whiskers/whiskers.dart';
void main() {
final template = Template(
'',
name: 'index.html',
);
final output = template.renderString(
const {},
onMissingVariable: (name, _) {
if (name == 'cacheBuster') {
return '20260408';
}
return null;
},
);
print(output);
}
```
If `onMissingVariable` returns a string, that value is rendered in place of the
missing variable. If it returns `null`, the default behavior is preserved:
strict mode throws and lenient mode renders nothing.
## Differences between strict mode and lenient mode
### Strict mode (default)
* Tag names may only contain the characters a-z, A-Z, 0-9, underscore, period and minus. Other characters in tags will cause a TemplateException to be thrown during parsing.
* During rendering, if no map key or object member which matches the tag name is found, then a TemplateException will be thrown.
### Lenient mode
* Tag names may use any characters.
* During rendering, if no map key or object member which matches the tag name is found, then silently ignore and output nothing.
## Nested paths
```dart
var t = Template('{{ author.name }}');
var output = template.renderString({'author': {'name': 'Greg Lowe'}});
```
## Partials - example usage
```dart
var partial = Template('{{ foo }}', name: 'partial');
var resolver = (String name) {
if (name == 'partial-name') { // Name of partial tag.
return partial;
}
};
var t = Template('{{> partial-name }}', partialResolver: resolver);
var output = t.renderString({'foo': 'bar'}); // bar
```
## Lambdas - example usage
```dart
var t = Template('{{# foo }}');
var lambda = (_) => 'bar';
t.renderString({'foo': lambda}); // bar
```
```dart
var t = Template('{{# foo }}hidden{{/ foo }}');
var lambda = (_) => 'shown';
t.renderString('foo': lambda); // shown
```
```dart
var t = Template('{{# foo }}oi{{/ foo }}');
var lambda = (LambdaContext ctx) => '${ctx.renderString().toUpperCase()}';
t.renderString({'foo': lambda}); // OI
```
```dart
var t = Template('{{# foo }}{{bar}}{{/ foo }}');
var lambda = (LambdaContext ctx) => '${ctx.renderString().toUpperCase()}';
t.renderString({'foo': lambda, 'bar': 'pub'}); // PUB
```
```dart
var t = Template('{{# foo }}{{bar}}{{/ foo }}');
var lambda = (LambdaContext ctx) => '${ctx.renderString().toUpperCase()}';
t.renderString({'foo': lambda, 'bar': 'pub'}); // PUB
```
In the following example `LambdaContext.renderSource(source)` re-parses the source string in the current context, this is the default behaviour in many mustache implementations. Since re-parsing the content is slow, and often not required, this library makes this step optional.
```dart
var t = Template('{{# foo }}{{bar}}{{/ foo }}');
var lambda = (LambdaContext ctx) => ctx.renderSource(ctx.source + ' {{cmd}}');
t.renderString({'foo': lambda, 'bar': 'pub', 'cmd': 'build'}); // pub build
```