【JavaScript】フォームに hidden input を動的に追加する方法|createElement・insertAdjacentHTML・FormData の使い分けと実践パターン解説

フォームを送信する際に、ユーザーには見せずにデータを添付したい場面があります。CSRF トークン・セッション ID・選択中のタブ状態・Ajax でのページ番号など、様々なシーンで type="hidden" の input 要素が活躍します。

この記事では、JavaScript で hidden input を動的に追加する方法を3つのアプローチで解説し、それぞれの違いと使い分け、そして innerHTML を使う際の XSS リスクも含めて実践的に説明します。

スポンサーリンク

type=”hidden” の input とは何か

type="hidden" の input 要素は、画面上には表示されませんが、フォーム送信時にその namevalue がサーバーに送信されます。ページのレンダリングには影響せず、CSS でスタイルを当てても意味がありません。

用途
CSRF トークン サーバー側で生成したトークンを埋め込み、改ざん防止
ID・外部キー 選択した商品 ID・ユーザー ID などを非表示で送信
状態の保持 現在のページ番号・タブ・並び順を送信時に付与
Ajax 追加パラメータ 送信直前に JavaScript で動的に値をセット
セキュリティ上の注意:hidden input は「ユーザーに見えない」だけで、ブラウザの開発者ツールや HTML ソースから誰でも確認・改ざんできます。機密情報(パスワード・個人情報)を hidden input に入れないでください。CSRF トークンのように改ざんを検知する仕組みとして使うのは正当な用途です。

3つのアプローチの比較

方法 既存 DOM への影響 XSS リスク 推奨度
createElement + setAttribute なし なし ◎ 最推奨
insertAdjacentHTML なし 値をエスケープすれば安全 ○ 可
innerHTML += 既存要素が再レンダリングされる あり(要注意) △ 非推奨

方法1:createElement + setAttribute(最推奨)

DOM 操作の基本かつ最も安全な方法です。要素を JavaScript オブジェクトとして生成・設定してからフォームに追加するため、ユーザー入力値をそのまま扱っても XSS が発生しません。

HTML
<form id="myForm">
  <input type="text" name="username">
  <button type="submit">送信</button>
</form>
createElement の基本
const form = document.getElementById('myForm');

// input 要素を生成して属性をセット
const hidden = document.createElement('input');
hidden.type  = 'hidden';
hidden.name  = 'token';
hidden.value = 'abc123xyz';

// フォームに追加
form.appendChild(hidden);
setAttribute vs プロパティ直接代入:hidden.setAttribute("type", "hidden")hidden.type = "hidden" はどちらも動作します。プロパティへの直接代入の方がタイプ数が少なく、IDE の補完も効くため一般的です。

既存の hidden input を更新する(重複追加の防止)

フォーム送信ボタンを複数回クリックしたとき、毎回新しい hidden input が追加されると同じ name が重複してしまいます。追加前に存在チェックを行い、あれば値を更新するだけにするのが安全です。

重複追加を防ぐパターン
function setHiddenField(form, name, value) {
  let input = form.querySelector(`input[name='${name}']`);
  if (!input) {
    input = document.createElement('input');
    input.type = 'hidden';
    input.name = name;
    form.appendChild(input);
  }
  input.value = value; // 既存なら値だけ更新
}

// 使い方
setHiddenField(form, 'page', '3');
setHiddenField(form, 'page', '4'); // 重複せず値が 4 に更新される

複数の hidden input を一括追加(DocumentFragment 活用)

複数フィールドを一括追加
function addHiddenFields(form, fields) {
  const fragment = document.createDocumentFragment();
  Object.entries(fields).forEach(([name, value]) => {
    const input = document.createElement('input');
    input.type  = 'hidden';
    input.name  = name;
    input.value = String(value);
    fragment.appendChild(input);
  });
  form.appendChild(fragment); // DOM 更新は1回だけ
}

// 使い方
addHiddenFields(form, {
  utm_source: 'google',
  utm_medium: 'cpc',
  referrer: document.referrer,
});

方法2:insertAdjacentHTML(開発者が制御する固定文字列のみOK)

insertAdjacentHTML は HTML 文字列を指定位置に挿入するメソッドです。innerHTML += と異なり既存の DOM を破壊しないため、こちらのほうが安全です。

ただし安全なのは、ソースコードに直接書いた固定文字列を使う場合のみです。ユーザー入力・API レスポンス・URL パラメータなど外部から来る値を value に使う場合は、後述のエスケープ関数が必要です。

insertAdjacentHTML の使い方
form.insertAdjacentHTML('beforeend',
  '<input type="hidden" name="token" value="abc123xyz">');
XSS 注意:value に変数を使う場合は必ずエスケープ。
以下のように動的な値をそのまま埋め込むと XSS の原因になります。
NG: 変数をそのまま埋め込む(XSS リスク)
// BAD: userInput に " name="evil" value="x" が入ると属性インジェクションが起きる
const userInput = getSomeValue();
form.insertAdjacentHTML('beforeend',
  `<input type="hidden" name="q" value="${userInput}">`);
OK: エスケープ関数を通す
function escapeAttr(str) {
  return String(str)
    .replace(/&/g, '&amp;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#x27;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;');
}

form.insertAdjacentHTML('beforeend',
  `<input type="hidden" name="q" value="${escapeAttr(userInput)}">`);
動的な値を扱う場合は 方法1(createElement)を使うのが最もシンプルで安全です。insertAdjacentHTML は静的な値を短く書きたいときに限定して使いましょう。要素追加・削除の基礎は【JavaScript】HTML 要素を追加・削除する方法でも解説しています。

方法3:innerHTML +=(非推奨・理由を理解する)

innerHTML += はコードが短く書けますが、2つの深刻な問題があるため原則として使わないことを推奨します。

問題1:既存の DOM が再レンダリングされる

innerHTML += は内部で「既存 HTML を文字列化 → 新しい HTML を結合 → 全体を再パース」という処理を行います。そのため、フォーム内のテキスト入力値・フォーカス・イベントリスナーがすべてリセットされます。

innerHTML += の問題を確認する
const form = document.getElementById('myForm');
const nameInput = form.querySelector('[name="username"]');
nameInput.addEventListener('input', () => console.log('入力中'));

// ユーザーが入力したあとに...
form.innerHTML += '<input type="hidden" name="token" value="x">';

// ↑ この時点で username の入力値・イベントリスナーが消える

問題2:XSS リスク

value に外部からの入力値を使うと、悪意のある文字列が HTML として解釈されスクリプトが実行される危険があります。

innerHTML += を使わざるを得ない場合は必ずエスケープ
// 本当に使う場合は先述の escapeAttr() でエスケープする
// ただし、この用途には createElement を使うほうがはるかに安全で明快

実践パターン集

フォーム送信直前に値を追加・更新する

submit イベントをフックして、送信直前に動的な値をセットするパターンです。

送信前フック
const form = document.getElementById('myForm');

form.addEventListener('submit', (e) => {
  // 送信時刻を付与
  setHiddenField(form, 'submitted_at', new Date().toISOString());

  // 現在のページ URL を付与
  setHiddenField(form, 'source_url', location.href);
});

function setHiddenField(form, name, value) {
  let input = form.querySelector(`input[name='${name}']`);
  if (!input) {
    input = document.createElement('input');
    input.type = 'hidden';
    input.name = name;
    form.appendChild(input);
  }
  input.value = value;
}

チェックボックスの選択 ID をまとめて hidden input に入れて送信

複数チェックボックスの選択値を1つの hidden input にまとめてカンマ区切りで送信するパターンです。

HTML
<form id="deleteForm">
  <input type="checkbox" class="item-cb" value="101"> 商品A
  <input type="checkbox" class="item-cb" value="102"> 商品B
  <input type="checkbox" class="item-cb" value="103"> 商品C
  <button type="submit">選択を削除</button>
</form>
選択 ID を hidden input に集約
const form = document.getElementById('deleteForm');

form.addEventListener('submit', (e) => {
  const checked = [...form.querySelectorAll('.item-cb:checked')]
    .map(cb => cb.value);

  if (checked.length === 0) {
    e.preventDefault();
    alert('削除する項目を選択してください');
    return;
  }

  setHiddenField(form, 'delete_ids', checked.join(','));
});

FormData で hidden input を含む全フィールドを収集して Ajax 送信

FormData オブジェクトはフォーム内のすべての input(hidden を含む)を自動的に収集します。動的に追加した hidden input も確実に含まれます。

FormData による Ajax 送信
form.addEventListener('submit', async (e) => {
  e.preventDefault();

  // 送信前に hidden field を追加
  setHiddenField(form, 'action', 'create');

  // FormData が hidden input も自動収集
  const formData = new FormData(form);

  // デバッグ: 送信データを確認
  for (const [key, value] of formData.entries()) {
    console.log(key, value);
  }

  try {
    const res = await fetch('/api/submit', {
      method: 'POST',
      body: formData, // Content-Type は自動で multipart/form-data
    });
    const result = await res.json();
    console.log('送信成功:', result);
  } catch (err) {
    console.error('送信失敗:', err);
  }
});

FormData の詳しい使い方は【JavaScript】FormDataの使い方|Ajaxでフォーム送信を簡単にする方法をご覧ください。

PHP での受け取り方

hidden input で送信された値は、通常の input と同じく $_POST または $_GET で受け取れます。

以下は2つのパターンを示しています。①は単純な値の受け取り、②は hidden input を利用した CSRF トークン検証の例です(CSRF 対策には hash_equals() でタイミング攻撃を防ぐ比較が必要です)。

PHP での受け取り
// ① 通常の hidden input 値の受け取り
$pageNo    = (int)($_POST['page']    ?? 1);
$sortOrder = $_POST['sort_order']    ?? 'asc';

// ② CSRF トークン検証(hidden input を CSRF 対策に使う場合の例)
// セッションに保存したトークンと比較する
$submittedToken = $_POST['csrf_token'] ?? '';
$expectedToken  = $_SESSION['csrf_token'] ?? '';

// hash_equals() でタイミング攻撃を防ぎながら比較
if (empty($submittedToken) || !hash_equals($expectedToken, $submittedToken)) {
    http_response_code(403);
    exit('不正なリクエストです');
}

PHP でのフォーム値の受け取りは【PHP】フォーム送信された値を受け取る方法も参考にしてください。

hidden input の値を読み取る・更新する・削除する

読み取り・更新・削除
const form = document.getElementById('myForm');

// 読み取り(name 属性で取得)
const tokenInput = form.querySelector('input[name="token"]');
console.log(tokenInput?.value);

// 更新
if (tokenInput) tokenInput.value = 'newToken456';

// 削除
tokenInput?.remove();

// 全ての hidden input を取得
const allHidden = form.querySelectorAll('input[type="hidden"]');
allHidden.forEach(el => console.log(el.name, el.value));

querySelector の使い方は【JavaScript】querySelector の使い方完全ガイドで詳しく解説しています。

よくある質問

Qhidden input の値はユーザーに見えませんか?
A画面上には表示されませんが、ブラウザの開発者ツール(F12)の Elements タブや HTML ソースからは誰でも確認できます。パスワード・個人情報など機密性の高いデータは入れないでください。
QinnerHTML += で hidden input を追加したらフォームの入力値が消えました。
AinnerHTML += はフォーム内の HTML を一度文字列に変換して再パースするため、既存の入力値・イベントリスナーがすべてリセットされます。createElementinsertAdjacentHTML('beforeend', ...) を使ってください。
Q同じ name で複数の hidden input を追加するとどうなりますか?
APHP の場合 $_POST["name"] は最後の値のみ取れます(配列にするには name="items[]" 形式が必要)。JavaScript の FormDatagetAll("name") で全値を取得できます。意図せず重複した場合は本記事で紹介した setHiddenField() で値の上書きのみにする方法を使ってください。
Qhidden input を使わずに Ajax で追加パラメータを送る方法はありますか?
Afetch や XMLHttpRequest を使う場合は、FormData.append(name, value) で直接追加できます。フォーム要素を経由せず JavaScript オブジェクトを JSON で送る場合は JSON.stringify() + Content-Type: application/json で送信する方法が一般的です。
QCSRF トークンを hidden input で送るのは安全ですか?
A正しく実装すれば有効な CSRF 対策です。サーバー側でセッションとトークンを紐づけて検証し、トークンはランダムかつ推測困難な値を使ってください。hidden input の値は改ざんできますが、正当なトークンを知らなければ攻撃は成立しません。

まとめ

JavaScript で hidden input を動的に追加する方法を3つ解説しました。選び方は明確です。

  • 動的な値を扱うなら createElement(XSS リスクなし・最推奨)
  • 静的な HTML を短く書くなら insertAdjacentHTML(既存 DOM を壊さない)
  • innerHTML += は使わない(入力値リセット・XSS リスク・再レンダリングコスト)

また、送信直前に値をセットする setHiddenField() ユーティリティと、FormData による Ajax 送信パターンをセットで覚えておくと実務で即使えます。

フォーム送信時のさらなる活用として、【JavaScript】フォーム送信時にローディングアニメーションを表示する方法も合わせてご覧ください。