On this page
views
introduction
dframework ships with a custom view engine. it uses a string to function evaluation model that compiles your templates into raw javascript functions. this means you can write standard javascript directly inside your templates while utilizing convenient directives for common tasks.
creating views
views are stored in the views directory and must use the .d file extension. they are referenced using dot notation. a file at views/app/home/index.d is referenced as app.home.index.
1<!-- views/greetings/hello.d -->2<html>3 <body>4 <h1>hello, {{ name }}!</h1>5 </body>6</html>you return views from your controllers using the render() helper.
1export default class GreetingController {2 async hello() {3 return render('greetings.hello', { name: 'world' });4 }5}dangerous javascript globals such as `process`, `require`, and `global` cannot be passed into view data. the engine strictly validates the view context and throws an error if tampering is detected.
displaying data
you display data passed to your views by wrapping the variable in double curly braces. the view engine automatically escapes the output to prevent xss attacks.
1hello, {{ user.name }}.2the current timestamp is {{ Date.now() }}.
if you need to render unescaped html, use the {!! !!} syntax.
1{!! user.bio_html !!}
you can convert objects to json strings using the @json directive.
1<script>[object Object]>2 const userState = @json(user);[object Object]>3</script>
control structures
the view engine provides shortcuts for common javascript control structures.
if statements
1@if (user.isAdmin)2 <button>admin panel</button>3@elseif (user.isModerator)4 <button>mod panel</button>5@else6 <span>standard user</span>7@endifloops
1@foreach (users as user)2 <li>{{ user.name }}</li>3@endforeach4 5@for (let i = 0; i < 10; i++)6 <span>{{ i }}</span>7@endforthe @forelse directive is a convenient way to loop over arrays with a fallback if the array is empty.
1@forelse (tracks as track)2 <li>{{ track.title }}</li>3@empty4 <li>no tracks found.</li>5@endforelse
layouts and includes
you can define a master layout and extend it from child views. use @yield to mark where content should be injected, and @section in the child view to provide that content.
1<!-- views/layouts/app.d -->2<html>3 <head>4 <title>app</title>5 </head>6 <body>7 <nav>...</nav>8 <main>9 @yield('content')10 </main>11 </body>12</html>1<!-- views/home.d -->2@extends('layouts.app')3 4@section('content')5 <h1>welcome home</h1>6@endsectionyou can include partials using the @include directive.
1@include('components.header')
javascript execution
since the view engine compiles directly to javascript functions, you can execute arbitrary server side code directly inside your templates. use the @js() directive for a single line of javascript, or the @js ... @endjs block for multiple lines.
1@js(const maxLimit = 50;)2 3@js4 let sortedUsers = users.sort((a, b) => b.score - a.score);5 sortedUsers = sortedUsers.slice(0, maxLimit);6@endjs7 8@foreach (sortedUsers as user)9 <li>{{ user.name }}</li>10@endforeach
localization
you can translate strings using the @t() directive. the framework automatically resolves the current locale from the request context and fetches the corresponding translation string.
1<h1>@t('home.welcome_message')</h1>2<p>@t('home.unread_count', { count: 5 })</p>
pagination
the view engine integrates with the framework Paginator class returned by model .paginate() methods. paginators are directly iterable in @foreach loops and provide automatic or custom html pagination controls.
1@foreach (users as user)2 <div>{{ user.name }}</div>3@endforeach4 5<!-- renders standard pagination controls (standalone directive) -->6@pagination(users)alternatively, you can render links directly using unescaped output:
1{!! users.links() !!}
existing url query parameters (like search terms or filter keys) are automatically preserved on all generated links.
by default, links render as standard anchors for full page navigation. to enable spa transitions, pass true as the second argument:
1<!-- enables spa navigation -->2@pagination(users, true)to update a specific container instead of a full page transition, pass a css selector as the third argument:
1<!-- updates the #user-list container via spa -->2@pagination(users, true, '#user-list')custom pagination html
if you provide content inside the @pagination block, the engine skips default html generation and renders your custom markup.
inside the block, the engine automatically exposes four variables that you can use to build your custom controls:
prev: previous page url (ornull)next: next page url (ornull)current: current page numberlast: total page counttotal: total item countperPage: items per pagepaginator: the paginator instance
1@pagination(users)2 <div class="custom-pagination">3 <p>page {{ current }} of {{ last }} ({{ total }} users)</p>4 5 @if (prev)6 <a href="{{ prev }}" class="btn">previous</a>7 @endif8 9 @if (next)10 <a href="{{ next }}" class="btn">next</a>11 @endif12 </div>13@endpaginationthe framework evaluates the block only when there is more than one page (last > 1). if there is only one page, the entire @pagination block is skipped.
automatic form handling
when you create an html <form> with a mutating method (e.g. POST, PUT), the view engine automatically injects a hidden _csrf input field and the current csrf token value. you do not need to manually output csrf tokens in your forms.
additionally, the engine injects a <meta name="csrf-token"> tag into the document's <head>, allowing your ajax requests to pick up the token.
view compilation
in the local environment, the framework uses a custom jit compiler and provides detailed error traces mapped directly to your original .d files.
in the production environment, views are ahead of time compiled into highly optimized .dc (.d compiled) files. the framework verifies the cryptographic integrity of these .dc files on startup to ensure your templates have not been tampered with.
compiled views are retained in an LRU cache capped at app.maxCompiledCacheSize entries (default 500). rendering a cached view touches it back to most recently used, so hot views stay resident under churn. see configuration > performance tuning keys for details.

