WordPress自作テーマの作り方|最小構成から主要ページまで

WordPressの自作テーマは、クラシックテーマならstyle.cssとindex.phpから認識させ、テンプレートタグ、ループ、テンプレート階層、functions.phpを段階的に追加して作れます。初めてのオリジナルテーマでは、完成済みHTMLを細かく分割する前に、どのURLへどのテンプレートが選ばれ、どのデータを安全に出力するかを理解することが重要です。

この記事では、クラシックテーマを主軸に、最小構成からトップ、投稿一覧、個別投稿、固定ページ、アーカイブ、検索、404までを一つのサンプルで作ります。ブロックテーマは同じファイル構成へ混ぜず、後半で違いを整理します。本番サイトを直接編集せず、ローカル環境でコードとデータを検証してから配布してください。

WordPressテーマを自作する前に知っておくこと

HTML/CSS/PHPの基礎があり、独自のページ構造やデザインを継続保守するなら、クラシックテーマの自作は学びやすい選択です。サイトエディターで運用者が共通部分まで編集したいならブロックテーマ、既存テーマの一部だけ変えたいなら子テーマのほうが目的に合う場合があります。

方式を比較します。

方式主なテンプレート向いている目的注意点
クラシックテーマPHPファイルPHPのテンプレートタグで構造を制御テンプレート階層と安全なPHP出力が必要
ブロックテーマtemplates内のHTMLサイトエディターで共通部分も編集ブロックマークアップとtheme.jsonを理解する
子テーマ親テーマとの差分既存テーマのデザイン・テンプレート変更親テーマの仕様と更新影響を受ける
独自プラグインPHPやブロックテーマ変更後も残す機能表示だけの責務と分ける

予約、フォーム、カスタム投稿タイプ、ショートコードなど、テーマを替えても残すべき機能はプラグイン側へ置きます。WordPress.orgのテーマ審査文書も、フォームやカスタム投稿タイプなど非デザイン機能をテーマへ含めないよう案内しています。

開発環境の選択と本番データの扱いは「WordPressのローカル環境」、追加項目の設計は「カスタムフィールドとACF」で確認できます。この記事のサンプルでは標準の投稿、固定ページ、メニュー、アイキャッチ画像までを扱い、外部プラグインへ依存しません。

オリジナルテーマの作り方を通して、毎回次の5点を確認します。

  1. リクエストに対して選ばれるテンプレートは何か
  2. メインクエリからどのデータを取得するか
  3. 出力先のHTML文脈に合うエスケープをしているか
  4. CSSとJavaScriptをWordPressの方法で読み込むか
  5. ログアウト状態、異なるデータ量、画面幅で表示できるか

テーマ開発の準備

本番のテーマファイルを管理画面から直接編集せず、ローカルまたは隔離した開発環境を用意します。ファイルの変更履歴をGitで管理し、小さな単位で動作確認すると、白画面になった箇所を戻しやすくなります。

準備するものは次のとおりです。

  • 現在サポートされるWordPress、PHP、MySQLまたはMariaDBの開発環境
  • UTF-8とPHPの構文を扱えるコードエディター
  • Gitリポジトリと除外設定
  • WordPress Theme Unit Testなどの検証用データ
  • 複数の投稿、固定ページ、カテゴリー、タグ、画像、コメント
  • ブラウザー開発ツールとPHPエラーログ
  • Theme Check、PHP_CodeSnifferなどの検査手段

wp-config.phpでは、ローカルまたはステージングに限定してデバッグログを有効にします。エラーを公開画面へ出さず、ログへ保存する例です。

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );

WordPress公式文書はデバッグ機能をローカルやステージングで使うよう案内しています。本番へ配布する前に、環境側の設定へ戻し、debug.logを公開場所へ残さないでください。

検証データは、長い日本語タイトル、本文なし、画像なし、複数カテゴリー、入れ子メニュー、長いURL、コメント閉鎖などを用意します。

WordPressテーマの最小構成

WordPressがクラシックテーマとして認識する最小構成は、テーマ情報を持つstyle.cssと、最終的なフォールバックであるindex.phpです。WordPress.org公式ディレクトリへ申請する場合はcomments.phpやscreenshotなど、審査用の追加必須ファイルがありますが、ローカルで認識させる最小構成とは分けて考えます。

作成するフォルダーをstarry-baseとすると、最初は次の形です。

wp-content/
└── themes/
    └── starry-base/
        ├── index.php
        └── style.css

style.cssの先頭へテーマヘッダーを書きます。Theme Nameは認識に必要です。配布時は版、ライセンス、テキストドメインも記載します。

/*
Theme Name: Starry Base
Description: A learning theme for a small content site.
Version: 1.0.0
Author: Example Author
Text Domain: starry-base
License: GNU General Public License v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
*/

body {
  margin: 0;
  color: #1f2937;
  background: #ffffff;
  font-family: system-ui, sans-serif;
  line-height: 1.7;
}

index.phpには、最小のループを置きます。まずPHPとして動くことを確認し、共通部分や詳細テンプレートは後から追加します。

<?php
if ( have_posts() ) :
    while ( have_posts() ) :
        the_post();
        ?>
        <article <?php post_class(); ?>>
            <h1><?php the_title(); ?></h1>
            <?php the_content(); ?>
        </article>
        <?php
    endwhile;
else :
    ?>
    <p><?php esc_html_e( 'Content not found.', 'starry-base' ); ?></p>
    <?php
endif;

これは学習用の最小表示です。head、body、言語属性、WordPressのフックがないため、このまま完成テーマにはしません。

既存テーマの小変更が目的なら、ゼロからオリジナルテーマを作る前に「子テーマの作り方」を確認します。自作が必要か市販・公式テーマで足りるかは「テーマの選び方」で、更新責任と制作コストも含めて判断してください。

オリジナルテーマを作成する手順

最小ファイルを作ったら、WordPressへ認識させ、有効化してログを確認します。白画面になったら別の標準テーマへ戻せるよう、管理画面とファイル管理の両方の経路を確保します。

  1. wp-content/themes内へstarry-baseフォルダーを作る
  2. テーマヘッダーを持つstyle.cssを保存する
  3. 最小ループを持つindex.phpを保存する
  4. 管理画面の「外観」からテーマの表示名を確認する
  5. テーマを有効化する
  6. トップ、投稿、固定ページを開く
  7. PHPエラーログを確認する
  8. Gitへ最初の動作版を記録する

テーマが一覧へ出ない場合は、style.cssの場所とTheme Nameを確認します。style.cssをassets/cssへ置いただけではテーマヘッダーとして認識されません。テーマルートに必要です。

有効化で白画面になる場合は、PHP構文エラー、開始タグ、未定義関数を確認します。ターミナルでPHPの構文検査を使えるなら、保存するたびに確認します。

php -l index.php

まず全URLがindex.phpへフォールバックして内容を表示できることを確認し、その後に専用ファイルを増やします。

テンプレートをヘッダー・本文・フッターへ分割する

共通の文書構造をheader.phpとfooter.phpへ分け、テンプレート側からget_headerとget_footerで読み込みます。WordPressが必要なCSS、JavaScript、管理バーなどを挿入できるよう、wp_headとwp_footerを必ず置きます。

header.phpにはdoctype、language_attributes、文字コード、viewport、wp_headを置きます。body開始直後にはbody_classとwp_body_openを使い、サイト名やメニューの後でmainを開始します。footer.phpでmainを閉じ、フッターの末尾にwp_footerを置いてbodyとhtmlを閉じます。

index.phpはget_headerから始め、ループを処理した後にget_footerで終えます。wp_headやwp_footerを省くと、テーマやプラグインが必要なCSS、JavaScript、管理バーを挿入できません。

再利用部分が増えたら、get_template_partを使い、template-parts/content.phpなどへ分けます。ファイル名だけを細かく増やすのではなく、投稿カード、検索結果、空状態など、独立して意味を持つ部品にします。

WordPressループで投稿を表示する

WordPressループは、現在のリクエストに対して準備されたメインクエリから投稿を一件ずつ取り出す仕組みです。have_postsで有無を確認し、the_postで現在の投稿データを進め、テンプレートタグで表示します。

一覧用の投稿カードは、タイトルをリンク付きで出し、抜粋と日付を表示します。

<?php if ( have_posts() ) : ?>
    <div class="post-list">
        <?php while ( have_posts() ) : ?>
            <?php the_post(); ?>
            <article <?php post_class( 'post-card' ); ?>>
                <h2 class="post-card__title">
                    <a href="<?php the_permalink(); ?>">
                        <?php the_title(); ?>
                    </a>
                </h2>
                <time datetime="<?php echo esc_attr( get_the_date( DATE_W3C ) ); ?>">
                    <?php echo esc_html( get_the_date() ); ?>
                </time>
                <?php the_excerpt(); ?>
            </article>
        <?php endwhile; ?>
    </div>

    <?php the_posts_pagination(); ?>
<?php else : ?>
    <p><?php esc_html_e( 'No posts were found.', 'starry-base' ); ?></p>
<?php endif; ?>

the_title、the_excerpt、the_contentなどは表示用テンプレートタグで、関数側が意図した出力処理を行います。get_the_titleなど戻り値を取得する関数をechoする場合は、出力文脈に合うエスケープを追加します。

メインループ内でquery_postsを使って条件を上書きしません。表示件数や並びを変える場合は、管理画面の設定、pre_get_posts、または独立したWP_Queryを目的に応じて使います。独自WP_Queryのループが終わったらwp_reset_postdataを呼び、グローバルな投稿データを戻します。

一覧を無制限に取得したり、テンプレート内で同じ重いクエリを何度も実行したりすると表示速度へ影響します。ページネーションと取得条件を設計し、計測してからキャッシュを検討します。

主要テンプレートファイルを作る

index.phpだけでもすべてのURLを表示できますが、トップ、一覧、個別投稿、固定ページ、検索、404では必要な構造が違います。テンプレート階層に沿って、具体的なファイルからindex.phpへ段階的にフォールバックさせます。

まず次の構成へ広げます。

starry-base/
├── 404.php
├── archive.php
├── footer.php
├── front-page.php
├── functions.php
├── header.php
├── home.php
├── index.php
├── page.php
├── search.php
├── single.php
├── style.css
└── template-parts/
    ├── content-card.php
    └── content-none.php

すべてを一度に作らず、追加するたびに対象URLとフォールバック先を確認します。共通の投稿カードはget_template_partで再利用し、各テンプレートにはページ固有の見出しとナビゲーションを置きます。

front-page.phpとhome.phpを作る

front-page.phpは、管理画面の「ホームページの表示」が最新の投稿でも固定ページでも、ファイルがあればサイトのフロントページに優先されます。home.phpは投稿一覧用です。名前から逆に連想しやすいため注意してください。

front-page.phpでは、固定ページをトップに指定した構成でもthe_contentを出せるようにします。

<?php get_header(); ?>

<?php if ( have_posts() ) : ?>
    <?php while ( have_posts() ) : ?>
        <?php the_post(); ?>
        <article <?php post_class( 'front-page' ); ?>>
            <?php the_content(); ?>
        </article>
    <?php endwhile; ?>
<?php endif; ?>

<?php get_footer(); ?>

home.phpは投稿一覧の見出しとカードを表示します。

<?php get_header(); ?>

<header class="archive-header">
    <h1><?php esc_html_e( 'Latest posts', 'starry-base' ); ?></h1>
</header>

<?php if ( have_posts() ) : ?>
    <?php while ( have_posts() ) : ?>
        <?php the_post(); ?>
        <?php get_template_part( 'template-parts/content', 'card' ); ?>
    <?php endwhile; ?>
    <?php the_posts_pagination(); ?>
<?php else : ?>
    <?php get_template_part( 'template-parts/content', 'none' ); ?>
<?php endif; ?>

<?php get_footer(); ?>

トップで最新記事も表示したい場合、固定ページのメインループと独自クエリを混同しません。別のWP_Queryを使い、終了後に投稿データを戻します。

single.phpとpage.phpを作る

single.phpは個別投稿、page.phpは固定ページの基本テンプレートです。投稿では日付、カテゴリー、前後ナビゲーション、コメントが必要でも、固定ページでは不要なことがあります。

single.phpの骨組みです。

<?php get_header(); ?>

<?php while ( have_posts() ) : ?>
    <?php the_post(); ?>
    <article <?php post_class(); ?>>
        <header class="entry-header">
            <h1><?php the_title(); ?></h1>
            <time datetime="<?php echo esc_attr( get_the_date( DATE_W3C ) ); ?>">
                <?php echo esc_html( get_the_date() ); ?>
            </time>
        </header>

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

    <?php the_post_navigation(); ?>

    <?php if ( comments_open() || get_comments_number() ) : ?>
        <?php comments_template(); ?>
    <?php endif; ?>
<?php endwhile; ?>

<?php get_footer(); ?>

page.phpは同じループを使いながら、投稿メタや前後記事を外します。固定ページ内で設定したブロック、ショートコード、ページ分割が動くよう、the_contentとwp_link_pagesを置きます。

<?php get_header(); ?>

<?php while ( have_posts() ) : ?>
    <?php the_post(); ?>
    <article <?php post_class(); ?>>
        <h1><?php the_title(); ?></h1>
        <div class="entry-content">
            <?php the_content(); ?>
            <?php wp_link_pages(); ?>
        </div>
    </article>
<?php endwhile; ?>

<?php get_footer(); ?>

投稿タイトルや本文はWordPressの表示用関数を使い、独自にDBへSQLを発行して取得しません。プラグインがthe_contentへ追加する処理もあるため、本文をget_post_fieldだけで代替すると機能が抜ける場合があります。

archive.php・search.php・404.phpを作る

archive.phpはカテゴリー、タグ、日付、投稿者などの一覧へ使われます。the_archive_titleとthe_archive_descriptionを使うと、現在のアーカイブに応じた見出しを出せます。

archive.phpでは見出しの後に共通の投稿カードをループし、ページネーションと0件時の部品を置きます。search.phpも同じカードを再利用できますが、get_search_queryで取得した検索語はesc_htmlで出力し、get_search_formで再検索できるようにします。

404.phpは存在しないURLで使われます。HTTPステータスはWordPressが処理するため、テンプレートで別ページへ自動転送せず、見つからないこと、トップへの導線、get_search_formによる検索を示します。

日本語サイトとして配布する場合、表示文字列を日本語へ固定するのではなく翻訳関数とテキストドメインを使い、翻訳ファイルまたは翻訳プラットフォームで対応できる形にします。

テンプレート階層と優先順位

テンプレート階層は、現在のURLとクエリに対して、WordPressが具体的なファイルから汎用ファイルへ順番に探す規則です。最初に見つかったファイルが使われ、最後はindex.phpへフォールバックします。

クラシックテーマでよく使う優先順は次のとおりです。

表示対象主な優先順
フロントページfront-page.php → home.phpまたはpage階層 → index.php
投稿一覧home.php → index.php
個別投稿single-{post-type}-{slug}.php → single-{post-type}.php → single.php → singular.php → index.php
固定ページカスタムテンプレート → page-{slug}.php → page-{id}.php → page.php → singular.php → index.php
カテゴリーcategory-{slug}.php → category-{id}.php → category.php → archive.php → index.php
検索結果search.php → index.php
404404.php → index.php

固定ページ「会社概要」だけを変えるためにpage-company.phpを作ることはできますが、ページごとに専用ファイルを増やすと変更箇所が分散します。共通レイアウトならpage.php、運用者が選ぶ種類ならカスタムページテンプレート、内容差ならブロックやカスタムフィールドを検討します。

テンプレートが選ばれない場合は、ファイル名、投稿タイプ、スラッグ、表示設定、キャッシュを確認します。front-page.phpが存在すると、最新投稿をトップに設定していても最優先になる点は特に混乱しやすい部分です。

テンプレート内でURLを見て独自に大量分岐するより、階層と条件分岐タグを使います。どのファイルが選ばれたか分からないときは、開発環境でテンプレート名をログへ出すか、デバッグ用プラグインを使います。

functions.phpでテーマ機能を追加する

functions.phpは、テーマの初期設定、アセット読込、メニュー、ウィジェットなどをフックへ登録する場所です。テンプレートのように直接表示するファイルではなく、有効テーマの読み込み時に実行されます。構文エラーがあるとサイト全体が止まるため、変更を小さく保ちます。

基本の設定例です。

<?php
function starry_base_setup() {
    load_theme_textdomain( 'starry-base', get_template_directory() . '/languages' );

    add_theme_support( 'title-tag' );
    add_theme_support( 'post-thumbnails' );
    add_theme_support( 'responsive-embeds' );
    add_theme_support( 'html5', array( 'search-form', 'comment-form', 'comment-list', 'gallery', 'caption', 'style', 'script' ) );

    register_nav_menus(
        array(
            'primary' => esc_html__( 'Primary menu', 'starry-base' ),
            'footer'  => esc_html__( 'Footer menu', 'starry-base' ),
        )
    );
}
add_action( 'after_setup_theme', 'starry_base_setup' );

function starry_base_enqueue_assets() {
    $theme = wp_get_theme();

    wp_enqueue_style(
        'starry-base-style',
        get_stylesheet_uri(),
        array(),
        $theme->get( 'Version' )
    );

    wp_enqueue_script(
        'starry-base-navigation',
        get_theme_file_uri( 'assets/js/navigation.js' ),
        array(),
        $theme->get( 'Version' ),
        array( 'strategy' => 'defer', 'in_footer' => true )
    );
}
add_action( 'wp_enqueue_scripts', 'starry_base_enqueue_assets' );

CSSをheader.phpへlink要素で直書きせず、wp_enqueue_styleを使います。JavaScriptもwp_enqueue_scriptで依存関係、版、読込位置を管理します。WordPressやプラグインが同じライブラリを持つ場合、二重読込を避けられます。

関数名、フックハンドル、グローバル変数には、テーマ固有のstarry_base_接頭辞を付けます。PHPの名前空間を採用する場合も、配布条件とWordPressのサポートPHP版を確認します。

functions.phpへ処理を詰め込みすぎず、inc/setup.php、inc/assets.phpなど責務別に分け、require_onceで読み込みます。ただし、テーマ切替後も必要な業務機能は分割先を変えるだけでなく、プラグインへ移します。フック、子テーマとの読込順、よくある停止原因は「functions.phpの基礎」で確認できます。

テンプレートタグと安全な出力

テンプレートタグは、サイト名、URL、タイトル、本文、日付、メニューなどをWordPressの規則に沿って取得・表示する関数です。the系の表示関数とget系の取得関数の違いを理解し、戻り値を出すときは文脈ごとにエスケープします。

出力先主な関数
HTML本文の文字esc_html見出し、ラベル、検索語
HTML属性esc_attrclass以外の動的属性、datetime
URLesc_urlhref、src
許可するHTMLを含む文章wp_kses_post管理済みの限定HTML
textareaesc_textarea編集フォームの入力値

WordPress公式は、値を出力する直前にエスケープする「escape late」を推奨しています。早い段階で無害化したつもりでも、その後に値が変わると安全性を確認しにくくなるためです。

URLとリンク文字列を別々に処理する例です。

<?php
$profile_url  = get_author_posts_url( get_the_author_meta( 'ID' ) );
$display_name = get_the_author();
?>

<a href="<?php echo esc_url( $profile_url ); ?>">
    <?php echo esc_html( $display_name ); ?>
</a>

翻訳対象の固定文字列は、__、esc_html__、esc_html_eなどを文脈に応じて使います。変数を含む文章では、翻訳文字列全体を分割せず、printfのプレースホルダーを使います。

入力を保存する機能を作る場合は、入力時の検証とサニタイズ、権限確認、nonce、出力時のエスケープを別々に考えます。nonceは操作意図の確認に役立ちますが、利用者の権限を証明しないためcurrent_user_canなどの権限確認も必要です。

セキュリティ・コーディング規約・表示速度

安全なテーマは、危険な入力を避けるだけでなく、保守者が意図を読み取れ、WordPressの更新へ追随できる構造を持ちます。WordPress Coding Standardsとテーマ審査要件を参考に、コードレビューと自動検査を行います。

セキュリティでは次を確認します。

  • 動的出力を文脈に合う関数でエスケープする
  • フォーム処理でnonceと権限を確認する
  • SQLが必要ならwpdbのprepareを使い、可能ならWordPress APIを優先する
  • APIキーやパスワードをテーマへ埋め込まない
  • 外部URL、リモート取得、ファイルアップロードを検証する
  • ユーザー入力をそのままHTMLやJavaScriptへ連結しない
  • テーマ切替後も必要な機能をテーマへ閉じ込めない

表示速度では、最初に計測して負荷源を特定します。CSSやJavaScriptを無条件に全ページへ読み込まず、必要なページやブロックだけへ限定します。大きな画像をそのまま出さず、WordPressの画像サイズとレスポンシブ画像機能を利用します。

データ取得では、投稿全件を無制限に取得しない、同じクエリをループ内で繰り返さない、外部APIをページ表示のたびに同期呼出ししないことが基本です。キャッシュする場合は、更新時の失効条件と期限を設計します。

アクセシビリティも品質条件です。見出し順、ランドマーク、キーボード操作、フォーカス表示、ラベル、色のコントラスト、動きの軽減を確認します。見た目のためだけに見出しレベルを飛ばしたり、クリック要素をdivだけで作ったりしません。

ブロックテーマを自作する場合の違い

ブロックテーマは、PHPテンプレートを中心にするクラシックテーマとは別の構造です。WordPressは、テーマルートのstyle.cssとtemplates/index.htmlによってブロックテーマとして認識します。index.htmlはブロックマークアップで記述し、テンプレートはtemplates、共通部分はpartsへ置きます。

最小に近い構成は次のとおりです。

starry-block/
├── parts/
│   ├── footer.html
│   └── header.html
├── templates/
│   └── index.html
├── style.css
└── theme.json

templates/index.htmlの例です。

<!-- wp:template-part {"slug":"header","tagName":"header"} /-->

<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
    <!-- wp:query {"query":{"perPage":10,"postType":"post"}} -->
    <div class="wp-block-query">
        <!-- wp:post-template -->
            <!-- wp:post-title {"isLink":true} /-->
            <!-- wp:post-excerpt /-->
        <!-- /wp:post-template -->
    </div>
    <!-- /wp:query -->
</main>
<!-- /wp:group -->

<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->

theme.jsonは、編集機能、色、文字、余白、要素・ブロックのスタイル、カスタムテンプレート、テンプレートパーツを設定する標準ファイルです。WordPress公式文書は、ブロックテーマで技術上の必須ではない場合があっても、実用上ほぼ必要な基盤と説明しています。schema、version、settings、stylesを上位プロパティとして使い、テーマのコンテンツ幅、色パレット、文字設定を定義できます。

theme.jsonのスキーマ版と利用可能なプロパティはWordPress版で変わります。公式のLiving Referenceと対象WordPress版を確認し、古い記事の例をそのまま貼り付けません。

ブロックテーマでは、サイトエディターで利用者が保存したテンプレートがDBへ入り、テーマファイルより優先される場合があります。ファイルを更新したのに表示が変わらないときは、利用者カスタマイズの存在を確認します。配布する変更とサイト固有の編集をどう同期するかが、クラシックテーマとは異なる運用課題です。

完成したテーマをテスト・配布する

完成判定はトップページの見た目だけで行いません。テンプレート階層、データの境界値、操作、コード品質、配布条件を確認します。

表示テストでは次を開きます。

  • 最新投稿を表示するフロントページ
  • 固定ページを指定したフロントページと投稿一覧
  • 投稿、固定ページ、カテゴリー、タグ、検索、404
  • コメントあり・なし、画像あり・なし、本文なし
  • 長いタイトル、長いURL、入れ子リスト、表、ギャラリー
  • ログイン中・ログアウト中、管理バーあり・なし
  • スマートフォン、タブレット、デスクトップ幅

機能テストでは、メニュー、検索、コメント、ページネーション、画像、埋め込み、キーボード操作、印刷を確認します。JavaScriptを無効にしても主要な移動と本文閲覧ができるかも見ます。

自動検査では、PHP構文、WordPress Coding Standards、Theme Check、HTML、アクセシビリティ、ライセンスを確認します。警告を機械的に無視せず、意図的な例外なら理由を記録します。

配布用ZIPには、開発用のnode_modules、Git履歴、DBダンプ、OSの隠しファイル、秘密情報、不要なソースマップを含めません。逆に、テーマが必要とするビルド済みCSS・JavaScript、翻訳、ライセンス、READMEは含めます。

WordPress.org公式ディレクトリへ申請する場合は、必要ファイル、GPL互換ライセンス、テーマ審査要件、Theme Unit Testを確認します。独自顧客向け配布でも、更新方法、対応WordPress・PHP、変更履歴、戻し方を文書化します。

本番反映前にステージングで有効化し、現在テーマから切り替えたときのメニュー位置、ウィジェット、アイキャッチ、カスタマイザー設定を確認します。テーマ切替はDBの投稿を消しませんが、テーマ固有設定や表示機能が使われなくなることがあります。

自作テーマでよくあるトラブル

トラブルは、発生した直前の変更とPHPログから調べます。複数ファイルを同時に書き換えず、動作していたコミットへ戻せる状態を保ちます。

症状主な原因確認すること
有効化すると白画面PHP構文、未定義関数、読込順PHPログ、構文検査、直前差分
テーマ一覧へ出ないstyle.cssの場所・ヘッダーテーマルートとTheme Name
CSSが反映されないenqueue漏れ、キャッシュ、パスwp_head、wp_enqueue_style、最終URL
JavaScriptが動かない依存関係、読込位置、構文コンソール、wp_footer、handle
投稿だけ404パーマリンク、投稿タイプパーマリンク再保存と登録処理
専用テンプレートにならないファイル名、階層、設定選択されたテンプレートとスラッグ
メニューが表示されない登録・割当て不足theme_locationと管理画面の割当て
画像が大きく崩れるサイズ未登録、CSS不足生成サイズ、srcset、幅・高さ

CSSが読み込まれないときは、get_stylesheet_uriが返すURLを開き、404や古いキャッシュでないか確認します。親テーマと子テーマではget_template_directory_uriとget_stylesheet_directory_uriの指す場所が違うため、使い分けます。

テンプレートが選ばれないときは、思い込みで条件分岐を追加せず、WordPress公式の階層表と実際のクエリを照合します。固定ページのスラッグ変更、フロントページ設定、カスタム投稿タイプ名がファイル名へ影響します。

関数衝突は、短い一般名の独自関数をグローバルへ定義したときに起こりやすくなります。テーマ固有接頭辞または名前空間を使い、同じファイルを二重requireしていないか確認します。

404を直すために.htaccessを何度も上書きする前に、投稿タイプ登録がプラグイン側で実行されているか、rewrite slugが重複していないかを確認します。テーマへ投稿タイプを入れると、テーマ切替でURLと管理画面が消えるため避けます。

よくある質問

Q
PHP初心者でもWordPressテーマを自作できますか?
A

HTML/CSSに加え、変数、条件分岐、ループ、関数、配列、include、エラー確認を理解していれば、最小テーマから段階的に学べます。本番で直接試さず、ローカル環境とGitで一変更ずつ確認してください。

Q
クラシックテーマとブロックテーマはどちらを選びますか?
A

PHPテンプレートで構造を厳密に管理し、既存の開発資産を使うならクラシックテーマが向きます。運用者がサイトエディターでヘッダーやテンプレートまで編集するならブロックテーマが候補です。必要な編集権限、対応WordPress版、引継ぎ方法で決めます。

Q
自作テーマに子テーマは必要ですか?
A

自分たちが自作テーマ本体を管理し、更新もそのソースへ反映するなら、必ずしも子テーマは必要ありません。外部提供の親テーマを更新しながら差分を保ちたい場合や、配布元とサイト固有改修を分離したい場合に子テーマを使います。

まとめ

WordPressの自作テーマは、クラシックテーマならstyle.cssとindex.phpの最小構成から始め、header.php、footer.php、ループ、主要テンプレート、テンプレート階層、functions.phpの順に広げると役割を理解しやすくなります。各段階で、選ばれたテンプレート、取得データ、安全な出力、アセット読込、表示結果を確認してください。

機能をテーマへ詰め込まず、切替後も必要な処理はプラグインへ分けます。完成後は検証データ、複数URL、画面幅、アクセシビリティ、コード規約、ライセンス、更新・復旧手順まで確認してから、ステージングと本番へ反映します。

この記事を書いた人

Hara Daizo

Hara Daizo

Web制作会社、Web担当者を経て独立。17年以上の実務経験で培った制作スキルとSEOノウハウを活かし、現在はSTARRY代表としてWordPressサイト制作・集客サポートを提供。大手クラウドソーシングのWebデザイナーランキング上位受賞多数。