Themes

Overview

ValPress themes are the presentation layer of the CMS, designed to be lightweight, performant, and deeply integrated with Laravel's Blade engine. Unlike traditional WordPress themes, ValPress themes follow a structured directory layout that separates logic, views, and assets, while maintaining compatibility with familiar concepts like functions.php and template tags.

Why It Matters

A well-structured theme system is critical for balancing performance with flexibility. By leveraging Laravel's native tools (Blade, Service Providers, and Routing) within a theme, developers can:

  • Improve Performance: Themes are compiled and cached using Laravel's optimized view engine.
  • Maintainability: Clear separation of concerns between src/ (logic), views/ (presentation), and res/ (assets).
  • Extensibility: Support for child themes allows for safe customization without modifying parent code.
  • Developer Experience: Use modern PHP and Blade features instead of complex procedural code.

How It Works

The ThemeManager is responsible for discovering, validating, and booting the active theme. During the ValPress boot sequence, the system:

  1. Detection: Locates the active theme in public/themes/{slug}.
  2. Child Theme Check: If an active child theme is defined, it boots the child theme alongside its parent.
  3. Namespace Registration: Registers the theme's views/ directory into Laravel's view search path, giving the theme precedence over core views.
  4. Resource Registration: Automatically maps routes/, lang/, src/Controllers, and src/Models if they exist.
  5. Logic Execution: Requires functions.php to allow the theme to register hooks and filters.
  6. Hierarchy Resolution: When a controller requests a view (e.g., view('post')), ValPress searches the theme first, then the parent theme, then the core.

Usage

Theme Structure

A standard ValPress theme resides in public/themes/{slug} and follows this layout:

my-theme/
├── config.php          # Metadata and parent theme definition
├── functions.php       # Hook registration and theme logic
├── screenshot.png      # Large preview (1200x900)
├── thumbnail.png       # Small preview (300x225)
├── views/              # Blade templates
├── res/                # Assets (CSS, JS, Images)
├── src/                # Custom PHP logic (Controllers, Models)
├── lang/               # Translation files
├── routes/             # Custom theme-specific routes
└── tests/              # PHPUnit tests (required for Marketplace publication)

Creating a New Theme

To create a theme, start with a config.php file containing the required metadata:

<?php
return [
    'name' => 'My Premium Theme',
    'version' => '1.0.0',
    'author' => 'ValPress Team',
    'short_description' => 'A clean, modern blog theme.',
    'screenshot' => 'screenshot.png',
    'thumbnail' => 'thumbnail.png',
];

Template Tags

ValPress provides helper functions (Template Tags) to retrieve data within your Blade templates:

  • valpress_get_permalink($post): Returns the absolute URL to a post.
  • valpress_get_post_excerpt($post, $length = 20): Returns a truncated version of the post content.
  • the_featured_image($post, $size = 'large'): Returns the URL of the post's featured image.
  • body_class(): Returns a space-separated string of CSS classes for the <body> tag.

Code Examples

1. The functions.php File

Use this file to register styles, scripts, and custom hooks.

<?php
// Enqueue theme assets
add_action('admin_enqueue_scripts', function () {
    ScriptManager::enqueue('my-theme-admin', asset('themes/my-theme/res/admin.js'));
});

// Register a custom footer message
add_filter('valpress_footer_text', function ($text) {
    return $text . ' | Powered by My Premium Theme';
});

// Register a theme-specific sidebar
add_action('valpress_init', function() {
    register_sidebar([
        'id' => 'primary-sidebar',
        'name' => __('Primary Sidebar'),
        'description' => __('Main sidebar for blog posts.'),
    ]);
});

2. A Basic Blade Template (views/post.blade.php)

@extends('layouts.app')

@section('content')
    <article @class(['post-item', 'featured' => $post->is_featured])>
        <header>
            <h1>{{ $post->post_title }}</h1>
            <img src="{{ the_featured_image($post, 'large') }}" alt="{{ $post->post_title }}">
        </header>

        <div class="entry-content">
            {!! apply_filters('the_content', $post->post_content) !!}
        </div>

        <footer>
            <p>Posted in {{ $post->category->name }} on {{ $post->created_at->format('M d, Y') }}</p>
        </footer>
    </article>
@endsection

3. Child Theme Configuration

To create a child theme, specify the template key in your config.php:

<?php
return [
    'name' => 'My Child Theme',
    'template' => 'parent-theme-slug', // Directory name of the parent theme
    'version' => '1.0.0',
    // ...
];

Advanced Usage

Theme-Specific Routes

You can define custom routes inside routes/web.php that only exist when the theme is active. This is useful for landing pages or custom AJAX endpoints.

// public/themes/my-theme/routes/web.php
use Illuminate\Support\Facades\Route;

Route::get('/special-offer', function() {
    return view('special-offer');
})->middleware('web');

Custom Controllers

For complex logic, place controllers in src/Controllers/ and register them via functions.php or routing. ValPress automatically handles PSR-4 autoloading for the src/ directory within your theme via the Themes\{StudlySlug} namespace (ExtensionAutoloader).

Update validation

Themes can implement ValidatesUpdate on src/Theme.php to participate in the Update Center pipeline. When verifying a core update, ValPress validates the active theme and its parent (for child themes). Theme self-updates run validation during the extension verify stage.

php artisan vp:make-plugin-validation my-theme

The scaffold command targets plugins; for themes, implement src/Theme.php following the same ValidatesUpdate contract. See Plugin Update Validation.

Marketplace Publication Requirements

Themes submitted to the ValPress Marketplace must include an automated test suite. During editorial review, editors verify that your package contains PHPUnit tests and that they pass against a ValPress installation. Submissions without tests, or with failing tests, will not be approved for publication.

Where to Place Tests

Place your theme tests under:

public/themes/{slug}/tests/

ValPress discovers these directories automatically via PSR-4 mappings in composer.json. Use namespaces that match your theme slug (for example, Themes\MyTheme\Tests\).

What to Cover

At minimum, your test suite should confirm that the theme renders correctly and integrates safely with ValPress. Prioritize coverage for:

  • Template rendering: Key views (home, single post, archive, page) return successful responses.
  • Theme logic: Custom routes, controllers, and functions.php hook registrations.
  • Child themes: Parent/child relationships and template hierarchy resolution, if applicable.
  • Assets and hooks: Enqueued scripts or styles, sidebars, and filters such as the_content or valpress_footer_text.

Verify Before You Submit

Run your theme tests from the ValPress project root before packaging your ZIP for upload:

php artisan test public/themes/my-theme/tests

You can also filter by namespace or test name:

php artisan test --filter MyTheme

All tests must pass. For test database setup, PHPUnit configuration, and additional examples, see Testing & Test Database Setup.

Example Theme Test

<?php // public/themes/my-theme/tests/HomePageTest.php

namespace Themes\MyTheme\Tests;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class HomePageTest extends TestCase
{
    use RefreshDatabase;

    public function test_home_page_loads_successfully(): void
    {
        $response = $this->get(route('home'));

        $response->assertStatus(200);
    }

    public function test_single_post_template_renders(): void
    {
        $post = \App\Models\Post::factory()->create([
            'post_title' => 'Theme Test Post',
            'post_status' => 'publish',
            'post_type' => 'post',
        ]);

        $response = $this->get(valpress_get_permalink($post));

        $response->assertStatus(200);
        $response->assertSee('Theme Test Post');
    }
}

Include the tests/ directory in your submission ZIP. Marketplace reviewers expect tests to be part of the distributed package, not maintained separately.

Best Practices

  • Ship Tests with Your Theme: Treat automated tests as a required deliverable, especially if you plan to publish on the Marketplace.
  • Use Hooking: Always use add_action and add_filter instead of modifying core files.
  • Escape Output: Use Blade's {{ $var }} for automatic escaping, and only use {!! $var !!} when you are certain the content is sanitized (e.g., filtered post content).
  • Relative Assets: Use the asset() helper or custom theme asset helpers to ensure URLs are correct across environments.
  • Localization: Always wrap strings in __() or trans() to ensure your theme is translatable.

Common Mistakes

  • Direct Database Queries: Avoid DB::table() inside views. Use models or repository classes in src/.
  • Hardcoding URLs: Never hardcode the site URL or theme paths; use valpress_get_permalink() and asset().
  • Missing Screenshots: Forgetting screenshot.png makes your theme look "broken" in the Admin Dashboard.
  • Ignoring Child Themes: Not checking for parent theme existence when creating a child theme can cause fatal errors.
  • Submitting Without Tests: Uploading a Marketplace submission with no tests/ directory, or with failing tests, will delay or block approval. Run php artisan test locally before every upload.

Summary

ValPress themes offer a modern, Laravel-powered approach to CMS design. By combining the flexibility of functions.php with the power of Blade and PSR-4 autoloading, you can build highly performant and maintainable frontend experiences. Remember to leverage the ThemeManager's automatic resource registration to keep your code organized. If you intend to distribute your theme via the Marketplace, include a passing PHPUnit test suite from the start — tests are a publication requirement, not an optional extra.