7.1 KiB
How to Create Custom Templates
This guide shows you how to override default templates with your own custom designs.
Overview
FolderWeb uses a template fallback system:
- Check
/custom/templates/for custom version - Fall back to
/app/default/templates/if not found
Important: Never modify files in /app/default/ — always create custom versions in /custom/.
Available Templates
- base.php - HTML wrapper (header, navigation, footer)
- page.php - Single page/article wrapper
- list.php - Simple list view (default)
- list-grid.php - Grid layout with images
- list-card-grid.php - Card grid (supports PDFs, external links)
- list-faq.php - Expandable FAQ/Q&A format
Customizing the Base Template
The base template controls your entire site layout.
Step 1: Copy the Default
mkdir -p custom/templates
cp app/default/templates/base.php custom/templates/base.php
Step 2: Edit Your Copy
The base template has access to these variables:
$content // The rendered page content (HTML)
$currentLang // Current language code (e.g., "en", "no")
$navigation // Array of navigation items
$homeLabel // Site title
$translations // Translation strings
$pageTitle // Current page title
$dirName // Parent directory name (for CSS classes)
$pageName // Current page name (for CSS classes)
Example: Add a Custom Header
<header class="site-header">
<div class="contain">
<a href="<?= $ctx->langPrefix ?>/" class="logo">
<img src="/custom/assets/logo.svg" alt="<?= $homeLabel ?>">
</a>
<nav>
<ul>
<?php foreach ($navigation as $item): ?>
<li>
<a href="<?= $item['url'] ?>"><?= htmlspecialchars($item['title']) ?></a>
</li>
<?php endforeach; ?>
</ul>
</nav>
</div>
</header>
Customizing the Page Template
The page template wraps individual articles and pages.
Step 1: Copy the Default
cp app/default/templates/page.php custom/templates/page.php
Step 2: Customize
Available variables:
$content // Main content HTML
$pageMetadata // Array of metadata (tags, categories, etc.)
$translations // Translation strings
Example: Add Author Information
<article>
<?php if (isset($pageMetadata['author'])): ?>
<div class="author-info">
<p>Written by <?= htmlspecialchars($pageMetadata['author']) ?></p>
</div>
<?php endif; ?>
<?= $content ?>
<?php if (isset($pageMetadata['tags'])): ?>
<div class="tags">
<strong><?= $translations['tags'] ?>:</strong>
<?php foreach ($pageMetadata['tags'] as $tag): ?>
<span class="tag"><?= htmlspecialchars($tag) ?></span>
<?php endforeach; ?>
</div>
<?php endif; ?>
</article>
Creating a Custom List Template
List templates control how directories with subdirectories are displayed.
Step 1: Create Your Template
touch custom/templates/list-custom.php
Step 2: Use List Template Variables
All list templates receive:
$items // Array of subdirectories
$metadata // Directory metadata
$pageContent // Optional intro content
$translations // Translation strings
Each item in $items has:
[
'title' => 'Post Title',
'date' => '2. november 2025',
'url' => '/blog/2025-11-02-post/',
'cover' => '/blog/2025-11-02-post/cover.jpg',
'summary' => 'Brief description',
'pdf' => '/blog/2025-11-02-post/document.pdf',
'redirect' => 'https://external-site.com'
]
Example: Timeline Template
<?php if (!empty($pageContent)): ?>
<div class="page-intro">
<?= $pageContent ?>
</div>
<?php endif; ?>
<div class="timeline">
<?php foreach ($items as $item): ?>
<article class="timeline-item">
<time><?= $item['date'] ?></time>
<h2>
<a href="<?= $item['url'] ?>"><?= htmlspecialchars($item['title']) ?></a>
</h2>
<?php if ($item['summary']): ?>
<p><?= htmlspecialchars($item['summary']) ?></p>
<?php endif; ?>
</article>
<?php endforeach; ?>
</div>
Step 3: Apply Your Template
Create a metadata.ini in the directory:
page_template = "list-custom"
Template Best Practices
Always Escape Output
Prevent XSS attacks by escaping user-generated content:
<?= htmlspecialchars($item['title']) ?>
Use Short Echo Tags
FolderWeb uses modern PHP, so short tags are always available:
<?= $variable ?> // Good
<?php echo $variable; ?> // Also works, but verbose
Check Before Using
Always check if variables exist:
<?php if (isset($item['cover']) && $item['cover']): ?>
<img src="<?= $item['cover'] ?>" alt="">
<?php endif; ?>
Leverage CSS Classes
The base template adds dynamic classes to <body>:
<body class="section-<?= $dirName ?> page-<?= $pageName ?>">
Use these for page-specific styling without JavaScript.
Advanced: Accessing the Context Object
Templates can access the full context object $ctx:
<?php
// Available properties:
$ctx->contentDir // Path to content directory
$ctx->currentLang // Current language
$ctx->defaultLang // Default language
$ctx->availableLangs // Array of available languages
$ctx->langPrefix // URL prefix (e.g., "/en" or "")
$ctx->requestPath // Current request path
$ctx->hasTrailingSlash // Boolean
$ctx->navigation // Navigation array (computed property)
$ctx->homeLabel // Site title (computed property)
$ctx->translations // Translation array (computed property)
?>
Example: Breadcrumb Navigation
Add breadcrumbs to your page template:
<nav class="breadcrumbs" aria-label="Breadcrumb">
<ol>
<li><a href="<?= $ctx->langPrefix ?>/">Home</a></li>
<?php
$parts = array_filter(explode('/', trim($ctx->requestPath, '/')));
$path = '';
foreach ($parts as $i => $part):
$path .= '/' . $part;
$isLast = ($i === count($parts) - 1);
?>
<li<?= $isLast ? ' aria-current="page"' : '' ?>>
<?php if ($isLast): ?>
<?= htmlspecialchars($part) ?>
<?php else: ?>
<a href="<?= $ctx->langPrefix . $path ?>/">
<?= htmlspecialchars($part) ?>
</a>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ol>
</nav>
Testing Your Templates
- Clear your browser cache
- Reload the page
- Check browser console for errors
- Validate HTML with W3C validator
Reverting Changes
To revert to default templates, simply delete your custom version:
rm custom/templates/base.php
FolderWeb will automatically fall back to the default.