Templates & Frontend
Build a Joomla Template From an Empty Folder
A Joomla template is ordinary HTML with a handful of placeholders in it. Strip away the frameworks and the starter kits and a working template is three files. Building one by hand is the fastest way to understand what all the others are…
What jdoc placeholders are
Joomla renders your template as a document, then replaces each jdoc:include tag with the output it names:
<jdoc:include type="modules" name="sidebar" style="card" />
type— what to render:modules,component,message,metas,styles,scripts.name— for modules, the position name that appears in the module manager.style— the module chrome, which wraps each module in markup. Omit it and modules render bare, without their titles.
That is the entire integration surface. Everywhere else you are writing plain HTML.
The folder layout
Two locations, and mixing them up is the most common first mistake:
templates/mytemplate/
templateDetails.xml
index.php
error.php
media/templates/site/mytemplate/
css/template.css
js/template.js
PHP lives under templates/, assets under media/templates/site/. Putting CSS inside the template folder puts it where the media handling does not look for it — a mistake worth knowing about, because plenty of copy-paste examples still do it.
templateDetails.xml
<?xml version="1.0" encoding="UTF-8"?>
<extension type="template" client="site">
<name>mytemplate</name>
<version>1.0.0</version>
<creationDate>2026-08</creationDate>
<author>Your name</author>
<description>A minimal site template.</description>
<files>
<filename>index.php</filename>
<filename>error.php</filename>
<filename>templateDetails.xml</filename>
<folder>html</folder>
</files>
<media destination="templates/site/mytemplate" folder="media">
<folder>css</folder>
<folder>js</folder>
</media>
<positions>
<position>header</position>
<position>menu</position>
<position>sidebar</position>
<position>footer</position>
<position>debug</position>
</positions>
</extension>
There is no <!DOCTYPE install> line and no version attribute on <extension>. If you copy a manifest that has them, delete them — nothing reads them.
The <files> list matters only for installation: name something that is not there and the installer fails. The <positions> list is what populates the position dropdown in the module manager — a position works without being listed, but nobody will be able to select it.
index.php
<?php
defined('_JEXEC') or die;
$wa = $this->getWebAssetManager();
$wa->registerAndUseStyle(
'tpl.mytemplate',
'media/templates/site/mytemplate/css/template.css',
['version' => 'auto']
);
?>
<!DOCTYPE html>
<html lang="<?php echo $this->language; ?>" dir="<?php echo $this->direction; ?>">
<head>
<jdoc:include type="metas" />
<jdoc:include type="styles" />
<jdoc:include type="scripts" />
</head>
<body>
<header>
<jdoc:include type="modules" name="header" style="none" />
</header>
<?php if ($this->countModules('menu')) : ?>
<nav><jdoc:include type="modules" name="menu" style="none" /></nav>
<?php endif; ?>
<main>
<jdoc:include type="message" />
<jdoc:include type="component" />
</main>
<?php if ($this->countModules('sidebar')) : ?>
<aside><jdoc:include type="modules" name="sidebar" style="card" /></aside>
<?php endif; ?>
<footer>
<jdoc:include type="modules" name="footer" style="none" />
</footer>
<jdoc:include type="modules" name="debug" style="none" />
</body>
</html>
Three things worth pointing out.
The head is three placeholders, not one. type="head" from Joomla 1.x and 2.x is replaced by metas, styles and scripts, which is what lets you control where scripts land.
Register assets, do not hard-code link tags. The asset manager handles versioning — that version => auto appends a cache-busting hash — and dependency ordering, so an extension asking for your stylesheet gets it in the right order.
Wrap positions in countModules(). Otherwise an unpublished sidebar leaves an empty <aside> holding its width and padding.
Registering it
Zip the template folder and install it through System → Install → Extensions. Joomla reads templateDetails.xml, copies the media folder into place and registers the positions. Then make it the default in System → Site Templates Styles.
For local work you can drop the folders straight onto disk and use the discover installer instead — faster than rebuilding a zip on every change.
Where to go next
Add error.php so 404s stay in your design, and html/ for layout overrides when the default component output does not fit. Both are optional to start; neither is optional in a template anyone else has to live with.