Theme Development: Getting Started

Last updated on May 21, 2026 04:15

PolyCMS uses a highly flexible, Blade-based theme engine. While the Admin Panel is a Vue 3 SPA, the public-facing frontend relies on traditional, ultra-fast Server-Side Rendered (SSR) Blade templates by default (though it fully supports Headless API-driven frontends as well).

This guide covers the architecture of a PolyCMS Theme and how to build one from scratch.

1. Directory Structure

All themes reside in the themes/ directory.

Let's look at the structure of a standard theme, such as the core flexiwhite blueprint:

themes/
└── flexiwhite/
    ├── theme.json           # The Theme Manifest
    ├── screenshot.png       # 800x600 preview image for the Admin panel
    ├── functions.php        # Optional: PHP logic, Hook registration
    ├── resources/
    │   └── views/           # Blade templates
    │       ├── layouts/
    │       │   └── app.blade.php  # The master wrapper
    │       ├── partials/
    │       └── templates/   # Specialized block templates (Landing pages, etc.)
    └── public/              # Compiled CSS/JS and Images

2. The Theme Manifest (theme.json)

To register a theme with the system, you must define a theme.json file. The ThemeManager uses this to identify the theme and expose its available templates to the Admin UI.

{
  "name": "FlexiWhite",
  "slug": "flexiwhite",
  "version": "1.0.0",
  "author": "Polyx Team",
  "description": "A clean, whitespace-heavy corporate theme.",
  "role": "main",
  "templates": {
    "posts": {
      "full-width": "Full Width No Sidebar",
      "gallery": "Gallery Focus Layout"
    },
    "pages": {
      "landing": "Marketing Landing Page",
      "contact": "Contact Form Layout"
    }
  }
}

Main Themes vs. Sub Themes

Notice the "role": "main" key. PolyCMS implements a Multi-Theme Architecture:

  • Main Themes (role: main): Provide the global layout (app.blade.php) and site-wide CSS. Only one can be active at a time.

  • Sub Themes (role: sub): Provide specialized templates (like a unique checkout flow or a Black Friday landing page). You can activate multiple Sub Themes simultaneously. Their templates will override the Main Theme when assigned to specific posts or pages.

3. The Master Layout (app.blade.php)

Every Main Theme must have a master layout file located at resources/views/layouts/app.blade.php. This file defines the HTML skeleton.

Critical Requirements: To ensure modules and plugins can inject their necessary CSS and JavaScript (like the Cookie Consent banner or Analytics scripts), your layout must include the @stack directives.

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    
    <!-- Render SEO Meta Tags dynamically -->
    <title>@yield('meta_title', config('app.name'))</title>
    
    <!-- Include Theme CSS -->
    <link rel="stylesheet" href="{{ theme_asset('css/style.css') }}">
    
    <!-- CRITICAL: Allow modules to inject CSS -->
    @stack('theme-styles')
</head>
<body>
    
    @include('theme::partials.header')

    <main>
        <!-- Render the specific page/post content here -->
        @yield('content')
    </main>

    @include('theme::partials.footer')

    <!-- CRITICAL: Allow modules to inject JS -->
    @stack('theme-scripts')
</body>
</html>

4. The theme_asset() Helper

When linking to CSS, JS, or images stored in your theme's public/ directory, always use the theme_asset() helper function rather than Laravel's standard asset().

<!-- CORRECT -->
<img src="{{ theme_asset('images/logo.svg') }}" alt="Logo">

<!-- INCORRECT -->
<img src="{{ asset('themes/flexiwhite/public/images/logo.svg') }}" alt="Logo">

theme_asset() automatically resolves the correct path based on the currently active theme and handles caching.

Next Steps

With your manifest and layout complete, your theme can now be activated from the Admin Panel under Appearance > Themes.

To dive deeper, explore how to Register Widget Areas or use functions.php to tap into the Hook System.