Gotmx escapes HTML by default to prevent XSS (Cross-Site Scripting) attacks. Understanding the escaping rules for each attribute ensures your templates handle user input safely.
Gotmx attributes fall into three categories based on how they handle HTML escaping. Each category exists for a specific reason.
| Attribute | Escaping | Why |
|---|---|---|
data-g-inner-text |
Always escaped | This is the recommended default for displaying any text content, especially user input. It escapes unconditionally — even Unescaped() cannot disable it. This guarantee means you never have to think about whether a value is safe. |
data-g-att-* |
Always escaped | Attribute values must always be escaped to prevent attribute injection attacks. There is no scenario where unescaped attribute values are safe. |
| Attribute | Escaping | Why |
|---|---|---|
data-g-outer-text |
Follows ctx.Escaped |
Replaces the entire element with text. Because it replaces the element itself (rather than injecting content into it), it participates in the global escaping setting. This allows Unescaped() to affect the output when rendering fully trusted content. By default (escaped mode), it is safe for user input. |
Text nodes with [[ .Path ]] |
Follows ctx.Escaped |
Same reasoning as data-g-outer-text — text nodes in the template body follow the global setting. Safe by default. |
| Attribute | Escaping | Why |
|---|---|---|
data-g-inner-html |
Never escaped | Explicitly designed for injecting pre-sanitized HTML. The name "inner-html" signals that the value is treated as raw HTML. |
data-g-as-unsafe-template |
No escaping | Uses Go's text/template, which has no auto-escaping. The word "unsafe" in the name is intentional. |
This is the most common source of confusion. The difference is intentional:
data-g-inner-textis the primary attribute for displaying user-facing text. It is designed to be unconditionally safe — no configuration, no flags, no footguns. When you usedata-g-inner-text, you never have to worry about the escaping state.data-g-outer-textreplaces the entire element and is typically used for computed values or template references rendered to strings. It follows the globalEscapedflag because there are legitimate use cases where the caller controls both the data and the escaping context.
Rule of thumb: When in doubt, use data-g-inner-text. It is always safe.
<!-- SAFE: inner-text always escapes, even with Unescaped() -->
<span data-g-inner-text="[[ .UserComment ]]">preview</span>When UserComment contains <script>alert('xss')</script>:
<!-- Output (default): -->
<span><script>alert('xss')</script></span>
<!-- Output (with Unescaped()): SAME — still escaped -->
<span><script>alert('xss')</script></span><!-- SAFE: attribute values are always escaped -->
<a data-g-att-href="[[ .UserUrl ]]"
data-g-att-title="[[ .UserBio ]]">
Profile
</a>When UserUrl contains javascript:alert('xss') or UserBio contains " onclick="alert('xss'):
<!-- Output: attributes are escaped, injection is neutralized -->
<a href="javascript:alert('xss')"
title="" onclick="alert('xss')">
Profile
</a>Attribute values are always HTML-escaped, preventing attribute injection attacks.
<!-- DANGEROUS: inner-html never escapes -->
<div data-g-inner-html="[[ .Content ]]">preview</div>When Content contains <script>alert('xss')</script>:
<!-- Output: the script tag is rendered as-is — XSS vulnerability! -->
<div><script>alert('xss')</script></div>If you must render HTML from user input, sanitize it server-side first (e.g., with bluemonday).
Given data = {"Value": "<b>Hello</b>"}:
<!-- data-g-inner-text: always escapes -->
<span data-g-inner-text="[[ .Value ]]">preview</span>
<!-- Default: <span><b>Hello</b></span> -->
<!-- Unescaped(): <span><b>Hello</b></span> (same!) -->
<!-- data-g-outer-text: follows global setting -->
<span data-g-outer-text="[[ .Value ]]">preview</span>
<!-- Default: <b>Hello</b> (escaped, element replaced) -->
<!-- Unescaped(): <b>Hello</b> (NOT escaped!) -->
<!-- data-g-inner-html: never escapes -->
<span data-g-inner-html="[[ .Value ]]">preview</span>
<!-- Default: <span><b>Hello</b></span> (raw HTML) -->
<!-- Unescaped(): <span><b>Hello</b></span> (same!) -->By default, all rendering escapes HTML. The Unescaped() render option disables escaping for text nodes and data-g-outer-text, but critically:
data-g-inner-textstill escapes (unconditionally safe)- Attribute values still escape (unconditionally safe)
data-g-inner-htmlnever escapes regardless
// Default: escaped
engine.Render(ctx, w, "template", data)
// Unescaped: only affects text nodes and g-outer-text
engine.Render(ctx, w, "template", trustedData, gotmx.Unescaped())Use Unescaped() only when you control the data source entirely (e.g., rendering a static page with no user input).
When using inline Go templates:
<!-- SAFE: data-g-as-template uses html/template (auto-escaped) -->
<div data-g-as-template="">Hello, {{ .Name }}!</div>
<!-- DANGEROUS: data-g-as-unsafe-template uses text/template (no escaping) -->
<div data-g-as-unsafe-template="">{{ .TrustedHtml }}</div>Prefer data-g-as-template over data-g-as-unsafe-template unless you specifically need unescaped output from trusted sources.
- Default to
data-g-inner-textfor displaying any data. It is the safest option and cannot be misconfigured. - Use
data-g-att-*for dynamic attributes. They are always escaped. - Use
data-g-inner-htmlonly for content you have sanitized server-side (e.g., with bluemonday). - Never pass
Unescaped()when rendering pages with user-generated content. - Sanitize on the server, not in templates — templates are the last line of defense, not the first.
- Use
data-g-as-template(notdata-g-as-unsafe-template) for inline Go templates that include user data.