WordPressオリジナルテーマ開発③|カスタム投稿タイプで制作実績ページを作る


はじめに

前回の記事ではWordPressオリジナルテーマ開発の第2弾として、home.phpsingle.phppage.php のテンプレートファイルの作り方と functions.php を使ったテーマの設定方法を解説しました。今回は第3弾としてカスタム投稿タイプを使って制作実績(Works)などのオリジナルコンテンツを管理する方法を解説します。WordPressの「投稿」や「固定ページ」とは別に、自分だけのコンテンツタイプを作ることでポートフォリオサイトや案件サイトの管理がグッと便利になります。


1. カスタム投稿タイプとは

1.1 なぜカスタム投稿タイプが必要か

WordPressには最初から「投稿(post)」と「固定ページ(page)」という2種類のコンテンツが用意されています。しかしポートフォリオサイトでは「制作実績」・飲食店のサイトでは「メニュー」・不動産サイトでは「物件情報」のような独自のコンテンツを管理したい場面が出てきます。

WordPressデフォルトカスタム投稿タイプ(例)
投稿(post)制作実績(works)
固定ページ(page)メニュー(menu)
スタッフ紹介(staff)
物件情報(property)

💡 カスタム投稿タイプを使うと管理画面がすっきりします: 制作実績をブログ記事と同じ「投稿」として管理すると混在して見づらくなります。専用のカスタム投稿タイプを作ることで管理画面のサイドバーに独立したメニューが追加され、コンテンツの種類ごとに整理できます。

1.2 カスタムタクソノミー(カスタム分類)も合わせて作る

投稿に「カテゴリー」や「タグ」があるように、カスタム投稿タイプにも独自の分類を作れます。制作実績であれば「制作ジャンル(Webサイト・ECサイト・ランディングページなど)」という分類が便利です。

通常の投稿カスタム投稿タイプ(制作実績)
カテゴリー制作ジャンル(works_genre)
タグ使用技術(works_skill)

2. カスタム投稿タイプを登録する(functions.php)

2.1 register_post_type()で登録する

functions.php に以下のコードを追加します。

// ===== カスタム投稿タイプ:制作実績(works)=====
function my_theme_register_post_types() {

    $labels = array(
        'name'               => '制作実績',
        'singular_name'      => '制作実績',
        'add_new'            => '新規追加',
        'add_new_item'       => '制作実績を追加',
        'edit_item'          => '制作実績を編集',
        'new_item'           => '新しい制作実績',
        'view_item'          => '制作実績を表示',
        'search_items'       => '制作実績を検索',
        'not_found'          => '制作実績が見つかりません',
        'not_found_in_trash' => 'ゴミ箱に制作実績はありません',
        'menu_name'          => '制作実績',
    );

    $args = array(
        'labels'              => $labels,
        'public'              => true,        // サイト上に公開する
        'show_ui'             => true,        // 管理画面に表示する
        'show_in_menu'        => true,        // サイドバーメニューに表示する
        'show_in_rest'        => true,        // ブロックエディター(Gutenberg)対応
        'menu_position'       => 5,           // メニューの表示位置(5=投稿の下)
        'menu_icon'           => 'dashicons-portfolio', // メニューアイコン
        'supports'            => array(
            'title',           // タイトル
            'editor',          // 本文(エディター)
            'thumbnail',       // アイキャッチ画像
            'excerpt',         // 抜粋
            'custom-fields',   // カスタムフィールド
        ),
        'has_archive'         => true,        // アーカイブページを有効にする
        'rewrite'             => array(
            'slug' => 'works',              // URL上のスラッグ(例:/works/)
        ),
    );

    register_post_type( 'works', $args );
}

add_action( 'init', 'my_theme_register_post_types' );

2.2 register_post_type()の主要な引数

引数内容
publictrue でサイト上に公開する
show_uitrue で管理画面に表示する
show_in_resttrue でGutenbergエディターが使えるようになる
supports使用できる機能(タイトル・本文・アイキャッチなど)
has_archivetrue でアーカイブページを有効にする
rewriteURLのスラッグを設定する
menu_icon管理画面のメニューアイコン(Dashiconsの名前を指定)

⚠️ カスタム投稿タイプを登録したらパーマリンクを再保存してください: 管理画面の「設定」→「パーマリンク」を開いて何も変更せず「変更を保存」をクリックするだけで、新しい投稿タイプのURLが認識されます。この手順を忘れるとアーカイブや個別ページが404になります。


3. カスタムタクソノミーを登録する

3.1 制作ジャンルと使用技術を登録する

// ===== カスタムタクソノミー =====
function my_theme_register_taxonomies() {

    // 制作ジャンル(階層あり=カテゴリーと同じ動作)
    $genre_labels = array(
        'name'              => '制作ジャンル',
        'singular_name'     => '制作ジャンル',
        'search_items'      => '制作ジャンルを検索',
        'all_items'         => 'すべての制作ジャンル',
        'parent_item'       => '親の制作ジャンル',
        'edit_item'         => '制作ジャンルを編集',
        'update_item'       => '制作ジャンルを更新',
        'add_new_item'      => '制作ジャンルを追加',
        'new_item_name'     => '新しい制作ジャンル',
        'menu_name'         => '制作ジャンル',
    );

    register_taxonomy( 'works_genre', 'works', array(
        'labels'       => $genre_labels,
        'hierarchical' => true,        // trueでカテゴリー型・falseでタグ型
        'public'       => true,
        'show_in_rest' => true,        // Gutenbergエディター対応
        'rewrite'      => array( 'slug' => 'works-genre' ),
    ) );

    // 使用技術(階層なし=タグと同じ動作)
    $skill_labels = array(
        'name'          => '使用技術',
        'singular_name' => '使用技術',
        'add_new_item'  => '使用技術を追加',
        'new_item_name' => '新しい使用技術',
        'menu_name'     => '使用技術',
    );

    register_taxonomy( 'works_skill', 'works', array(
        'labels'       => $skill_labels,
        'hierarchical' => false,       // タグ型
        'public'       => true,
        'show_in_rest' => true,
        'rewrite'      => array( 'slug' => 'works-skill' ),
    ) );
}

add_action( 'init', 'my_theme_register_taxonomies' );

4. カスタムフィールドを活用する

4.1 カスタムフィールドとは

制作実績ページでは「制作期間」「クライアント名」「担当範囲」「サイトURL」などのタイトルや本文では表現しにくい情報を管理したい場合があります。カスタムフィールドを使うとこれらの追加情報を構造化して管理できます。

4.2 Advanced Custom Fields(ACF)プラグインを使う

カスタムフィールドはWordPressの標準機能でも使えますが、管理画面が使いにくいため ACF(Advanced Custom Fields) というプラグインを使うのが一般的です。

インストール手順:
1. 管理画面「プラグイン」→「新規追加」
2. 「Advanced Custom Fields」で検索
3. 「今すぐインストール」→「有効化」

ACFで制作実績に追加するフィールドの例:

フィールドラベルフィールド名タイプ
クライアント名client_nameテキスト
制作期間production_periodテキスト
担当範囲responsible_areaチェックボックス
サイトURLsite_urlURL
制作背景backgroundテキストエリア

4.3 テンプレートでカスタムフィールドの値を取得する

// ACFの値を取得する(get_field()関数)
$client_name       = get_field( 'client_name' );
$production_period = get_field( 'production_period' );
$site_url          = get_field( 'site_url' );
$responsible_area  = get_field( 'responsible_area' ); // チェックボックスは配列で返る

// 制作担当範囲の表示例(配列を展開して表示)
if ( $responsible_area && is_array( $responsible_area ) ) {
    echo '<ul class="responsible-list">';
    foreach ( $responsible_area as $area ) {
        echo '<li>' . esc_html( $area ) . '</li>';
    }
    echo '</ul>';
}

💡 esc_html() で出力時のXSS対策を忘れずに: 第◯弾のセキュリティ改善記事で解説した htmlspecialchars() と同じ役割をWordPressの esc_html() 関数が担います。カスタムフィールドの値を出力する際は必ず通しましょう。


5. カスタム投稿タイプのテンプレートを作る

5.1 archive-works.php(一覧ページ)

カスタム投稿タイプ works のアーカイブページ(/works/)に使われるテンプレートです。

<?php get_header(); ?>

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

        <h1 class="page-title">制作実績</h1>

        <!-- タクソノミーによる絞り込みタブ -->
        <div class="genre-tabs">
            <a href="<?php echo get_post_type_archive_link( 'works' ); ?>" class="genre-tab">
                すべて
            </a>
            <?php
            $genres = get_terms( array(
                'taxonomy' => 'works_genre',
                'hide_empty' => true,
            ) );

            if ( $genres && !is_wp_error( $genres ) ) :
                foreach ( $genres as $genre ) : ?>
                    <a href="<?php echo esc_url( get_term_link( $genre ) ); ?>"
                       class="genre-tab">
                        <?php echo esc_html( $genre->name ); ?>
                    </a>
                <?php endforeach;
            endif; ?>
        </div>

        <?php if ( have_posts() ) : ?>

            <div class="works-grid">

                <?php while ( have_posts() ) : the_post(); ?>

                    <article class="works-card" id="works-<?php the_ID(); ?>">

                        <a href="<?php the_permalink(); ?>" class="works-card__link">

                            <?php if ( has_post_thumbnail() ) : ?>
                                <div class="works-card__thumb">
                                    <?php the_post_thumbnail( 'my-card-thumb' ); ?>
                                </div>
                            <?php endif; ?>

                            <div class="works-card__body">
                                <h2 class="works-card__title">
                                    <?php the_title(); ?>
                                </h2>

                                <?php
                                // カスタムタクソノミーの制作ジャンルを表示
                                $genres = get_the_terms( get_the_ID(), 'works_genre' );
                                if ( $genres && !is_wp_error( $genres ) ) :
                                ?>
                                    <div class="works-card__genre">
                                        <?php foreach ( $genres as $genre ) : ?>
                                            <span class="genre-badge">
                                                <?php echo esc_html( $genre->name ); ?>
                                            </span>
                                        <?php endforeach; ?>
                                    </div>
                                <?php endif; ?>

                                <?php
                                // 使用技術タグを表示
                                $skills = get_the_terms( get_the_ID(), 'works_skill' );
                                if ( $skills && !is_wp_error( $skills ) ) :
                                ?>
                                    <div class="works-card__skills">
                                        <?php foreach ( $skills as $skill ) : ?>
                                            <span class="skill-tag">
                                                <?php echo esc_html( $skill->name ); ?>
                                            </span>
                                        <?php endforeach; ?>
                                    </div>
                                <?php endif; ?>

                            </div>

                        </a>

                    </article>

                <?php endwhile; ?>

            </div>

            <div class="pagination">
                <?php the_posts_pagination(); ?>
            </div>

        <?php else : ?>
            <p class="no-posts">制作実績はまだありません。</p>
        <?php endif; ?>

    </div>
</main>

<?php get_footer(); ?>

5.2 single-works.php(詳細ページ)

個別の制作実績を表示するテンプレートです。

<?php get_header(); ?>

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

        <?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>

            <article class="works-single" id="works-<?php the_ID(); ?>">

                <header class="works-single__header">
                    <h1 class="works-single__title">
                        <?php the_title(); ?>
                    </h1>

                    <!-- 制作ジャンルと使用技術 -->
                    <div class="works-single__meta">
                        <?php
                        $genres = get_the_terms( get_the_ID(), 'works_genre' );
                        if ( $genres && !is_wp_error( $genres ) ) :
                            foreach ( $genres as $genre ) :
                                echo '<span class="genre-badge">' . esc_html( $genre->name ) . '</span>';
                            endforeach;
                        endif;

                        $skills = get_the_terms( get_the_ID(), 'works_skill' );
                        if ( $skills && !is_wp_error( $skills ) ) :
                            foreach ( $skills as $skill ) :
                                echo '<span class="skill-tag">' . esc_html( $skill->name ) . '</span>';
                            endforeach;
                        endif;
                        ?>
                    </div>
                </header>

                <?php if ( has_post_thumbnail() ) : ?>
                    <div class="works-single__thumb">
                        <?php the_post_thumbnail( 'large' ); ?>
                    </div>
                <?php endif; ?>

                <!-- ACFのカスタムフィールドを表示 -->
                <dl class="works-single__info">

                    <?php if ( get_field( 'client_name' ) ) : ?>
                        <dt>クライアント</dt>
                        <dd><?php echo esc_html( get_field( 'client_name' ) ); ?></dd>
                    <?php endif; ?>

                    <?php if ( get_field( 'production_period' ) ) : ?>
                        <dt>制作期間</dt>
                        <dd><?php echo esc_html( get_field( 'production_period' ) ); ?></dd>
                    <?php endif; ?>

                    <?php if ( get_field( 'responsible_area' ) ) : ?>
                        <dt>担当範囲</dt>
                        <dd>
                            <?php
                            $areas = get_field( 'responsible_area' );
                            if ( is_array( $areas ) ) {
                                echo esc_html( implode( '・', $areas ) );
                            }
                            ?>
                        </dd>
                    <?php endif; ?>

                    <?php if ( get_field( 'site_url' ) ) : ?>
                        <dt>サイトURL</dt>
                        <dd>
                            <a href="<?php echo esc_url( get_field( 'site_url' ) ); ?>"
                               target="_blank"
                               rel="noopener noreferrer">
                                <?php echo esc_url( get_field( 'site_url' ) ); ?>
                            </a>
                        </dd>
                    <?php endif; ?>

                </dl>

                <!-- 本文 -->
                <div class="works-single__content">
                    <?php the_content(); ?>
                </div>

                <!-- 前後の制作実績へのリンク -->
                <nav class="works-navigation">
                    <?php
                    previous_post_link(
                        '<div class="nav-previous">%link</div>',
                        '&laquo; %title',
                        true, '', 'works'
                    );
                    next_post_link(
                        '<div class="nav-next">%link</div>',
                        '%title &raquo;',
                        true, '', 'works'
                    );
                    ?>
                </nav>

            </article>

        <?php endwhile; endif; ?>

    </div>
</main>

<?php get_footer(); ?>

6. トップページに制作実績を表示する

6.1 WP_Queryでカスタム投稿タイプを取得する

トップページの index.php にWorksセクションとして最新の制作実績を数件表示したい場合は WP_Query を使います。

<?php
// 制作実績を最新6件取得する
$works_query = new WP_Query( array(
    'post_type'      => 'works',    // カスタム投稿タイプのスラッグ
    'posts_per_page' => 6,          // 取得件数
    'orderby'        => 'date',     // 並び順
    'order'          => 'DESC',     // 新しい順
) );
?>

<section class="works-section">
    <h2>Works</h2>

    <?php if ( $works_query->have_posts() ) : ?>

        <div class="works-grid">
            <?php while ( $works_query->have_posts() ) : $works_query->the_post(); ?>

                <article class="works-card">
                    <a href="<?php the_permalink(); ?>">
                        <?php if ( has_post_thumbnail() ) : ?>
                            <?php the_post_thumbnail( 'my-card-thumb' ); ?>
                        <?php endif; ?>
                        <h3><?php the_title(); ?></h3>
                    </a>
                </article>

            <?php endwhile; ?>
        </div>

        <a href="<?php echo get_post_type_archive_link( 'works' ); ?>" class="btn-more">
            制作実績をもっと見る
        </a>

    <?php endif; ?>

    <?php wp_reset_postdata(); ?>
</section>

⚠️ WP_Query を使ったあとは必ず wp_reset_postdata() を呼びましょう: WP_Query でメインのループ以外のクエリを実行すると、WordPressのグローバルな投稿情報が上書きされます。ループが終わった後に wp_reset_postdata() を呼ぶことで元の状態に戻します。これを忘れると the_title() などが意図しない投稿のタイトルを出力してしまうことがあります。


7. 現時点のテーマフォルダ構成

my-portfolio-theme/
├── index.php
├── home.php
├── single.php
├── page.php
├── archive-works.php    ← 今回追加(制作実績一覧)
├── single-works.php     ← 今回追加(制作実績詳細)
├── functions.php        ← カスタム投稿タイプ・タクソノミーを追加
├── style.css
├── header.php
├── footer.php
├── sidebar.php
├── css/
├── js/
└── images/

8. 動作確認チェックリスト

functions.phpregister_post_type() を追加して管理画面に「制作実績」メニューが表示されるか確認したか

パーマリンクの再保存(設定→パーマリンク→変更を保存)を行ったか

register_taxonomy() を追加して制作ジャンル・使用技術の設定欄が表示されるか確認したか

ACFプラグインをインストールしてカスタムフィールドを設定したか

管理画面で制作実績を1件入力してアーカイブページ(/works/)が表示されるか確認したか

制作実績の個別ページが正しく表示されるか確認したか

archive-works.phpsingle-works.php が正しく読み込まれているか確認したか

WP_Query の後に wp_reset_postdata() を書いているか確認したか

出力時に esc_html()esc_url() でエスケープしているか確認したか


まとめ

今回はWordPressオリジナルテーマ開発の第3弾として、カスタム投稿タイプを使って制作実績ページを作る方法を解説しました。ポイントをまとめると:

  • register_post_type() でカスタム投稿タイプを登録すると管理画面に専用メニューが追加される
  • register_taxonomy() でカスタムタクソノミー(分類)を登録してコンテンツを整理できる
  • カスタム投稿タイプ登録後はパーマリンクの再保存が必須
  • ACFプラグインを使うとクライアント名・制作期間・サイトURLなどの追加情報を管理しやすい
  • アーカイブは archive-works.php・個別ページは single-works.php というファイル名で自動的に対応するテンプレートになる
  • トップページに最新の制作実績を表示するには WP_Query を使い、ループ後に wp_reset_postdata() を忘れずに呼ぶ
  • 出力時は esc_html()esc_url() でエスケープしてXSSを防ぐ

次の記事では、作成したオリジナルテーマにSearch Console・GA4のタグを設置する方法と、テーマ全体の最終確認・本番環境へのアップロード手順を解説します。お楽しみに!

コメント

タイトルとURLをコピーしました