Theme Development
Create beautiful, custom themes for VoidForge CMS. Learn the template system, menus, settings, and best practices.
Introduction
VoidForge themes control your site's appearance and layout. Themes are built with PHP, HTML, CSS, and JavaScript, using a simple template system.
📄 Template Hierarchy
Flexible template system for any content type
🎨 Theme Settings
Built-in customization options
📍 Navigation Menus
Multiple menu locations support
🪝 Hook Integration
Extend functionality with hooks
Theme Structure
Each theme lives in its own folder within /content/themes/.
Required Files
functions.php— Theme setup and configurationindex.php— Fallback template for all contentstyle.css— Theme stylesheet with header comment
Your First Theme
Step 1: Create style.css
/*
Theme Name: My Theme
Description: A custom theme
Version: 1.0.0
Author: Your Name
*/
body {
font-family: system-ui, sans-serif;
line-height: 1.6;
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
Step 2: Create functions.php
<?php
// Register menu location
Theme::registerMenuLocation('primary', 'Primary Navigation');
// Register theme settings
Theme::registerSettings([
'footer_text' => [
'type' => 'text',
'label' => 'Footer Text',
'default' => '© 2025'
]
]);
Step 3: Create index.php
<?php get_header(); ?>
<main>
<?php if ($posts): ?>
<?php foreach ($posts as $post): ?>
<article>
<h2><?= esc($post['title']) ?></h2>
<div><?= the_excerpt($post) ?></div>
<a href="<?= Post::permalink($post) ?>">Read more</a>
</article>
<?php endforeach; ?>
<?php endif; ?>
</main>
<?php get_footer(); ?>
Template Hierarchy
VoidForge looks for templates in a specific order, from most specific to least:
| Content Type | Templates (in order) |
|---|---|
| Single Post | single-{type}.php → single.php → index.php |
| Page | page-{slug}.php → page.php → index.php |
| Archive | archive-{type}.php → archive.php → index.php |
| Category | category-{slug}.php → category.php → archive.php |
| Homepage | home.php → index.php |
| Search | search.php → index.php |
| 404 | 404.php → index.php |
Theme Settings
// Register in functions.php
Theme::registerSettings([
'primary_color' => [
'type' => 'color',
'label' => 'Primary Color',
'default' => '#8b5cf6'
],
'show_sidebar' => [
'type' => 'checkbox',
'label' => 'Show Sidebar',
'default' => true
],
'posts_per_page' => [
'type' => 'number',
'label' => 'Posts Per Page',
'default' => 10
]
]);
// Use in templates
$color = Theme::setting('primary_color');
if (Theme::setting('show_sidebar')) { ... }
Custom Fields
Access post custom fields defined in the admin.
// Get single value
$value = Post::getMeta($post['id'], 'field_name');
// Get with default
$value = Post::getMeta($post['id'], 'field_name', 'default');
// Get all meta
$meta = Post::getAllMeta($post['id']);
Available Field Types
text, textarea, number, email, url, date, datetime, color, select, checkbox, radio, image, gallery, file, wysiwyg, repeater
Assets
// Theme URL
Theme::url() // /content/themes/my-theme
// In templates
<link rel="stylesheet" href="<?= Theme::url() ?>/style.css">
<script src="<?= Theme::url() ?>/js/main.js"></script>
<img src="<?= Theme::url() ?>/images/logo.png">
// Enqueue with hooks
Plugin::addAction('vf_head', function() {
Plugin::enqueueStyle('theme', Theme::url() . '/style.css');
});
Plugin::addAction('vf_footer', function() {
Plugin::enqueueScript('theme', Theme::url() . '/js/main.js');
});
Theme Hooks
Actions
| Hook | Description |
|---|---|
vf_head | Inside <head> tag |
vf_footer | Before </body> tag |
get_header | When header.php loads |
get_footer | When footer.php loads |
template_redirect | Before template loads |
Filters
| Hook | Description |
|---|---|
the_content | Filter post content |
the_title | Filter post title |
the_excerpt | Filter post excerpt |
body_class | Filter body CSS classes |
template_include | Override template selection |
SEO Integration
VoidForge includes a built-in SEO system that automatically outputs meta tags when themes use the vf_head hook.
Required Theme Setup
Your header.php must include the vf_head hook inside the <head> tag:
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= get_page_title() ?></title>
<?php Plugin::doAction('vf_head'); ?>
</head>
What Gets Output
The SEO system automatically outputs:
- Meta description — From post SEO settings or auto-generated from excerpt
- Canonical URL — Prevents duplicate content issues
- Robots meta — Index/noindex, follow/nofollow directives
- Open Graph tags — For Facebook, LinkedIn, etc.
- Twitter Cards — Optimized Twitter/X sharing
- JSON-LD Schema — Structured data for rich search results
Page Title Function
Use get_page_title() for SEO-optimized titles:
// Returns formatted title based on SEO settings
// Example: "Post Title | Site Name"
<title><?= get_page_title() ?></title>
Debug Mode
Add ?seo_debug=1 to any frontend URL to see SEO output (admin only):
// View SEO data for any page
https://yoursite.com/your-post?seo_debug=1
Custom Post Type Templates
Create specific templates for custom post types:
Single Template
Create single-{post_type}.php:
<!-- single-portfolio.php -->
<?php get_header(); ?>
<article class="portfolio-item">
<h1><?= esc($post['title']) ?></h1>
<?php if ($gallery = Post::getMeta($post['id'], 'gallery')): ?>
<div class="gallery">
<?php foreach ($gallery as $img): ?>
<img src="<?= esc($img['url']) ?>">
<?php endforeach; ?>
</div>
<?php endif; ?>
<div><?= the_content($post) ?></div>
</article>
<?php get_footer(); ?>
Archive Template
Create archive-{post_type}.php:
<!-- archive-portfolio.php -->
<?php get_header(); ?>
<h1>Portfolio</h1>
<div class="portfolio-grid">
<?php foreach ($posts as $post): ?>
<a href="<?= Post::permalink($post) ?>" class="item">
<?php if ($img = Post::featuredImage($post)): ?>
<img src="<?= esc($img) ?>">
<?php endif; ?>
<h3><?= esc($post['title']) ?></h3>
</a>
<?php endforeach; ?>
</div>
<?php get_footer(); ?>
Best Practices
Security
- Always escape output with
esc(),esc_attr(),esc_url() - Never trust user input
- Use
the_content()instead of raw content
Performance
- Minimize database queries in templates
- Use appropriate image sizes
- Lazy load images when possible
Code Quality
- Use consistent indentation and formatting
- Comment complex template logic
- Keep functions.php organized