Create display templates for your schemas Last updated: 26. May 2026

Display templates are at the core of how Docly renders your documents as web pages. A .hash file defines the HTML output for all documents of a given schema. This guide explains everything you need to know — from placement and naming, to data binding, loops, master pages, images, and more.

What is a display template?

A display template is a .hash file that transforms a JSON document into HTML. When a visitor opens a document on your site, Docly finds the matching display template and uses it to render the page.

For example, if you have a schema called Blog post, Docly will look for a file called #/Blog post.hash to determine how all Blog post documents should be displayed.

Without a display template, your documents have no visual output — they are just raw JSON data.

Display templates are rendered by HashJS — Docly's JavaScript engine for data binding and server-side rendering, running on V8. The #...# syntax in .hash files is HashJS. See hashjs.org for the full reference.

Important: Display templates must always be placed directly in the # folder. For example: #/Blog post.hash. If you place them in a subfolder like #/Templates/Blog post.hash, they will not be picked up by Docly.

Naming convention

The filename of the .hash file must match the schema name exactly. The format is:

#/[Schema name].hash

Examples:

Schema name: "Employee"       → File: #/Employee.hash
Schema name: "Blog post"      → File: #/Blog post.hash
Schema name: "Code example"   → File: #/Code example.hash
Schema name: "Documentation.5" → File: #/Documentation.5.hash

This site (developers.docly.net) uses several display templates. For instance, #/Code example.hash renders all documents with the "Code example" schema, and #/Documentation.5.hash renders the documentation pages you are reading right now.

Simple data binding with #FieldName#

The most fundamental feature of #JS is data binding. Use #FieldName# to output the value of a field from your document.

Suppose your schema has fields Name, Number, and Email, and a document contains this data:

{
    "Name": "John Doe",
    "Number": 42,
    "Email": "john@example.com"
}

Your display template can render this like so:

<h1>#Name#</h1>
<p>Employee number: #Number#</p>
<a href="mailto:#Email#">Send email to #Name#</a>

Output:

<h1>John Doe</h1>
<p>Employee number: 42</p>
<a href="mailto:john@example.com">Send email to John Doe</a>

It's that simple — every #FieldName# is replaced with the corresponding value from the document.

Code blocks and expressions

#JS has two distinct ways to embed JavaScript:

1. Code blocks #{...}# — for multi-line code where you need to declare variables, perform calculations, or write more complex logic. These blocks do not output anything directly.

2. Single expressions #expression# — for outputting a value, like #Name# or #item.Title.toUpperCase()#. This also applies to control flow statements like #if(...){}# and #for(...){#.

Code block example — declare variables and run logic:

#{
    let greeting = Name ? 'Hello, ' + Name + '!' : 'Hello, stranger!';
    let level = Number > 100 ? 'Senior' : 'Junior';
}#
<h1>#greeting#</h1>
<p>Level: #level#</p>

Control flow with #if# / #for# — these use single # delimiters, not code blocks:

#if (Number > 100) {#
    <span class="badge bg-success">Senior employee</span>
#} else {#
    <span class="badge bg-info">Employee</span>
#}#

Notice the difference: #{...}# wraps a multi-line code block, while #if(...){# and #for(...){# are single statements using plain # delimiters. You can freely mix HTML between the opening and closing statements.

Looping over arrays

If your schema contains arrays (lists of items), you can loop over them using for...of inside #{...}# blocks.

Given this data:

{
    "Numbers": [1, 2, 3],
    "Objects": [
        { "Name": "test 1" },
        { "Name": "test 2" },
        { "Name": "test 3" }
    ]
}

You can loop like this:

<b>Numbers:</b>

#for(let number of Numbers) {#
    <a>#number#</a>
#}#

<b>Objects:</b>

#for(let obj of Objects) {#
    <div>#obj.Name#</div>
#}#

Output:

<b>Numbers:</b>

<a>1</a>
<a>2</a>
<a>3</a>

<b>Objects:</b>

<div>test 1</div>
<div>test 2</div>
<div>test 3</div>

Notice how you access properties on each item using dot notation: #obj.Name#.

Linking images and files

If your schema has image or file fields, you can link to them in your template.

Images: Use the docly.linkImage() function to generate an optimized image URL. The parameters are: field name, width, height, and quality (1-3).

<!-- Single image field called "Image1" -->
<img src="#docly.linkImage(Image1,1024,768,2)#" alt="An image" />

Files: Use the docly.linkFile() function to generate a download URL. The parameters are: field name and optional filename:

<!-- File field called "File1" -->
<a href="#docly.linkFile(File1)#">Download file</a>

Inside loops: When looping over arrays that contain images or files, prefix with the loop variable:

#for (let item of Items) {#
<div>
    <img src="#docly.linkImage(item.Image1,1024,768,2)#" alt="An image" />
    <a href="#docly.linkFile(item.File1)#">Download</a>
</div>
#}#

Images from other documents: Use file.Url with additional parameters to return embedded file or image. Image example:

#for (let file of docly.getFiles('Blog')) {#
<div>
    <img src="#file.Url#/#file.Image1#/800x600x2/image.jpg#" alt="Blog image" />
</div>
#}#

File example:

#for (let file of docly.getFiles('Blog')) {#
<div>
    <a href="#file.Url#/#file.File1#" target="_blank">Download file</a>
</div>
#}#

Using master pages

Most websites share a common layout — header, footer, navigation, etc. Instead of repeating this in every template, you can define a master page and have your display templates inherit from it.

A master page is also a .hash file placed in the # folder. It contains the shared HTML structure with placeholder elements that child templates replace.

Master page example (#/master.hash):

<!DOCTYPE html>
<html lang="en">
<head>
    <title>My Site</title>
    <link rel="stylesheet" href="/assets/style.css">
</head>
<body>
    <header>
        <h1>My Website</h1>
        <nav>...</nav>
    </header>

    <main id="content">
        <!-- Child templates replace this -->
    </main>

    <footer>
        <p>© 2024 My Website</p>
    </footer>
</body>
</html>

Child template example (#/Blog post.hash):

<!--#master file="#/master.hash" -->
<html lang="en">
<head>
    <title xdt:Transform="Replace">#Title# - My Site</title>
</head>
<body>
    <main id="content" xdt:Transform="Replace">
        <article>
            <h1>#Title#</h1>
            <p class="lead">#Introduction#</p>
            <div>#Body#</div>
        </article>
    </main>
</body>
</html>

Key points:

  • The first line <!--#master file="#/master.hash" --> tells Docly which master page to use.
  • Use xdt:Transform="Replace" on elements to replace the corresponding element in the master page. A child element is matched to the master by path (tag hierarchy); if the child has an id, it matches tag[id=X] anywhere in the master. Use xdt:Locator="Match(attr)" to match on any other attribute.
  • Use xdt:Transform="Remove" to remove an element from the master page that you don't want in this template. Other actions like Insert, InsertBefore(selector), InsertAfter(selector), SetAttributes, RemoveAll, RemoveAttributes(...) and Create let you extend, reposition, patch attributes or add new elements. Action names are case-insensitive.
  • For the full reference of all actions and targeting modes, see the Master directive reference.

Include files

You can split your templates into reusable parts and include them using <!--#include -->:

<!--#include file="#/Header.hash" -->
<!--#include file="#/Sidenav.hash" -->
<!--#include file="#/Footer.hash" -->

This is useful for navigation menus, sidebars, footers, or any reusable component. The included files also have access to the same document data and can use all #JS features.

The request object

Inside your templates, the request object gives you access to useful metadata about the current page and request:

<!-- Page title (document filename without extension) -->
<title>#request.title#</title>

<!-- Current URL -->
<a href="#request.Url#">Link to this page</a>

<!-- Current folder path -->
<p>You are in: #request.folderpath#</p>

A complete example from this site

This very developer site uses display templates extensively. Here is a simplified version of how the Code example display template works.

The schema (#/Code example.docly) defines fields like Description, Data, and Code. The template (#/Code example.hash) renders them:

<!--#master file="#/master.hash" -->
<html lang="en">
<head>
    <title xdt:Transform="Replace">#request.title# - Docly dev</title>
</head>
<body>
    <main id="main" xdt:Transform="Replace">
        <div class="docs-wrapper">
            <div class="docs-content">
                <div class="container">
                    <h1>#request.title#</h1>
                    <p>#Description#</p>

                    <h3>Data</h3>
                    <pre><code>#Data#</code></pre>

                    <h3>Code</h3>
                    <pre><code>#Code#</code></pre>
                </div>
            </div>
        </div>
    </main>
</body>
</html>

When you open a Code example document (like Simple binding), Docly automatically:

  1. Finds the document and its schema ("Code example")
  2. Looks for #/Code example.hash
  3. Merges it with the master page #/master.hash
  4. Binds all #FieldName# placeholders with the document data
  5. Sends the final HTML to the browser

Debugging display templates

Display templates can fail in subtle ways — the page renders, but the content is wrong, or part of the output is replaced with raw #variable#-strings. This section lists the most common failure modes and how to diagnose them.

Symptom

Likely cause

Resolution

The document renders as raw JSON

The .hash file is not in the # folder, or the filename does not match the schema name

Verify the file is located at #/<Schema name>.hash

#variable# is printed as literal text

HashJS syntax is wrong, or the variable does not exist on the document

Check the variable name in the schema; use #typeof(variable)# to debug whether it is defined and what type it is

Output shows the wrong user or otherwise stale content

Per-request data placed directly inside #...# gets cached on the first visitor and reused for everyone else

Move dynamic / per-user data into an #/API/ endpoint and fetch it from browser-side JavaScript. See the KB article Hash files are cached

A loop produces no rows

The expression driving the loop (e.g. data-loop or for(let item of ...)) returns null or an empty array

Test the expression in isolation with #typeof(getpages(...))# and verify the input data exists

The page returns a 500 error

A syntax error somewhere in the .hash template

Check the server log / Docly's error reporting for the exact line and message

If your template still misbehaves after checking these, the next step is to isolate the issue: copy the template to a minimal test document with hardcoded data, then add expressions back one at a time until the failure reappears.

Related: Hash files are cached — explains the caching model in detail and why per-request data must come from #/API/ endpoints.

Summary and checklist

Rule Details
Placement Always in the # folder directly — not in a subfolder
Filename Must match the schema name exactly: #/[Schema name].hash
Simple binding Use #FieldName# to output field values
Code blocks Use #{...}# for multi-line code (variables, logic)
Expressions & control flow Use #expression# for single expressions, or #if(...){# / #for(...){# for control flow
Arrays Use #for(let item of Items) {# ... #}#
Images Use #docly.linkImage(FieldName,width,height,quality)#
Files Use #docly.linkFile(FieldName)#
Master pages Use <!--#master file="#/master.hash" -->
Includes Use <!--#include file="#/filename.hash" -->

Related resources

Before you create a display template, you need a schema. See About schemas for the basics, and Understanding and Using Templates for an overview of all template types.