Phase 2 — Module 2.2: Creating the StreamVault Theme Structure from Scratch

What We Are Building

We are creating a complete classic WordPress theme folder from scratch. By the end of this module you will have a working theme that you can activate in WordPress.


Step 1 — Create the Theme Folder

Navigate to your WordPress installation:

Local Sites → streamvault → app → public → wp-content → themes

Inside themes/ folder, create a new folder:

themes/
├── astra/              ← existing
└── streamvault/        ← create this folder

Open this entire public folder in VS Code.


Step 2 — Create All Required Files

Inside streamvault/ folder, create these files one by one:

streamvault/
├── style.css
├── index.php
├── functions.php
├── header.php
├── footer.php
├── sidebar.php
├── front-page.php
├── page.php
├── single.php
├── archive.php
├── search.php
├── 404.php
└── screenshot.png      ← we skip this for now

Open the following folder in Visual Studio Code:

D:\LocalSites\streamvault\app\public\wp-content\themes

Run the following commands in the Visual Studio Code terminal. Copy the entire code below, then paste it into the VS Code terminal all at once and press Enter.


    mkdir streamvault
    cd streamvault

    mkdir template-parts, assets, assets\css, assets\js

    New-Item style.css -ItemType File
    New-Item index.php -ItemType File
    New-Item functions.php -ItemType File
    New-Item header.php -ItemType File
    New-Item footer.php -ItemType File
    New-Item sidebar.php -ItemType File
    New-Item front-page.php -ItemType File
    New-Item page.php -ItemType File
    New-Item single.php -ItemType File
    New-Item archive.php -ItemType File
    New-Item search.php -ItemType File
    New-Item 404.php -ItemType File
    New-Item template-parts\content-post.php -ItemType File
    New-Item template-parts\content-none.php -ItemType File
    New-Item assets\js\main.js -ItemType File

If the streamvault folder already exists

Do not run:

mkdir streamvault
cd streamvault

Instead, navigate into it:

cd streamvault

Then run the remaining commands to create the folders and files inside it.


Step 3 — style.css

This is the most important file. Without the theme header comment, WordPress will not recognize this as a valid theme.

Create style.css:


    /*
    Theme Name: StreamVault
    Theme URI: https://streamvault.local
    Author: Gagan
    Author URI: https://streamvault.local
    Description: A custom Netflix-style streaming platform theme built from scratch.
    Version: 1.0.0
    Requires at least: 6.0
    Requires PHP: 8.0
    License: GPL v2 or later
    License URI: https://www.gnu.org/licenses/gpl-2.0.html
    Text Domain: streamvault
    Tags: custom-theme, streaming, movies, entertainment
    */

    *,
    *::before,
    *::after {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
    }

    :root {
        --color-bg-primary: #141414;
        --color-bg-secondary: #1f1f1f;
        --color-bg-card: #2a2a2a;
        --color-accent: #e50914;
        --color-accent-hover: #b20710;
        --color-text-primary: #ffffff;
        --color-text-secondary: #b3b3b3;
        --color-text-muted: #757575;
        --color-border: #333333;
        --font-primary: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        --container-width: 1280px;
        --container-padding: 0 40px;
        --border-radius: 4px;
        --transition: all 0.3s ease;
    }

    html {
        font-size: 16px;
        scroll-behavior: smooth;
    }

    body {
        font-family: var(--font-primary);
        background-color: var(--color-bg-primary);
        color: var(--color-text-primary);
        line-height: 1.6;
        min-height: 100vh;
    }

    a {
        color: var(--color-text-primary);
        text-decoration: none;
        transition: var(--transition);
    }

    a:hover {
        color: var(--color-accent);
    }

    img {
        max-width: 100%;
        height: auto;
        display: block;
    }

    ul {
        list-style: none;
    }

    .container {
        max-width: var(--container-width);
        margin: 0 auto;
        padding: var(--container-padding);
    }

    .btn {
        display: inline-flex;
        align-items: center;
        gap: 8px;
        padding: 10px 24px;
        border: none;
        border-radius: var(--border-radius);
        font-size: 1rem;
        font-weight: 600;
        cursor: pointer;
        transition: var(--transition);
        text-decoration: none;
    }

    .btn-primary {
        background-color: var(--color-accent);
        color: var(--color-text-primary);
    }

    .btn-primary:hover {
        background-color: var(--color-accent-hover);
        color: var(--color-text-primary);
    }

    .btn-secondary {
        background-color: rgba(109, 109, 110, 0.7);
        color: var(--color-text-primary);
    }

    .btn-secondary:hover {
        background-color: rgba(109, 109, 110, 0.4);
        color: var(--color-text-primary);
    }

    .section-title {
        font-size: 1.4rem;
        font-weight: 700;
        color: var(--color-text-primary);
        margin-bottom: 20px;
        text-transform: uppercase;
        letter-spacing: 1px;
    }

    .site-main {
        min-height: 70vh;
        padding: 40px 0;
    }


Step 4 — functions.php

This is the brain of the theme. Create functions.php:


    <?php

    defined('ABSPATH') || exit;

    define('STREAMVAULT_VERSION', '1.0.0');
    define('STREAMVAULT_DIR', get_template_directory());
    define('STREAMVAULT_URI', get_template_directory_uri());

    function streamvault_theme_setup() {
        load_theme_textdomain('streamvault', STREAMVAULT_DIR . '/languages');

        add_theme_support('title-tag');
        add_theme_support('post-thumbnails');
        add_theme_support('html5', [
            'search-form',
            'comment-form',
            'comment-list',
            'gallery',
            'caption',
            'style',
            'script',
        ]);
        add_theme_support('custom-logo', [
            'height'      => 60,
            'width'       => 200,
            'flex-height' => true,
            'flex-width'  => true,
        ]);
        add_theme_support('customize-selective-refresh-widgets');

        add_image_size('sv-poster', 300, 450, true);
        add_image_size('sv-banner', 1280, 720, true);
        add_image_size('sv-card', 400, 225, true);
        add_image_size('sv-thumbnail', 150, 150, true);

        register_nav_menus([
            'primary'  => __('Primary Menu', 'streamvault'),
            'footer'   => __('Footer Menu', 'streamvault'),
            'mobile'   => __('Mobile Menu', 'streamvault'),
        ]);
    }
    add_action('after_setup_theme', 'streamvault_theme_setup');

    function streamvault_enqueue_assets() {
        wp_enqueue_style(
            'streamvault-style',
            get_stylesheet_uri(),
            [],
            STREAMVAULT_VERSION
        );

        wp_enqueue_script(
            'streamvault-main',
            STREAMVAULT_URI . '/assets/js/main.js',
            [],
            STREAMVAULT_VERSION,
            true
        );

        wp_localize_script('streamvault-main', 'streamvault_data', [
            'ajax_url' => admin_url('admin-ajax.php'),
            'nonce'    => wp_create_nonce('streamvault_nonce'),
            'site_url' => get_site_url(),
        ]);
    }
    add_action('wp_enqueue_scripts', 'streamvault_enqueue_assets');

    function streamvault_widgets_init() {
        register_sidebar([
            'name'          => __('Main Sidebar', 'streamvault'),
            'id'            => 'sidebar-main',
            'before_widget' => '<div id="%1$s" class="widget %2$s">',
            'after_widget'  => '</div>',
            'before_title'  => '<h3 class="widget-title">',
            'after_title'   => '</h3>',
        ]);

        register_sidebar([
            'name'          => __('Footer Column 1', 'streamvault'),
            'id'            => 'footer-col-1',
            'before_widget' => '<div id="%1$s" class="footer-widget %2$s">',
            'after_widget'  => '</div>',
            'before_title'  => '<h4 class="footer-widget-title">',
            'after_title'   => '</h4>',
        ]);

        register_sidebar([
            'name'          => __('Footer Column 2', 'streamvault'),
            'id'            => 'footer-col-2',
            'before_widget' => '<div id="%1$s" class="footer-widget %2$s">',
            'after_widget'  => '</div>',
            'before_title'  => '<h4 class="footer-widget-title">',
            'after_title'   => '</h4>',
        ]);

        register_sidebar([
            'name'          => __('Footer Column 3', 'streamvault'),
            'id'            => 'footer-col-3',
            'before_widget' => '<div id="%1$s" class="footer-widget %2$s">',
            'after_widget'  => '</div>',
            'before_title'  => '<h4 class="footer-widget-title">',
            'after_title'   => '</h4>',
        ]);
    }
    add_action('widgets_init', 'streamvault_widgets_init');

    function streamvault_excerpt_length($length) {
        return 30;
    }
    add_filter('excerpt_length', 'streamvault_excerpt_length');

    function streamvault_excerpt_more($more) {
        return '...';
    }
    add_filter('excerpt_more', 'streamvault_excerpt_more');


Step 5 — header.php

Create header.php:


    <!DOCTYPE html>
    <html <?php language_attributes(); ?>>
    <head>
        <meta charset="<?php bloginfo('charset'); ?>">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <?php wp_head(); ?>
    </head>
    <body <?php body_class(); ?>>
    <?php wp_body_open(); ?>

    <header class="site-header">
        <div class="container">
            <div class="header-inner">

                <div class="site-branding">
                    <?php if (has_custom_logo()): ?>
                        <?php the_custom_logo(); ?>
                    <?php else: ?>
                        <a href="<?php echo esc_url(home_url('/')); ?>" class="site-logo-text">
                            <?php bloginfo('name'); ?>
                        </a>
                    <?php endif; ?>
                </div>

                <nav class="primary-navigation" aria-label="<?php esc_attr_e('Primary Navigation', 'streamvault'); ?>">
                    <?php
                    wp_nav_menu([
                        'theme_location' => 'primary',
                        'menu_class'     => 'primary-menu',
                        'container'      => false,
                        'fallback_cb'    => false,
                    ]);
                    ?>
                </nav>

                <div class="header-actions">
                    <a href="<?php echo esc_url(home_url('/search')); ?>" class="header-search-toggle" aria-label="Search">
                        &#128269;
                    </a>
                    <?php if (is_user_logged_in()): ?>
                        <a href="<?php echo esc_url(wp_logout_url(home_url())); ?>" class="btn btn-secondary">
                            <?php esc_html_e('Logout', 'streamvault'); ?>
                        </a>
                    <?php else: ?>
                        <a href="<?php echo esc_url(wp_login_url()); ?>" class="btn btn-primary">
                            <?php esc_html_e('Sign In', 'streamvault'); ?>
                        </a>
                    <?php endif; ?>
                </div>

            </div>
        </div>
    </header>


Step 6 — footer.php

Create footer.php:


    <footer class="site-footer">
        <div class="container">

            <div class="footer-top">
                <div class="footer-branding">
                    <a href="<?php echo esc_url(home_url('/')); ?>" class="footer-logo">
                        <?php bloginfo('name'); ?>
                    </a>
                    <p class="footer-tagline"><?php bloginfo('description'); ?></p>
                </div>

                <div class="footer-widgets">
                    <?php if (is_active_sidebar('footer-col-1')): ?>
                        <div class="footer-widget-area">
                            <?php dynamic_sidebar('footer-col-1'); ?>
                        </div>
                    <?php endif; ?>

                    <?php if (is_active_sidebar('footer-col-2')): ?>
                        <div class="footer-widget-area">
                            <?php dynamic_sidebar('footer-col-2'); ?>
                        </div>
                    <?php endif; ?>

                    <?php if (is_active_sidebar('footer-col-3')): ?>
                        <div class="footer-widget-area">
                            <?php dynamic_sidebar('footer-col-3'); ?>
                        </div>
                    <?php endif; ?>
                </div>
            </div>

            <div class="footer-bottom">
                <p class="footer-copyright">
                    &copy; <?php echo esc_html(date('Y')); ?>
                    <?php bloginfo('name'); ?>.
                    <?php esc_html_e('All Rights Reserved.', 'streamvault'); ?>
                </p>

                <?php if (has_nav_menu('footer')): ?>
                    <nav class="footer-navigation" aria-label="<?php esc_attr_e('Footer Navigation', 'streamvault'); ?>">
                        <?php
                        wp_nav_menu([
                            'theme_location' => 'footer',
                            'menu_class'     => 'footer-menu',
                            'container'      => false,
                            'depth'          => 1,
                            'fallback_cb'    => false,
                        ]);
                        ?>
                    </nav>
                <?php endif; ?>
            </div>

        </div>
    </footer>

    <?php wp_footer(); ?>
    </body>
    </html>


Step 7 — index.php

This is the ultimate fallback template. Create index.php:


    <?php get_header(); ?>

    <main class="site-main">
        <div class="container">
            <?php if (have_posts()): ?>
                <div class="posts-grid">
                    <?php while (have_posts()): the_post(); ?>
                        <?php get_template_part('template-parts/content', get_post_type()); ?>
                    <?php endwhile; ?>
                </div>
                <?php the_posts_navigation(); ?>
            <?php else: ?>
                <?php get_template_part('template-parts/content', 'none'); ?>
            <?php endif; ?>
        </div>
    </main>

    <?php get_footer(); ?>


Step 8 — front-page.php

This loads on the homepage. Create front-page.php:


    <?php get_header(); ?>

    <main class="site-main home-page">
        <section class="hero-section">
            <div class="container">
                <div class="hero-content">
                    <h1 class="hero-title">
                        <?php esc_html_e('Unlimited Movies & Series', 'streamvault'); ?>
                    </h1>
                    <p class="hero-subtitle">
                        <?php esc_html_e('Watch anywhere. Stream anytime. Cancel anytime.', 'streamvault'); ?>
                    </p>
                    <div class="hero-actions">
                        <a href="<?php echo esc_url(home_url('/movies')); ?>" class="btn btn-primary">
                            <?php esc_html_e('Browse Movies', 'streamvault'); ?>
                        </a>
                        <a href="<?php echo esc_url(home_url('/series')); ?>" class="btn btn-secondary">
                            <?php esc_html_e('Browse Series', 'streamvault'); ?>
                        </a>
                    </div>
                </div>
            </div>
        </section>
    </main>

    <?php get_footer(); ?>


Step 9 — page.php

Create page.php:


    <?php get_header(); ?>

    <main class="site-main">
        <div class="container">
            <?php while (have_posts()): the_post(); ?>
                <article id="page-<?php the_ID(); ?>" <?php post_class('single-page'); ?>>
                    <header class="page-header">
                        <h1 class="page-title"><?php the_title(); ?></h1>
                    </header>
                    <div class="page-content">
                        <?php the_content(); ?>
                    </div>
                </article>
            <?php endwhile; ?>
        </div>
    </main>

    <?php get_footer(); ?>


Step 10 — single.php

Create single.php:


    <?php get_header(); ?>

    <main class="site-main">
        <div class="container">
            <div class="single-post-layout">
                <div class="post-content-area">
                    <?php while (have_posts()): the_post(); ?>
                        <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
                            <header class="entry-header">
                                <h1 class="entry-title"><?php the_title(); ?></h1>
                                <div class="entry-meta">
                                    <span class="post-date"><?php echo get_the_date(); ?></span>
                                    <span class="post-author">
                                        <?php esc_html_e('by', 'streamvault'); ?>
                                        <?php the_author(); ?>
                                    </span>
                                    <span class="post-categories"><?php the_category(', '); ?></span>
                                </div>
                            </header>

                            <?php if (has_post_thumbnail()): ?>
                                <div class="entry-thumbnail">
                                    <?php the_post_thumbnail('sv-banner'); ?>
                                </div>
                            <?php endif; ?>

                            <div class="entry-content">
                                <?php the_content(); ?>
                            </div>

                            <footer class="entry-footer">
                                <div class="post-tags">
                                    <?php the_tags('<span class="tags-label">Tags: </span>', ', ', ''); ?>
                                </div>
                            </footer>
                        </article>

                        <nav class="post-navigation">
                            <?php
                            the_post_navigation([
                                'prev_text' => '&larr; %title',
                                'next_text' => '%title &rarr;',
                            ]);
                            ?>
                        </nav>
                    <?php endwhile; ?>
                </div>

                <aside class="post-sidebar">
                    <?php get_sidebar(); ?>
                </aside>
            </div>
        </div>
    </main>

    <?php get_footer(); ?>


Step 11 — archive.php

Create archive.php:


    <?php get_header(); ?>

    <main class="site-main">
        <div class="container">
            <header class="archive-header">
                <h1 class="archive-title">
                    <?php the_archive_title(); ?>
                </h1>
                <?php the_archive_description('<div class="archive-description">', '</div>'); ?>
            </header>

            <?php if (have_posts()): ?>
                <div class="posts-grid">
                    <?php while (have_posts()): the_post(); ?>
                        <?php get_template_part('template-parts/content', get_post_type()); ?>
                    <?php endwhile; ?>
                </div>
                <?php the_posts_navigation(); ?>
            <?php else: ?>
                <?php get_template_part('template-parts/content', 'none'); ?>
            <?php endif; ?>
        </div>
    </main>

    <?php get_footer(); ?>


Step 12 — search.php

Create search.php:


    <?php get_header(); ?>

    <main class="site-main">
        <div class="container">
            <header class="search-header">
                <h1 class="search-title">
                    <?php
                    printf(
                        esc_html__('Search Results for: %s', 'streamvault'),
                        '<span>' . get_search_query() . '</span>'
                    );
                    ?>
                </h1>
                <?php get_search_form(); ?>
            </header>

            <?php if (have_posts()): ?>
                <div class="posts-grid">
                    <?php while (have_posts()): the_post(); ?>
                        <?php get_template_part('template-parts/content', get_post_type()); ?>
                    <?php endwhile; ?>
                </div>
                <?php the_posts_navigation(); ?>
            <?php else: ?>
                <div class="no-results">
                    <p><?php esc_html_e('No results found. Try a different search term.', 'streamvault'); ?></p>
                    <?php get_search_form(); ?>
                </div>
            <?php endif; ?>
        </div>
    </main>

    <?php get_footer(); ?>


Step 13 — 404.php

Create 404.php:


    <?php get_header(); ?>

    <main class="site-main">
        <div class="container">
            <div class="error-404">
                <h1 class="error-code">404</h1>
                <h2 class="error-title">
                    <?php esc_html_e('Page Not Found', 'streamvault'); ?>
                </h2>
                <p class="error-message">
                    <?php esc_html_e('The page you are looking for does not exist or has been moved.', 'streamvault'); ?>
                </p>
                <a href="<?php echo esc_url(home_url('/')); ?>" class="btn btn-primary">
                    <?php esc_html_e('Go Back Home', 'streamvault'); ?>
                </a>
            </div>
        </div>
    </main>

    <?php get_footer(); ?>


Step 14 — sidebar.php

Create sidebar.php:


    <?php if (is_active_sidebar('sidebar-main')): ?>
        <aside class="widget-area sidebar-main" aria-label="<?php esc_attr_e('Sidebar', 'streamvault'); ?>">
            <?php dynamic_sidebar('sidebar-main'); ?>
        </aside>
    <?php endif; ?>


Step 15 — Template Parts Folder

Create this folder structure:

streamvault/
└── template-parts/
    ├── content-post.php
    └── content-none.php

Create template-parts/content-post.php:


    <article id="post-<?php the_ID(); ?>" <?php post_class('post-card'); ?>>
        <?php if (has_post_thumbnail()): ?>
            <div class="post-card-thumbnail">
                <a href="<?php the_permalink(); ?>">
                    <?php the_post_thumbnail('sv-card'); ?>
                </a>
            </div>
        <?php endif; ?>

        <div class="post-card-body">
            <div class="post-card-meta">
                <span class="post-category"><?php the_category(', '); ?></span>
                <span class="post-date"><?php echo get_the_date(); ?></span>
            </div>

            <h2 class="post-card-title">
                <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
            </h2>

            <div class="post-card-excerpt">
                <?php the_excerpt(); ?>
            </div>

            <a href="<?php the_permalink(); ?>" class="btn btn-primary post-card-link">
                <?php esc_html_e('Read More', 'streamvault'); ?>
            </a>
        </div>
    </article>

Create template-parts/content-none.php:


    <div class="no-content">
        <h2><?php esc_html_e('Nothing Found', 'streamvault'); ?></h2>
        <p><?php esc_html_e('It seems we cannot find what you are looking for.', 'streamvault'); ?></p>
        <?php get_search_form(); ?>
    </div>


Step 16 — Assets Folder

Create this folder structure:

streamvault/
└── assets/
    ├── css/
    └── js/
        └── main.js

Create assets/js/main.js:


    (function() {
        'use strict';

        document.addEventListener('DOMContentLoaded', function() {
            console.log('StreamVault theme loaded');
        });
    })();


Step 17 — Activate the Theme

Go to Appearance → Themes

You will now see StreamVault in the themes list. Click Activate.

Visit https://streamvault.local — you will see your custom theme running.


Final Folder Structure

streamvault/
├── style.css
├── index.php
├── functions.php
├── header.php
├── footer.php
├── sidebar.php
├── front-page.php
├── page.php
├── single.php
├── archive.php
├── search.php
├── 404.php
├── template-parts/
│   ├── content-post.php
│   └── content-none.php
└── assets/
    ├── css/
    └── js/
        └── main.js

Summary

  • A valid WordPress theme needs minimum two files — style.css with theme header comment and index.php.
  • functions.php sets up theme supports, image sizes, menus, widgets, and enqueues assets.
  • header.php and footer.php are included via get_header() and get_footer().
  • Template parts in template-parts/ folder are reusable components included via get_template_part().
  • CSS variables in style.css define the Netflix-dark color scheme for StreamVault.
  • After creating all files — go to Appearance → Themes → Activate StreamVault.


No comments:

Post a Comment

PHP & Laravel — Zero to Hero Episode 21: Authentication — User Registration, Login, and Route Protection

What Are We Doing in This Post? Our blog works perfectly but it has a serious problem. Anyone who visits the site can create, edit, and del...