Rendering & data
Crema evaluates expressions against the data map you pass to view / render (and nested maps, lists, and bean-style getters). This page covers how values become HTML — escaped or raw — and the helpers that turn data into JSON, URLs, and translation strings. For making views from controllers, see the View helper.
Passing data
Section titled “Passing data”Every render starts with a Map<String, Any?> (Kotlin) or Map<String, ?> (Java). Keys become top-level names in the template:
view( "posts.show", mapOf( "title" to "Hello", "post" to post, "tags" to listOf("kona", "jvm"), ),).render()Views.make( "posts.show", Map.of( "title", "Hello", "post", post, "tags", List.of("kona", "jvm") )).render();<h1>{{ title }}</h1><p>{{ post.body }}</p>Nested paths use dots: post.body walks map keys or zero-arg getters (getBody(), isActive(), or a method/field named body).
View.with(key, value) / with(map) merges more keys before render(). Child contexts (includes, loops, components) inherit parent data and may overlay loop variables or slots.
Escaped echoes
Section titled “Escaped echoes”{{ expr }} prints the value as text with HTML escaping for &, <, >, ", and ':
<p>Hello {{ name }}</p>{{-- name = "<script>" → Hello <script> --}}Empty or missing values render as an empty string — not the literal null.
Raw HTML
Section titled “Raw HTML”{!! expr !!} prints without escaping. Use only for trusted markup you built yourself (or already sanitized):
<div class="prose">{!! html !!}</div>Unclosed {!! / {{ sequences are errors at render time.
Literal @ and comments
Section titled “Literal @ and comments”| Syntax | Result |
|---|---|
@@ |
A single @ in the output (useful when documenting directives) |
{{-- … --}} |
Comment — stripped before any other processing |
{{-- Never appears in HTML --}}<code>@@extends</code>Expressions
Section titled “Expressions”Echoes and most directive arguments accept a small expression language:
| Kind | Examples |
|---|---|
| Literals | true, false, null / nil, 42, 3.5, 'string', "string" |
| Comparisons | ==, !=, <, >, <=, >= (numbers compare numerically; otherwise stringified) |
| Logic | and, or, not …, !… |
| Helpers | isset(name), empty(name) |
| Paths | user.name, errors.email |
@if(count > 0 and ready) Ready@endif
{{ isset(name) }}{{ empty(items) }}{{ not ok }}{{ a == 1 or b == 2 }}isset(path) is true when the resolved value is non-null. empty(path) is true when the value is not truthy (see truthiness).
There is no arbitrary code execution in templates — only this expression subset and property access.
@json($expr) encodes a value as JSON (Jackson). Useful inside <script> or data attributes:
<script> window.bootstrap = @json($user);</script>crema.render( "page", mapOf("user" to mapOf("name" to "Ada", "id" to 1)),)// → window.bootstrap = {"name":"Ada","id":1};crema.render( "page", Map.of("user", Map.of("name", "Ada", "id", 1)));// → window.bootstrap = {"name":"Ada","id":1};@lang('key') resolves a translation string. Crema prefers a lang map in the view data when present; otherwise it calls Lang.get from kona-support.
@lang('messages.welcome')@lang('messages.hello', replace)The optional second argument is an expression that evaluates to a Map of replacements. Placeholders in the string use :name form (Hi :name + name → Ada → Hi Ada).
crema.render( "greet", mapOf( "replace" to mapOf("name" to "Ada"), // or override a single key: // "lang" to mapOf("messages.hello" to "Hi :name"), ),)crema.render( "greet", Map.of("replace", Map.of("name", "Ada")));Register SupportServiceProvider (or call Lang.setTranslator) before rendering when you rely on the global translator.
@asset
Section titled “@asset”@asset('css/app.css') builds a public URL. With no base in the data map it emits /css/app.css. Pass asset_url or assetUrl to prefix a CDN:
<link rel="stylesheet" href="@asset('css/app.css')" />Leading slashes on the path argument are trimmed before joining.
@vite('resources/js/app.js') emits a script (or link) tag pointing at /build/.... Lists are supported:
@vite('resources/js/app.js')@vite(['resources/js/app.js', 'resources/css/app.css']).css entries become <link rel="stylesheet">; everything else becomes <script type="module">. This helper is intentionally thin — it does not read a Vite manifest. For hot-reload and manifest resolution, see Vite / asset bundling and Inertia.
Edge cases
Section titled “Edge cases”| Situation | Behavior |
|---|---|
Missing key in {{ name }} |
Empty string |
null / nil literal |
Empty string when echoed |
Nested null (user.name when user is null) |
Empty string |
Unclosed {{ or {!! |
Render throws |
| Map vs bean | Maps use key lookup; other objects use getters / zero-arg methods / public fields |
- Layouts & inheritance — share HTML shells across pages
- Control structures — conditionals and loops over the same expressions
- Directive reference — full directive tables