WordPressのカスタムフィールドにURLを入力し、テンプレートでリンクとして表示したい場面は多くあります。参考サイトのリンク、外部サービスへの誘導、ダウンロードURL、SNSプロフィールなど、用途はさまざまです。
この記事では、get_post_meta()によるURL取得の基本から、セキュリティ対策(サニタイズ・バリデーション・エスケープの3層防御)、カスタムメタボックスの作成、ACF対応、ショートコード化、WP_Queryによるフィルタリングまで、実務で必要な知識を体系的に解説します。
この記事で学べること
- get_post_meta() でカスタムフィールドのURLを取得する基本
- esc_url() / sanitize_url() / FILTER_VALIDATE_URL によるセキュリティ3層防御
- カスタムメタボックスでURL入力欄を作成する方法
- ACF(URL・リンクフィールド)での実装方法
- ショートコード化・WP_Queryフィルタリング・REST API連携
- 7つの実務パターン(参考リンク・SNS・ダウンロード等)
基本:get_post_meta() でカスタムフィールドのURLを取得する
カスタムフィールドに入力されたURLを取得するには、get_post_meta() 関数を使います。取得した値を esc_url() でエスケープしてからリンクタグに出力するのが基本パターンです。
single.php / page.php
<?php
// カスタムフィールド「custom_url」の値を取得
$custom_url = get_post_meta( get_the_ID(), 'custom_url', true );
// 値が存在する場合のみリンクを表示
if ( $custom_url ) {
echo '<a href="' . esc_url( $custom_url ) . '">参考サイトはこちら</a>';
}
?>
| 引数 |
説明 |
例 |
| $post_id |
投稿ID |
get_the_ID() |
| $key |
カスタムフィールドのキー名 |
'custom_url' |
| $single |
true = 単一の値、false = 配列で返す |
true(通常はtrue) |
⚠ 注意:第3引数の $single を省略すると配列が返ります。URLを1つだけ取得する場合は必ず true を指定してください。false(デフォルト)の場合、同じキーに複数の値が登録されているケースでは全て配列で返されます。
カスタムフィールド取得の代替関数
| 関数 |
返り値 |
用途 |
get_post_meta( $id, $key, true ) |
単一の値(文字列) |
最もよく使う(推奨) |
get_post_meta( $id, $key, false ) |
配列(同キー全値) |
同キーに複数値がある場合 |
get_post_meta( $id ) |
全メタデータの連想配列 |
デバッグ・一括取得 |
get_post_custom( $id ) |
全メタデータの連想配列 |
get_post_meta($id) と同等 |
get_post_custom_values( $key, $id ) |
指定キーの値の配列 |
引数の順序が逆(非推奨) |
⚠ 注意:get_post_custom() と get_post_custom_values() は内部的に get_post_meta() のラッパーです。特別な理由がない限り get_post_meta() を使いましょう。
関連記事:投稿のカスタムフィールドを取得する方法 / get_post_meta完全ガイド
リンクテキストもカスタムフィールドで管理する
URLだけでなく、リンクのテキスト(アンカーテキスト)もカスタムフィールドで管理すると柔軟性が増します。
URL + テキストを取得
<?php
$url = get_post_meta( get_the_ID(), 'ref_url', true );
$text = get_post_meta( get_the_ID(), 'ref_text', true );
if ( $url ) {
// テキスト未入力時はURLをそのまま表示
$label = $text ? esc_html( $text ) : esc_url( $url );
printf(
'<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>',
esc_url( $url ),
$label
);
}
?>
? ポイント:外部サイトへのリンクには target="_blank" と rel="noopener noreferrer" を付けましょう。noopener はセキュリティ対策(window.opener へのアクセス防止)、noreferrer はリファラー送信の抑制です。
セキュリティ:URL処理の3層防御
カスタムフィールドのURLを扱う際は、入力・保存・出力の3段階でセキュリティ対策を行うことが重要です。これを怠ると、XSS(クロスサイトスクリプティング)やフィッシング攻撃の原因になります。
| 段階 |
目的 |
使用する関数 |
処理内容 |
| ① 入力サニタイズ |
不正な文字を除去 |
sanitize_url()
esc_url_raw() |
危険な文字・プロトコルを除去してDBに保存 |
| ② バリデーション |
URL形式かチェック |
filter_var(FILTER_VALIDATE_URL)
wp_http_validate_url() |
正しいURL形式でなければ保存しない |
| ③ 出力エスケープ |
HTML内で安全に出力 |
esc_url()
esc_attr() |
href属性やHTMLコンテキストに応じてエスケープ |
3層防御の実装例
// ① 保存時:サニタイズ + バリデーション
function save_custom_url( $post_id ) {
$raw_url = $_POST['custom_url'] ?? '';
// サニタイズ:危険なプロトコル(javascript: 等)を除去
$sanitized = sanitize_url( $raw_url );
// バリデーション:正しいURL形式かチェック
if ( $sanitized && filter_var( $sanitized, FILTER_VALIDATE_URL ) ) {
update_post_meta( $post_id, 'custom_url', $sanitized );
} else {
delete_post_meta( $post_id, 'custom_url' );
}
}
// ③ 出力時:esc_url() でエスケープ
$url = get_post_meta( get_the_ID(), 'custom_url', true );
if ( $url ) {
echo '<a href="' . esc_url( $url ) . '">リンク</a>';
}
? 危険な例:以下は絶対にNGです。エスケープなしの出力はXSS攻撃の原因になります。
// ❌ 危険:エスケープなし
echo '<a href="' . $custom_url . '">リンク</a>';
// ❌ 危険:javascript: プロトコルが実行される
// 攻撃者が custom_url に「javascript:alert(1)」を入力した場合
echo '<a href="' . $custom_url . '">クリック</a>';
URL関連関数の使い分け
| 関数 |
用途 |
使用場面 |
javascript: 除去 |
esc_url() |
出力エスケープ |
href属性、src属性に出力する時 |
✅ |
esc_url_raw() |
DB保存用サニタイズ |
DBに保存する前(HTMLエンコードなし) |
✅ |
sanitize_url() |
DB保存用サニタイズ |
esc_url_raw() のエイリアス(WP 5.9+) |
✅ |
wp_http_validate_url() |
HTTPリクエスト用バリデーション |
外部URLへリクエストを送る前の検証 |
✅ |
filter_var(FILTER_VALIDATE_URL) |
PHP標準バリデーション |
URL形式の厳密チェック |
❌ |
URLの正規化(保存前の前処理)
ユーザーが入力するURLには揺れがあります。保存前に正規化することで、検索やマッチングの精度が向上します。
URL正規化関数
/**
* URLを正規化する
* - スキームなし → https:// を付与
* - 末尾スラッシュを統一
* - 不要なクエリパラメータを除去
*/
function normalize_custom_url( $url ) {
$url = trim( $url );
// スキームがなければ https:// を付与
if ( $url && ! preg_match( '#^https?://#i', $url ) ) {
$url = 'https://' . $url;
}
// 末尾スラッシュを統一(パスがある場合は付与)
$parsed = wp_parse_url( $url );
if ( isset( $parsed['path'] ) && $parsed['path'] === '%s' ) {
$url = trailingslashit( $url );
}
return sanitize_url( $url );
}
// 使用例
echo normalize_custom_url( 'example.com' ); // https://example.com/
echo normalize_custom_url( 'http://example.com' ); // http://example.com/
カスタムメタボックスでURL入力欄を作成する
WordPressのデフォルトのカスタムフィールド入力欄は使いづらいため、専用のメタボックスを作成すると使い勝手が大幅に向上します。
functions.php(子テーマ)
/**
* URL入力用カスタムメタボックスを追加
*/
function add_url_meta_box() {
add_meta_box(
'custom_url_box', // メタボックスID
'参考リンク', // タイトル
'render_url_meta_box', // コールバック
'post', // 投稿タイプ
'normal', // 表示位置
'high' // 優先度
);
}
add_action( 'add_meta_boxes', 'add_url_meta_box' );
/**
* メタボックスのHTML出力
*/
function render_url_meta_box( $post ) {
// nonceフィールド(CSRF対策)
wp_nonce_field( 'save_custom_url', 'custom_url_nonce' );
$url = get_post_meta( $post->ID, 'custom_url', true );
$text = get_post_meta( $post->ID, 'custom_url_text', true );
?>
<table class="form-table">
<tr>
<th><label for="custom_url">URL</label></th>
<td>
<input type="url" id="custom_url" name="custom_url"
value="<?php echo esc_attr( $url ); ?>"
class="large-text" placeholder="https://example.com">
</td>
</tr>
<tr>
<th><label for="custom_url_text">リンクテキスト</label></th>
<td>
<input type="text" id="custom_url_text" name="custom_url_text"
value="<?php echo esc_attr( $text ); ?>"
class="large-text" placeholder="参考サイト名">
</td>
</tr>
</table>
<?php
}
? type=”url” について:HTML5の <input type="url"> はブラウザ側でURL形式のバリデーションを行います。ただし、チェックされるのはフォーム送信時のみで、JavaScriptで送信した場合やAjax保存時はバイパスされます。そのため、サーバーサイドでのバリデーションは必須です。
functions.php(保存処理)
/**
* メタボックスの値を保存
*/
function save_url_meta_box( $post_id ) {
// nonce検証
if ( ! isset( $_POST['custom_url_nonce'] ) ||
! wp_verify_nonce( $_POST['custom_url_nonce'], 'save_custom_url' ) ) {
return;
}
// 自動保存時はスキップ
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
// 権限チェック
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
// URL:サニタイズ + バリデーション + 保存
$url = sanitize_url( $_POST['custom_url'] ?? '' );
if ( $url && filter_var( $url, FILTER_VALIDATE_URL ) ) {
update_post_meta( $post_id, 'custom_url', $url );
} else {
delete_post_meta( $post_id, 'custom_url' );
}
// テキスト:サニタイズ + 保存
$text = sanitize_text_field( $_POST['custom_url_text'] ?? '' );
update_post_meta( $post_id, 'custom_url_text', $text );
}
add_action( 'save_post', 'save_url_meta_box' );
? セキュリティのポイント:保存処理では必ず①nonce検証(CSRF対策)、②自動保存チェック、③権限チェックの3つを行いましょう。これはWordPressでメタボックスを作成する際の鉄則です。
アンダースコアプレフィックスで管理画面から隠す
カスタムフィールドのキー名を _(アンダースコア)で始めると、WordPress管理画面の「カスタムフィールド」パネルに表示されなくなります。メタボックスで専用UIを作成した場合、ユーザーが誤って直接編集するのを防げます。
プレフィックス付きキーの例
// ❌ 管理画面のカスタムフィールド欄に表示される
update_post_meta( $post_id, 'custom_url', $url );
// ✅ 管理画面のカスタムフィールド欄に表示されない(推奨)
update_post_meta( $post_id, '_custom_url', $url );
// 取得時も同じプレフィックスを指定
$url = get_post_meta( get_the_ID(), '_custom_url', true );
⚠ 注意:ACFも内部的にアンダースコアプレフィックスを使用しています。ACFフィールドのデータを get_post_meta() で直接取得する場合は、フィールド名の前に _ が付いたメタキーも保存されている点に注意してください(例: reference_url → _reference_url にACFの設定値が格納)。
ACF(Advanced Custom Fields)での実装
ACFを使えば、コードを書かずにURL入力フィールドを作成できます。ACFにはURLに関連するフィールドが2種類あります。
URLフィールド vs リンクフィールドの違い
| 項目 |
URLフィールド |
リンクフィールド |
| 返り値 |
文字列(URLのみ) |
配列(url, title, target) |
| 入力UI |
URLテキストボックス |
URL + テキスト + target選択 |
| 内部リンク検索 |
❌ なし |
✅ あり(投稿検索ポップアップ) |
| バリデーション |
URL形式チェック |
URL形式チェック |
| おすすめ用途 |
外部URLのみ必要な場合 |
テキスト・target も管理したい場合 |
ACF URLフィールドの出力
ACF URLフィールド
<?php
$url = get_field( 'reference_url' );
if ( $url ) {
echo '<a href="' . esc_url( $url ) . '" target="_blank" rel="noopener">参考サイト</a>';
}
?>
ACF リンクフィールドの出力
ACF リンクフィールド(配列で取得)
<?php
$link = get_field( 'reference_link' );
if ( $link ) {
$url = esc_url( $link['url'] );
$title = esc_html( $link['title'] );
$target = $link['target'] ? esc_attr( $link['target'] ) : '_self';
printf(
'<a href="%s" target="%s" rel="noopener noreferrer">%s</a>',
$url,
$target,
$title
);
}
?>
関連記事:カスタムフィールドの入力有無に応じて表示を切り替える方法
複数URLを管理する(参考リンク一覧)
1つの投稿に複数の参考URLを登録したいケースは多くあります。ネイティブのカスタムフィールドとACFリピーターフィールドの2つの方法を紹介します。
方法1:同じキーで複数値を登録
同じキーの複数値を全て取得
<?php
// 第3引数を false にすると配列で全件取得
$urls = get_post_meta( get_the_ID(), 'ref_urls', false );
if ( ! empty( $urls ) ) {
echo '<h3>参考リンク</h3>';
echo '<ul>';
foreach ( $urls as $url ) {
if ( filter_var( $url, FILTER_VALIDATE_URL ) ) {
printf( '<li><a href="%s" target="_blank" rel="noopener">%s</a></li>',
esc_url( $url ),
esc_html( $url )
);
}
}
echo '</ul>';
}
?>
関連記事:カスタムフィールドの値をリストタグで囲う方法
方法2:ACFリピーターフィールド
ACF リピーターフィールド
<?php
if ( have_rows( 'reference_links' ) ) : ?>
<div class="reference-links">
<h3>参考リンク</h3>
<ul>
<?php while ( have_rows( 'reference_links' ) ) : the_row(); ?>
<li>
<a href="<?php echo esc_url( get_sub_field( 'url' ) ); ?>"
target="_blank" rel="noopener noreferrer">
<?php echo esc_html( get_sub_field( 'label' ) ); ?>
</a>
</li>
<?php endwhile; ?>
</ul>
</div>
<?php endif; ?>
ショートコードでカスタムフィールドのURLを出力する
テンプレートファイルを編集せずに、記事本文内でカスタムフィールドのURLをリンクとして表示したい場合は、ショートコードを作成します。
functions.php(ショートコード登録)
/**
* [custom_link key="custom_url" text="参考サイト" new_tab="true"]
*/
function custom_link_shortcode( $atts ) {
$atts = shortcode_atts( array(
'key' => 'custom_url',
'text' => '',
'new_tab' => 'true',
'class' => '',
), $atts, 'custom_link' );
$url = get_post_meta( get_the_ID(), sanitize_key( $atts['key'] ), true );
if ( ! $url || ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
return '';
}
$label = $atts['text'] ? esc_html( $atts['text'] ) : esc_url( $url );
$target = ( $atts['new_tab'] === 'true' ) ? ' target="_blank" rel="noopener noreferrer"' : '';
$class = $atts['class'] ? ' class="' . esc_attr( $atts['class'] ) . '"' : '';
return sprintf( '<a href="%s"%s%s>%s</a>', esc_url( $url ), $target, $class, $label );
}
add_shortcode( 'custom_link', 'custom_link_shortcode' );
記事本文での使用例
<!-- 基本的な使い方 -->
[custom_link key="custom_url" text="参考サイトはこちら"]
<!-- 同じタブで開く -->
[custom_link key="custom_url" text="詳しくはこちら" new_tab="false"]
<!-- CSSクラスを付与 -->
[custom_link key="download_url" text="ダウンロード" class="btn btn-primary"]
関連記事:オリジナルのカスタムショートコードを作成する方法
WP_Queryでカスタムフィールドの有無によるフィルタリング
「URLが入力されている投稿だけを表示したい」「特定のURLを含む投稿を検索したい」場合は、WP_Queryのmeta_queryを使います。
URLが存在する投稿を取得
<?php
$args = array(
'post_type' => 'post',
'posts_per_page' => 10,
'meta_query' => array(
array(
'key' => 'custom_url',
'value' => '',
'compare' => '!=',
),
),
);
$query = new WP_Query( $args );
if ( $query->have_posts() ) :
while ( $query->have_posts() ) : $query->the_post();
$url = get_post_meta( get_the_ID(), 'custom_url', true );
echo '<li><a href="' . esc_url( $url ) . '">' . get_the_title() . '</a></li>';
endwhile;
wp_reset_postdata();
endif;
?>
特定ドメインのURLを含む投稿を検索
LIKEで部分一致検索
$args = array(
'post_type' => 'post',
'meta_query' => array(
array(
'key' => 'custom_url',
'value' => 'example.com',
'compare' => 'LIKE',
),
),
);
// → custom_url に「example.com」を含む投稿を取得
関連記事:WP_Queryでカスタムフィールドを複数条件でソートする方法 / カスタムフィールドを検索対象にする方法
register_post_meta()でREST API対応にする
WordPress 4.9.8以降では、register_post_meta()を使ってカスタムフィールドをREST APIに公開できます。ブロックエディタ(Gutenberg)との連携にも必要です。
functions.php
function register_custom_url_meta() {
register_post_meta( 'post', 'custom_url', array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'sanitize_callback' => 'sanitize_url',
'auth_callback' => function() {
return current_user_can( 'edit_posts' );
},
) );
}
add_action( 'init', 'register_custom_url_meta' );
REST APIからの取得・更新
// 取得:GET /wp-json/wp/v2/posts/123
// → レスポンスの meta.custom_url にURLが含まれる
// 更新:POST /wp-json/wp/v2/posts/123
// リクエストボディ:
{
"meta": {
"custom_url": "https://example.com/new-url"
}
}
? ポイント:register_post_meta() で sanitize_callback を設定すると、REST API経由での更新時にも自動でサニタイズが適用されます。auth_callback は認証チェックで、未認証ユーザーによる更新を防ぎます。
パフォーマンス:get_post_meta のキャッシュの仕組み
get_post_meta() は内部で wp_cache(オブジェクトキャッシュ)を利用しています。同一リクエスト内での動作を理解すると、無駄な最適化を避けられます。
| タイミング |
動作 |
DBクエリ |
| 投稿をクエリした時点 |
その投稿の全メタデータが一括でキャッシュに読み込まれる |
1回(全メタ一括取得) |
| 1回目の get_post_meta() |
キャッシュから取得(DBアクセスなし) |
0回 |
| 2回目以降の get_post_meta() |
同じくキャッシュから取得 |
0回 |
| update_post_meta() 後 |
キャッシュが更新される |
1回(UPDATE文) |
変数にキャッシュする必要はない
// ❌ 不要な最適化(get_post_meta は内部キャッシュがある)
$meta_cache = get_post_meta( get_the_ID() ); // 全メタを変数にキャッシュ
$url = $meta_cache['custom_url'][0] ?? '';
$text = $meta_cache['custom_text'][0] ?? '';
// ✅ そのまま複数回呼んでOK(DBアクセスは発生しない)
$url = get_post_meta( get_the_ID(), 'custom_url', true );
$text = get_post_meta( get_the_ID(), 'custom_text', true );
? ポイント:ループ内で同じ投稿の get_post_meta() を複数回呼んでも、パフォーマンスに影響はほぼありません。ただし、ループ外で別の投稿のメタを大量に取得する場合は update_meta_cache() で事前にプリフェッチすると効率的です。
実務で使える7つのパターン
パターン1:参考リンクセクション
記事の末尾に参考リンクをカード型で表示するパターンです。
参考リンクカード
<?php
$url = get_post_meta( get_the_ID(), 'ref_url', true );
$text = get_post_meta( get_the_ID(), 'ref_text', true );
$desc = get_post_meta( get_the_ID(), 'ref_desc', true );
if ( $url ) : ?>
<div style="border:1px solid #e2e8f0;border-radius:8px;padding:1em;margin:1em 0;">
<p style="margin:0 0 0.3em;font-size:12px;color:#64748b;">? 参考リンク</p>
<a href="<?php echo esc_url( $url ); ?>" target="_blank"
rel="noopener noreferrer"
style="font-weight:bold;text-decoration:none;">
<?php echo esc_html( $text ?: $url ); ?>
</a>
<?php if ( $desc ) : ?>
<p style="margin:0.3em 0 0;font-size:13px;color:#64748b;">
<?php echo esc_html( $desc ); ?>
</p>
<?php endif; ?>
</div>
<?php endif; ?>
パターン2:ダウンロードボタン
ダウンロードボタン
<?php
$download_url = get_post_meta( get_the_ID(), 'download_url', true );
if ( $download_url ) : ?>
<a href="<?php echo esc_url( $download_url ); ?>"
download
class="download-btn"
style="display:inline-block;padding:12px 24px;
background:linear-gradient(135deg,#3b82f6,#2563eb);
color:#fff;border-radius:8px;text-decoration:none;
font-weight:bold;transition:transform 0.2s;">
⬇ ダウンロード
</a>
<?php endif; ?>
パターン3:SNSプロフィールリンク(著者情報)
SNSリンクアイコン表示
<?php
$sns_links = array(
'twitter_url' => array( 'label' => 'X (Twitter)', 'icon' => '?' ),
'github_url' => array( 'label' => 'GitHub', 'icon' => '?' ),
'website_url' => array( 'label' => 'Website', 'icon' => '?' ),
);
echo '<div class="sns-links" style="display:flex;gap:12px;">';
foreach ( $sns_links as $key => $info ) {
$url = get_post_meta( get_the_ID(), $key, true );
if ( $url && filter_var( $url, FILTER_VALIDATE_URL ) ) {
printf(
'<a href="%s" target="_blank" rel="noopener" title="%s">%s</a>',
esc_url( $url ),
esc_attr( $info['label'] ),
$info['icon']
);
}
}
echo '</div>';
?>
パターン4:外部リンクへのリダイレクト
投稿のパーマリンクを外部URLに置き換えるパターンです。アフィリエイトリンクの管理などに使えます。
functions.php(パーマリンク置換)
/**
* カスタムフィールドにURLがあればパーマリンクを置換
*/
add_filter( 'post_link', function( $permalink, $post ) {
$external = get_post_meta( $post->ID, 'external_url', true );
if ( $external && filter_var( $external, FILTER_VALIDATE_URL ) ) {
return esc_url( $external );
}
return $permalink;
}, 10, 2 );
パターン5:投稿ヘッダーにOGP画像URLを設定
OGP画像をカスタムフィールドで指定
add_action( 'wp_head', function() {
if ( ! is_singular() ) return;
$ogp_url = get_post_meta( get_the_ID(), 'ogp_image_url', true );
if ( $ogp_url && filter_var( $ogp_url, FILTER_VALIDATE_URL ) ) {
printf(
'<meta property="og:image" content="%s">' . "\n",
esc_url( $ogp_url )
);
}
} );
関連記事:投稿ごとにOGP画像を設定するカスタムフィールドの作成方法
パターン6:条件付き表示(値なしの場合の処理)
値なし時のフォールバック
<?php
$url = get_post_meta( get_the_ID(), 'custom_url', true );
if ( $url ) {
// URLが入力されている → 外部リンク
printf(
'<a href="%s" target="_blank" rel="noopener">詳細はこちら(外部サイト)</a>',
esc_url( $url )
);
} else {
// URLが未入力 → 投稿自体のパーマリンク
printf(
'<a href="%s">詳細はこちら</a>',
esc_url( get_permalink() )
);
}
?>
関連記事:カスタムフィールドに値が入っていない記事を取得する方法 / カスタムフィールドの入力有無に応じて表示を切り替える方法
パターン7:管理画面の投稿一覧にURL列を追加
functions.php
// カラム追加
add_filter( 'manage_posts_columns', function( $columns ) {
$columns['custom_url'] = 'カスタムURL';
return $columns;
} );
// カラム内容
add_action( 'manage_posts_custom_column', function( $column, $post_id ) {
if ( $column === 'custom_url' ) {
$url = get_post_meta( $post_id, 'custom_url', true );
if ( $url ) {
printf( '<a href="%s" target="_blank">%s</a>',
esc_url( $url ),
esc_html( wp_trim_words( $url, 5 ) )
);
} else {
echo '—';
}
}
}, 10, 2 );
関連記事:管理画面の投稿一覧にカスタムフィールドの値を表示する方法
トラブルシューティング
| 症状 |
原因 |
対処法 |
| get_post_meta() が空を返す |
キー名のタイポ、$single 未指定で配列が返っている |
キー名を正確に確認。var_dump( get_post_meta( $id ) ) で全メタデータを出力して確認 |
| リンクをクリックしても遷移しない |
URLに https:// がない(例: example.com) |
保存時に esc_url_raw() を通すと自動で http:// が付与される。またはバリデーションで弾く |
URLに & が表示される |
二重エスケープ(esc_url() を複数回呼んでいる) |
esc_url() は出力直前に1回だけ呼ぶ |
| ACFフィールドの値が取得できない |
get_post_meta() と get_field() のキー名が異なる |
ACFは内部的に _ プレフィックス付きでメタキーを保存する。ACF使用時は get_field() を使う |
| ループ内で同じURLが出力される |
get_the_ID() がループ外のIDを参照 |
$post->ID を明示的に渡すか、wp_reset_postdata() を忘れていないか確認 |
| メタボックスの値が保存されない |
nonce検証の失敗、自動保存時の処理、権限不足 |
nonce名・action名の一致を確認。DOING_AUTOSAVE チェックが正しいか確認 |
| REST APIで meta が返らない |
register_post_meta() の show_in_rest が未設定 |
show_in_rest => true を設定し、init フックで登録 |
まとめ
| やりたいこと |
方法 |
セキュリティ |
| 基本的なURL取得・リンク表示 |
get_post_meta() + esc_url() |
出力エスケープ |
| 専用入力欄の作成 |
add_meta_box() + nonce + 権限チェック |
3層防御 |
| ACFでの実装 |
URLフィールド or リンクフィールド |
ACF内蔵バリデーション |
| 記事本文での出力 |
ショートコード [custom_link] |
バリデーション + エスケープ |
| URLありの投稿を検索 |
WP_Query + meta_query |
— |
| REST API連携 |
register_post_meta() |
sanitize_callback + auth_callback |
| 複数URLの管理 |
$single=false or ACFリピーター |
ループ内で個別バリデーション |
カスタムフィールドのURLをリンクとして表示する基本は get_post_meta() + esc_url() の組み合わせです。ここに入力サニタイズ(sanitize_url)とバリデーション(FILTER_VALIDATE_URL)を加えた3層防御を意識することで、安全な実装になります。
実務では、メタボックスやACFで入力UIを整え、ショートコードやWP_Queryと組み合わせることで、柔軟かつ堅牢なURL管理が実現できます。
関連記事:投稿のカスタムフィールドを取得する方法 / get_post_meta完全ガイド / 投稿や固定ページのURLを取得する方法