Building a Custom Theme for PolyCMS — Scaffold Guide
Overview
PolyCMS themes control the entire frontend appearance and layout of your blog. The theme system uses a template hierarchy inspired by WordPress, where specific templates override generic ones.
This guide provides a complete scaffold to build a custom theme from scratch.
Prerequisites: PHP knowledge, HTML/CSS, and familiarity with the PolyCMS hooks system.
Theme Directory Structure
All themes live in modules/polycms/themes/. Each theme has this structure:
modules/polycms/themes/
└── my-theme/
├── style.css # Theme manifest (required)
├── index.php # Fallback template (required)
├── header.php # Site header
├── footer.php # Site footer
├── single.php # Single post template
├── page.php # Static page template
├── category.php # Category archive template
├── tag.php # Tag archive template
├── archive.php # Generic archive template
├── search.php # Search results template
├── 404.php # Not found page
├── functions.php # Theme functions (optional)
├── assets/
│ ├── css/
│ │ └── theme.css
│ ├── js/
│ │ └── theme.js
│ └── images/
└── language/
└── english/
└── my_theme_lang.php
Template Hierarchy
When PolyCMS renders a page, it looks for templates in this order:
Single Post: single.php → index.php
Static Page: page.php → index.php
Category: category.php → archive.php → index.php
Tag: tag.php → archive.php → index.php
Search: search.php → index.php
Homepage: homepage.php → index.php
404: 404.php → index.php
The first matching template is used. index.php is always the final fallback.
Step 1: Create style.css (Theme Manifest)
The style.css file at the theme root serves as the manifest. PolyCMS reads the comment header to identify the theme.
style.css
/*
Theme Name: My Custom Theme
Theme URI: https://your-website.com/my-theme
Description: A clean, modern blog theme for PolyCMS.
Version: 1.0.0
Author: Your Name
Author URI: https://your-website.com
Requires PolyCMS: 1.0.0
License: Commercial
*/
/* Theme styles can go here or in assets/css/theme.css */
Step 2: Create header.php
The header template is included at the top of every page.
header.php
<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>
<!DOCTYPE html>
<html lang="<?php echo polycms_get_locale(); ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<?php
// SEO meta tags (auto-generated by PolyCMS)
if (isset($seo_data)) {
echo polycms_render_meta_tags($seo_data);
}
if (isset($og_tags)) {
echo $og_tags;
}
if (isset($twitter_tags)) {
echo $twitter_tags;
}
?>
<!-- Theme CSS -->
<link rel="stylesheet" href="<?php echo polycms_theme_url(); ?>/assets/css/theme.css">
<?php
// Allow plugins to inject into <head>
polycms_do_action('polycms_head');
?>
</head>
<body class="polycms-frontend <?php echo isset($body_class) ? $body_class : ''; ?>">
<?php
// Render admin bar for logged-in staff
polycms_render_admin_bar();
?>
<!-- Navigation -->
<nav class="site-nav">
<div class="container">
<a href="<?php echo polycms_blog_url(); ?>" class="site-logo">
<?php echo polycms_get_option('blog_title', 'My Blog'); ?>
</a>
<div class="nav-menu">
<?php echo polycms_render_menu('primary'); ?>
</div>
</div>
</nav>
Step 3: Create footer.php
footer.php
<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>
<!-- Footer -->
<footer class="site-footer">
<div class="container">
<p>© <?php echo date('Y'); ?> <?php echo polycms_get_option('blog_title', 'My Blog'); ?>. All rights reserved.</p>
</div>
</footer>
<?php
// Allow plugins to inject before </body>
polycms_do_action('polycms_footer');
?>
<!-- Theme JS -->
<script src="<?php echo polycms_theme_url(); ?>/assets/js/theme.js"></script>
</body>
</html>
Step 4: Create index.php (Fallback Template)
index.php
<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>
<?php include __DIR__ . '/header.php'; ?>
<main class="site-main">
<div class="container">
<h1><?php echo $title ?? 'Blog'; ?></h1>
<?php if (!empty($posts)): ?>
<div class="post-grid">
<?php foreach ($posts as $post): ?>
<article class="post-card">
<?php if ($post->featured_image): ?>
<img src="<?php echo $post->featured_image; ?>"
alt="<?php echo htmlspecialchars($post->title); ?>"
class="post-thumbnail">
<?php endif; ?>
<div class="post-card-body">
<h2>
<a href="<?php echo polycms_post_url($post); ?>">
<?php echo htmlspecialchars($post->title); ?>
</a>
</h2>
<div class="post-meta">
<time datetime="<?php echo $post->published_at; ?>">
<?php echo date('M d, Y', strtotime($post->published_at)); ?>
</time>
<span class="reading-time"><?php echo $post->reading_time; ?> min read</span>
</div>
<p><?php echo $post->excerpt; ?></p>
</div>
</article>
<?php endforeach; ?>
</div>
<?php if (isset($pagination)): ?>
<div class="pagination">
<?php echo $pagination; ?>
</div>
<?php endif; ?>
<?php else: ?>
<p>No posts found.</p>
<?php endif; ?>
</div>
</main>
<?php include __DIR__ . '/footer.php'; ?>
Step 5: Create single.php (Single Post Template)
single.php
<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>
<?php include __DIR__ . '/header.php'; ?>
<main class="site-main">
<article class="single-post">
<div class="container">
<!-- Post Header -->
<header class="post-header">
<?php if (!empty($post->categories)): ?>
<div class="post-categories">
<?php foreach ($post->categories as $cat): ?>
<a href="<?php echo polycms_category_url($cat); ?>" class="category-badge">
<?php echo htmlspecialchars($cat->name); ?>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
<h1><?php echo htmlspecialchars($post->title); ?></h1>
<div class="post-meta">
<time datetime="<?php echo $post->published_at; ?>">
<?php echo date('F d, Y', strtotime($post->published_at)); ?>
</time>
<span><?php echo $post->reading_time; ?> min read</span>
<span><?php echo $post->views; ?> views</span>
</div>
</header>
<!-- Featured Image -->
<?php if ($post->featured_image): ?>
<div class="featured-image">
<img src="<?php echo $post->featured_image; ?>"
alt="<?php echo htmlspecialchars($post->title); ?>">
</div>
<?php endif; ?>
<!-- Post Content -->
<div class="post-content">
<?php
// Apply content filters (plugins can modify this)
echo polycms_apply_filters('polycms_post_content', $post->content);
?>
</div>
<!-- Tags -->
<?php if (!empty($post->tags)): ?>
<div class="post-tags">
<?php foreach ($post->tags as $tag): ?>
<a href="<?php echo polycms_tag_url($tag); ?>" class="tag">
#<?php echo htmlspecialchars($tag->name); ?>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
<!-- Comments -->
<?php if ($post->allow_comments): ?>
<section class="comments-section">
<?php echo polycms_render_comments($post->id); ?>
</section>
<?php endif; ?>
<!-- Related Posts -->
<?php if (!empty($related_posts)): ?>
<section class="related-posts">
<h3>Related Posts</h3>
<div class="post-grid">
<?php foreach ($related_posts as $rp): ?>
<a href="<?php echo polycms_post_url($rp); ?>" class="related-card">
<strong><?php echo htmlspecialchars($rp->title); ?></strong>
</a>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
</div>
</article>
</main>
<?php include __DIR__ . '/footer.php'; ?>
Step 6: Create category.php
category.php
<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>
<?php include __DIR__ . '/header.php'; ?>
<main class="site-main">
<div class="container">
<header class="archive-header">
<h1>Category: <?php echo htmlspecialchars($category->name); ?></h1>
<?php if ($category->description): ?>
<p class="archive-description"><?php echo $category->description; ?></p>
<?php endif; ?>
</header>
<?php if (!empty($posts)): ?>
<div class="post-grid">
<?php foreach ($posts as $post): ?>
<article class="post-card">
<h2><a href="<?php echo polycms_post_url($post); ?>"><?php echo htmlspecialchars($post->title); ?></a></h2>
<p><?php echo $post->excerpt; ?></p>
</article>
<?php endforeach; ?>
</div>
<?php if (isset($pagination)) echo $pagination; ?>
<?php else: ?>
<p>No posts found in this category.</p>
<?php endif; ?>
</div>
</main>
<?php include __DIR__ . '/footer.php'; ?>
Theme Options (Optional)
Register custom settings that admins can configure without editing code.
functions.php
<?php
defined('BASEPATH') or exit('No direct script access allowed');
// Register theme options
polycms_add_action('polycms_theme_options', function() {
return [
[
'key' => 'primary_color',
'label' => 'Primary Color',
'type' => 'color',
'default' => '#82b440'
],
[
'key' => 'posts_per_page',
'label' => 'Posts Per Page',
'type' => 'number',
'default' => 10
],
[
'key' => 'show_sidebar',
'label' => 'Show Sidebar',
'type' => 'select',
'options' => ['yes' => 'Yes', 'no' => 'No'],
'default' => 'yes'
]
];
});
Available Template Helper Functions
| Function | Description |
|---|---|
polycms_blog_url() | Base URL of the blog |
polycms_post_url($post) | URL to a specific post |
polycms_category_url($cat) | URL to a category archive |
polycms_tag_url($tag) | URL to a tag archive |
polycms_theme_url() | URL to the active theme directory |
polycms_render_menu($location) | Render a navigation menu |
polycms_render_admin_bar() | Render the frontend admin bar |
polycms_render_comments($post_id) | Render the comment section |
polycms_render_meta_tags($seo) | Render SEO meta tags |
polycms_render_widgets($area) | Render widgets for a sidebar area |
polycms_get_option($key, $default) | Get a blog setting |
polycms_apply_filters($hook, $value) | Apply content filters |
polycms_do_action($hook) | Trigger an action hook |
For the complete list, see Template Tags & Helper Functions.
Installation
- Create the
my-theme/folder inmodules/polycms/themes/. - Add all template files as described above.
- Go to Blog → Themes in Perfex CRM.
- Find "My Custom Theme" and click Activate.
Best Practices
- Always include
header.phpandfooter.phpin every template for consistent layout. - Use
polycms_do_action('polycms_head')andpolycms_do_action('polycms_footer')so plugins can inject assets. - Escape all user-generated content with
htmlspecialchars()to prevent XSS. - Apply content filters using
polycms_apply_filters('polycms_post_content', $content)so plugins can modify output. - Call
polycms_render_admin_bar()in the header so staff members get frontend editing shortcuts. - Use responsive CSS — test your theme on mobile, tablet, and desktop.
Need Help Building Your Theme?
If you need assistance creating a custom theme, want help with template hierarchy, styling, or have any design-related questions — don't hesitate to contact us. We provide completely free support to all PolyCMS customers.
Simply leave a comment on the PolyCMS page on CodeCanyon describing what you'd like to achieve, and our team will guide you through the process with code examples, layout advice, or troubleshooting assistance. We're here to help you build exactly the theme you need.
Related Documentation
- Hooks & Filters Reference — All available hooks for theme developers.
- Building a Custom Plugin — Create plugins that work with your theme.
- Theme Management — How admins install and manage themes.
- Widgets & Sidebars — Add widget areas to your theme.