Templates
Kyrux uses Django-style template inheritance. Templates live in apps/<name>/templates/.
Variable convention
| Form | Source | Example |
|---|---|---|
{{ .titulo }} |
View data | map[string]any{"titulo": "..."} |
{{ AppName }} |
Framework | App name from .env |
{{ Version }} |
Framework | App version |
{{ Env }} |
Framework | development / production |
{{ Addr }} |
Framework | 0.0.0.0:8000 |
{{ GoVersion }} |
Framework | go1.22.3 |
{{ url "name" }} |
Framework | Resolves a URL by name |
{{ statics "path/file.css" }} / {{ statics "app" "path/file.css" }} |
Framework | Resolves a static file URL (global or per-app) |
{{ csrf_token }} |
Framework | Hidden security input |
Template inheritance
apps/blog/templates/base.html:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>{% block "title" %}{{ AppName }}{% endblock "title" %}</title>
<link rel="stylesheet" href="{{ statics "blog" "styles/style.css" }}">
</head>
<body>
{% include "partials/navbar.html" %}
<main>
{% block "content" %}{% endblock "content" %}
</main>
{% block "scripts" %}{% endblock "scripts" %}
</body>
</html>
apps/blog/templates/post.html:
{% extends "base.html" %}
{% block "title" %}{{ .post.Titulo }} — {{ AppName }}{% endblock "title" %}
{% block "content" %}
<article>
<h1>{{ .post.Titulo }}</h1>
<p>{{ .post.Conteudo }}</p>
</article>
{% endblock "content" %}
{% block "scripts" %}
<script src="{{ statics "blog" "scripts/post.js" }}"></script>
{% endblock "scripts" %}
Template directives
| Directive | Description |
|---|---|
{% extends "base.html" %} |
Inherits from another template |
{% block "name" %}...{% endblock "name" %} |
Defines/overrides a block |
{% include "partials/nav.html" %} |
Includes a partial template |
Rendering a fragment (for Realtime)
html, err := render.Partial("blog", "partials/lista.html", map[string]any{
"posts": posts,
})
Context Processors — global variables in templates
A ContextProcessor is a function that adds variables to the context of every template in an app — without having to pass them manually in each view.
import "kyrux/core/render"
// Global processor: available in ALL templates of ALL apps
render.AddDefaultProcessor(func(ctx *router.Context) map[string]any {
return map[string]any{
"site_nome": "My Site",
"ano_atual": time.Now().Year(),
}
})
Access in the template (dot notation):
<footer>{{ .site_nome }} — {{ .ano_atual }}</footer>
Processors are cumulative: each call to AddDefaultProcessor adds a new one — it doesn't replace the previous ones.
Custom functions in templates
Register Go functions for use in all templates via render.AddFunc:
import "kyrux/core/render"
// Register before any render call (e.g. in the app's init() or in bootstrap)
render.AddFunc("formatarData", func(t time.Time) string {
return t.Format("02/01/2006")
})
render.AddFunc("upper", strings.ToUpper)
Usage in the template:
<span>{{ .post.CriadoEm | formatarData }}</span>
<h1>{{ .titulo | upper }}</h1>
Static files
There are two kinds of statics, both served automatically at /statics/ by the same handler:
- Per app: live in
apps/<name>/statics/. - Global: live in
statics/at the project root, shared across all apps.
Use the statics function in templates to generate the URL — every argument passed is concatenated (with /) to build the path.
Syntax:
{{ statics "path/file.ext" }} <!-- global static -->
{{ statics "app-name" "path/file.ext" }} <!-- per-app static -->
How resolution works: for each request to /statics/<path>, the framework tries, in this order:
statics/<path>at the project root.- If not found, it uses the first segment of
<path>as the app name and looks inapps/<app>/statics/<rest>.
That's why {{ statics "blog" "styles/base.css" }} generates a URL that includes the app name (/statics/blog/styles/base.css) — this is required for the per-app fallback to work. Meanwhile {{ statics "styles/base.css" }} (no app name) generates /statics/styles/base.css, resolved directly from the root folder.
Examples — per-app static:
<!-- CSS and JS -->
<link rel="stylesheet" href="{{ statics "blog" "styles/base.css" }}">
<script src="{{ statics "blog" "scripts/app.js" }}"></script>
<!-- Fixed image -->
<img src="{{ statics "blog" "images/logo.png" }}" alt="Logo">
<!-- Image with a name coming from the database -->
<img src="{{ statics "blog" "uploads/" .UserPhoto }}" alt="Photo">
<!-- Fully dynamic path -->
<img src="{{ statics "blog" .ImagePath }}" alt="Image">
<!-- Combining prefix, subfolder and variable -->
<img src="{{ statics "blog" "uploads/" .Category "/" .FileName }}" alt="File">
Generated URLs:
/statics/blog/styles/base.css
/statics/blog/uploads/avatar.jpg
Folder structure per app:
apps/
└── blog/
└── statics/
├── styles/ ← CSS
├── scripts/ ← JavaScript
└── images/ ← Images and other assets
Global statics (shared across all apps, resolved with priority over per-app statics):
statics/ ← project root
├── styles/
├── scripts/
└── images/
Examples — global static:
<link rel="stylesheet" href="{{ statics "styles/global.css" }}">
<!-- generates /statics/styles/global.css, resolved from statics/styles/global.css -->
<img src="{{ statics "images/logo.png" }}" alt="Logo">
Use global statics for files shared across apps (e.g. CSS reset, framework JS, fonts, site icons); use per-app statics for files exclusive to one app.