{"id":50505085,"url":"https://github.com/serverpod/whiskers","last_synced_at":"2026-06-02T15:02:20.952Z","repository":{"id":351609547,"uuid":"1205185996","full_name":"serverpod/whiskers","owner":"serverpod","description":"A templating library that implements the Mustache template specification with some extensions.","archived":false,"fork":false,"pushed_at":"2026-04-15T17:49:37.000Z","size":75,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-26T06:11:56.138Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/serverpod.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-08T18:07:51.000Z","updated_at":"2026-04-28T08:52:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/serverpod/whiskers","commit_stats":null,"previous_names":["serverpod/whiskers"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/serverpod/whiskers","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serverpod%2Fwhiskers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serverpod%2Fwhiskers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serverpod%2Fwhiskers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serverpod%2Fwhiskers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/serverpod","download_url":"https://codeload.github.com/serverpod/whiskers/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/serverpod%2Fwhiskers/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33827068,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-02T02:00:07.132Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-02T15:02:20.118Z","updated_at":"2026-06-02T15:02:20.937Z","avatar_url":"https://github.com/serverpod.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Whiskers - Mustache templates with extensions\n\nA 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.\n\n![Whiskers logo](https://raw.githubusercontent.com/serverpod/whiskers/main/misc/whiskers.webp)\n\nSee the [mustache manual](https://mustache.github.io/mustache.5.html) for detailed usage information.\n\nThis library passes all [mustache specification](https://github.com/mustache/spec/tree/master/specs) tests.\n\n## Example usage\n```dart\nimport 'package:whiskers/whiskers.dart';\n\nmain() {\n\tvar source = '''\n\t  {{# names }}\n            \u003cdiv\u003e{{ lastname }}, {{ firstname }}\u003c/div\u003e\n\t  {{/ names }}\n\t  {{^ names }}\n\t    \u003cdiv\u003eNo names.\u003c/div\u003e\n\t  {{/ names }}\n\t  {{! I am a comment. }}\n\t''';\n\n\tvar template = Template(source, name: 'template-filename.html');\n\n\tvar output = template.renderString({'names': [\n\t\t{'firstname': 'Greg', 'lastname': 'Lowe'},\n\t\t{'firstname': 'Bob', 'lastname': 'Johnson'}\n\t]});\n\n\tprint(output);\n}\n```\n\nA template is parsed when it is created. After parsing, it can be rendered any\nnumber of times with different values. A `TemplateException` is thrown if there\nis a problem parsing or rendering the template.\n\nThe `Template` constructor allows passing a `name`. This name is used in error\nmessages. When working with multiple templates, it is helpful to pass a name so\nthat error messages clearly identify which template caused the error.\n\nBy default, all output from `{{variable}}` tags is HTML-escaped. This behavior\ncan be changed by passing `htmlEscapeValues: false` to the `Template`\nconstructor. You can also use a `{{{triple mustache}}}` tag or an unescaped\nvariable tag like `{{\u0026unescaped}}`; output from those tags is not escaped.\n\n## Handling missing variables\n\n```dart\nimport 'package:whiskers/whiskers.dart';\n\nvoid main() {\n  final template = Template(\n    '\u003cscript src=\"app.js?v={{cacheBuster}}\"\u003e\u003c/script\u003e',\n    name: 'index.html',\n  );\n\n  final output = template.renderString(\n    const {},\n    onMissingVariable: (name, _) {\n      if (name == 'cacheBuster') {\n        return '20260408';\n      }\n      return null;\n    },\n  );\n\n  print(output);\n}\n```\n\nIf `onMissingVariable` returns a string, that value is rendered in place of the\nmissing variable. If it returns `null`, the default behavior is preserved:\nstrict mode throws and lenient mode renders nothing.\n\n## Differences between strict mode and lenient mode\n\n### Strict mode (default)\n\n* 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.\n\n* During rendering, if no map key or object member which matches the tag name is found, then a TemplateException will be thrown.\n\n### Lenient mode\n\n* Tag names may use any characters.\n* During rendering, if no map key or object member which matches the tag name is found, then silently ignore and output nothing.\n\n## Nested paths\n\n```dart\n  var t = Template('{{ author.name }}');\n  var output = template.renderString({'author': {'name': 'Greg Lowe'}});\n```\n\n## Partials - example usage\n\n```dart\n\nvar partial = Template('{{ foo }}', name: 'partial');\n\nvar resolver = (String name) {\n   if (name == 'partial-name') { // Name of partial tag.\n     return partial;\n   }\n};\n\nvar t = Template('{{\u003e partial-name }}', partialResolver: resolver);\n\nvar output = t.renderString({'foo': 'bar'}); // bar\n\n```\n\n## Lambdas - example usage\n\n```dart\nvar t = Template('{{# foo }}');\nvar lambda = (_) =\u003e 'bar';\nt.renderString({'foo': lambda}); // bar\n```\n\n```dart\nvar t = Template('{{# foo }}hidden{{/ foo }}');\nvar lambda = (_) =\u003e 'shown';\nt.renderString('foo': lambda); // shown\n```\n\n```dart\nvar t = Template('{{# foo }}oi{{/ foo }}');\nvar lambda = (LambdaContext ctx) =\u003e '\u003cb\u003e${ctx.renderString().toUpperCase()}\u003c/b\u003e';\nt.renderString({'foo': lambda}); // \u003cb\u003eOI\u003c/b\u003e\n```\n\n```dart\nvar t = Template('{{# foo }}{{bar}}{{/ foo }}');\nvar lambda = (LambdaContext ctx) =\u003e '\u003cb\u003e${ctx.renderString().toUpperCase()}\u003c/b\u003e';\nt.renderString({'foo': lambda, 'bar': 'pub'}); // \u003cb\u003ePUB\u003c/b\u003e\n```\n\n```dart\nvar t = Template('{{# foo }}{{bar}}{{/ foo }}');\nvar lambda = (LambdaContext ctx) =\u003e '\u003cb\u003e${ctx.renderString().toUpperCase()}\u003c/b\u003e';\nt.renderString({'foo': lambda, 'bar': 'pub'}); // \u003cb\u003ePUB\u003c/b\u003e\n```\n\nIn 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.\n\n```dart\nvar t = Template('{{# foo }}{{bar}}{{/ foo }}');\nvar lambda = (LambdaContext ctx) =\u003e ctx.renderSource(ctx.source + ' {{cmd}}');\nt.renderString({'foo': lambda, 'bar': 'pub', 'cmd': 'build'}); // pub build\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserverpod%2Fwhiskers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fserverpod%2Fwhiskers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserverpod%2Fwhiskers/lists"}