Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/rbbydotdev/opal/llms.txt

Use this file to discover all available pages before exploring further.

Opal’s template system allows you to wrap your markdown content in HTML templates to create complete static websites.

Template Engines

Opal supports four popular template engines:

EJS

Embedded JavaScript templates with full JS syntax

Mustache

Logic-less templates for simple substitution

Nunjucks

Rich templating inspired by Jinja2

Liquid

Safe templates with simple syntax

Template Basics

Templates are files in your workspace with specific extensions:
  • EJS: .ejs or .eta files
  • Mustache: .mustache files
  • Nunjucks: .njk or .nunjucks files
  • Liquid: .liquid files
  • HTML: .html files (no processing)
Templates can include markdown content and have access to workspace data

Creating Templates

1

Create template file

Right-click in file tree and create a new file with template extension
2

Write template markup

Use template syntax to define structure and include content
3

Preview

Open template file to see rendered preview
4

Use in build

Templates are automatically applied during static site build

EJS Templates

EJS templates use JavaScript syntax for logic:
<!DOCTYPE html>
<html>
<head>
  <title><%= title %></title>
</head>
<body>
  <h1><%= heading %></h1>
  
  <% if (showContent) { %>
    <div class="content">
      <%- content %>
    </div>
  <% } %>
  
  <ul>
    <% items.forEach(item => { %>
      <li><%= item %></li>
    <% }) %>
  </ul>
</body>
</html>
  • <%= value %>: Output escaped value
  • <%- value %>: Output raw HTML (unescaped)
  • <% code %>: Execute JavaScript code
  • <%# comment %>: Template comments
  • <%- include('partial') %>: Include another template

Markdown in EJS

EJS templates can import markdown content:
<article>
  <%- await include('content/post.md', { process: 'markdown' }) %>
</article>
Use await include() with markdown files to process them before inclusion

Mustache Templates

Mustache provides logic-less templates:
<!DOCTYPE html>
<html>
<head>
  <title>{{title}}</title>
</head>
<body>
  <h1>{{heading}}</h1>
  
  {{#showContent}}
    <div class="content">
      {{{content}}}
    </div>
  {{/showContent}}
  
  <ul>
    {{#items}}
      <li>{{.}}</li>
    {{/items}}
  </ul>
</body>
</html>
  • {{value}}: Output escaped value
  • {{{value}}}: Output raw HTML
  • {{#section}}...{{/section}}: Conditional section
  • {{^section}}...{{/section}}: Inverted section
  • {{!comment}}: Template comments
  • {{>partial}}: Include partial template

Nunjucks Templates

Nunjucks offers rich templating features:
<!DOCTYPE html>
<html>
<head>
  <title>{{ title }}</title>
</head>
<body>
  <h1>{{ heading }}</h1>
  
  {% if showContent %}
    <div class="content">
      {{ content | safe }}
    </div>
  {% endif %}
  
  <ul>
    {% for item in items %}
      <li>{{ item }}</li>
    {% endfor %}
  </ul>
</body>
</html>
  • {{ value }}: Output escaped value
  • {{ value | safe }}: Output raw HTML
  • {% if condition %}...{% endif %}: Conditional
  • {% for item in list %}...{% endfor %}: Loop
  • {# comment #}: Template comments
  • {% include "partial.njk" %}: Include template
  • {{ value | filter }}: Apply filters

Nunjucks Filters

Apply transformations with filters:
{{ title | upper }}
{{ date | date("YYYY-MM-DD") }}
{{ content | markdown | safe }}

Liquid Templates

Liquid provides safe templating:
<!DOCTYPE html>
<html>
<head>
  <title>{{ title }}</title>
</head>
<body>
  <h1>{{ heading }}</h1>
  
  {% if showContent %}
    <div class="content">
      {{ content }}
    </div>
  {% endif %}
  
  <ul>
    {% for item in items %}
      <li>{{ item }}</li>
    {% endfor %}
  </ul>
</body>
</html>
  • {{ value }}: Output value
  • {% if condition %}...{% endif %}: Conditional
  • {% for item in array %}...{% endfor %}: Loop
  • {% comment %}...{% endcomment %}: Comments
  • {% include "partial" %}: Include template
  • {{ value | filter }}: Apply filters

Template Data

Templates have access to various data:

Available Variables

{
  // Custom data you provide
  title: "Page Title",
  content: "<p>Rendered markdown</p>",
  
  // Workspace context
  workspace: {
    name: "my-site",
    files: [...]
  },
  
  // File context
  currentFile: {
    path: "/index.md",
    name: "index.md"
  }
}

Passing Custom Data

Provide data when rendering:
const templateManager = new TemplateManager(workspace);

await templateManager.renderTemplate(
  absPath("/templates/layout.ejs"),
  {
    title: "My Page",
    author: "Jane Doe",
    date: new Date()
  }
);

Template Organization

Organize templates for maintainability:
Create base layouts:
/templates/
  base.ejs          # Main layout
  minimal.ejs       # Minimal layout
  sidebar.ejs       # Layout with sidebar

Including Partials

Reuse template fragments:
<%- include('partials/header.ejs', { title: 'Home' }) %>
<main>
  <%- content %>
</main>
<%- include('partials/footer.ejs') %>

Error Handling

When template errors occur:
  • Syntax errors: Highlighted in editor with line numbers
  • Runtime errors: Displayed with stack trace
  • Missing includes: Clear error message with path
  • Variable errors: Shows undefined variable name
Template errors prevent build completion. Fix errors before deploying.

Best Practices

  • Minimize logic in templates
  • Use helpers for complex operations
  • Separate data processing from presentation
  • Prefer partials over duplication
Choose the right template engine:
  • EJS: When you need JavaScript logic
  • Mustache: For simple variable substitution
  • Nunjucks: For feature-rich templates
  • Liquid: For safe, sandboxed templates
Optimize performance:
  • Pre-compute expensive operations
  • Reuse data across templates
  • Minimize file system access
  • Use workspace data efficiently
Track template changes:
  • Commit templates to git
  • Document template variables
  • Test templates before deploying
  • Maintain changelog for major changes

Advanced Features

Custom Filters

Extend template engines with custom filters:
// Add custom filter for date formatting
const formatted = value.toLocaleDateString('en-US', {
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

Template Inheritance

Create template hierarchies:
{# base.njk #}
<!DOCTYPE html>
<html>
<head>
  {% block head %}
    <title>{% block title %}{% endblock %}</title>
  {% endblock %}
</head>
<body>
  {% block content %}{% endblock %}
</body>
</html>

{# page.njk #}
{% extends "base.njk" %}

{% block title %}My Page{% endblock %}

{% block content %}
  <h1>Welcome</h1>
{% endblock %}
Template inheritance is supported in Nunjucks and Liquid

Debugging Templates

Troubleshoot template issues:
1

Check syntax

Verify template syntax is correct for the engine
2

Inspect variables

Log available data to console
3

Test partials

Verify included templates exist and work
4

Preview output

Use live preview to see rendered result
Enable detailed error messages in development mode for better debugging