Create display templates for your schemas Last updated: 25. Apr 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.

Important: Display templates must always be placed directly in the # folder (the hash 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 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 parameteres 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 src="#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 schema 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

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