MODX Custom Template Development
A MODX template is an HTML document with MODX placeholders. No mandatory structure, no imposed classes. Complete markup control — one of MODX's main advantages for developers.
Project Files Structure
assets/
├── css/
│ └── app.css # Compiled CSS
├── js/
│ └── app.js # Compiled JS
└── images/
└── logo.svg
src/ # Sources (Vite/SCSS)
├── scss/
│ ├── main.scss
│ ├── _variables.scss
│ └── components/
└── js/
└── main.js
MODX stores templates in database (via Manager), chunks too. For versioning use FileBasedOverrides or StaticElements — file system storage with database synchronization.
Main Template
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>[[*longtitle:default=`[[*pagetitle]]`]] — [[++site_name]]</title>
<meta name="description" content="[[*description:notempty=`[[*description]]`:default=`[[++site_tagline]]`]]">
[[++seo_canonical]]
<link rel="stylesheet" href="[[++assets_url]]css/app.css?v=[[++build_version]]">
<link rel="icon" href="[[++assets_url]]images/favicon.ico">
</head>
<body class="[[*class_key:is=`modWebLink`:then=`page-link`:else=`page`]]">
[[$header]]
<main id="main" class="site-main">
[[*content]]
</main>
[[$footer]]
<script src="[[++assets_url]]js/app.js?v=[[++build_version]]" defer></script>
[[- Debug: [[++debug:is=`1`:then=`[[!session]]`]] ]]
</body>
</html>
Header Chunk
[[- @FILE chunks/header.html ]]
<header class="site-header [[+scrolled:isnotempty=`site-header--scrolled`]]">
<div class="container">
<a href="[[++site_url]]" class="site-logo">
<img src="[[++assets_url]]images/logo.svg" alt="[[++site_name]]" width="160" height="40">
</a>
<nav class="site-nav" role="navigation" aria-label="Main menu">
[[pdoMenu?
&startId=`0`
&level=`2`
&tplOuter=`@INLINE <ul class="nav-list">[[+wrapper]]</ul>`
&tpl=`@INLINE <li class="nav-item [[+classes]]"><a href="[[+link]]" [[+attributes]]>[[+menutitle]]</a>[[+wrapper]]</li>`
&tplInner=`@INLINE <ul class="nav-submenu">[[+wrapper]]</ul>`
&hereClass=`nav-item--active`
&parentClass=`nav-item--parent`
&sortby=`menuindex`
]]
</nav>
<button class="nav-toggle" aria-label="Open menu" aria-expanded="false">
<span></span><span></span><span></span>
</button>
</div>
</header>
Template for Different Page Types
For inner pages — separate template or conditions via TV:
[[- Inner page template ]]
<div class="page-layout [[*tv.sidebar_position:is=`right`:then=`page-layout--sidebar-right`:else=``]]">
<div class="page-content">
[[*content]]
</div>
[[- Show sidebar if TV enabled ]]
[[*tv.show_sidebar:is=`1`:then=`[[$sidebar]]`]]
</div>
Breadcrumbs via pdoTools
[[$breadcrumbs]]
[[pdoCrumbs?
&tplWrapper=`@INLINE <nav aria-label="Breadcrumbs"><ol class="breadcrumbs">[[+output]]</ol></nav>`
&tpl=`@INLINE <li class="breadcrumb-item"><a href="[[+link]]">[[+menutitle]]</a></li>`
&tplCurrent=`@INLINE <li class="breadcrumb-item breadcrumb-item--current" aria-current="page">[[+menutitle]]</li>`
&tplHome=`@INLINE <li class="breadcrumb-item"><a href="[[+link]]">Home</a></li>`
]]
Static Elements (File-Based Template Storage)
// In MODX template:
// Type: Static template
// File: [[++base_path]]templates/main.tpl
// Export all elements to files:
$modx->runProcessor('element/template/export', [
'id' => $templateId
]);
Plugin StaticElements or FileBasedOverrides automates synchronization.
Vite for Building
// vite.config.js
export default {
build: {
outDir: 'assets',
rollupOptions: {
input: { app: 'src/js/main.js' },
output: {
assetFileNames: 'css/[name].[ext]',
entryFileNames: 'js/[name].js',
chunkFileNames: 'js/[name].js'
}
}
},
server: {
proxy: {
'/': 'http://yourdomain.local'
}
}
};
Timeline
Template set development (homepage + inner page + blog + 404) without design — 2–3 days. From finished design with 8–12 page types — 2–3 weeks.







