17.

JavaScript のみで form を POST 送信する方法完全ガイド

編集
この記事の要点
  • HTML を一切書かず、JavaScript だけで
    を動的生成 → POST 送信する手法
  • 基本パターンは document.createElement("form") + form.method="POST" + form.action=URL + 各 inputappendChild + document.body.appendChild(form) + form.submit()
  • ページ遷移を伴う送信は form.submit、遷移なしfetch() / XMLHttpRequest
  • ファイル送信は FormData を使い enctype="multipart/form-data" を設定
  • Laravel / Rails 等は CSRF トークンを hidden input で含める必要あり

なぜ JavaScript だけで form を作るのか

HTML に を書かず、JavaScript で動的にフォームを組み立てて POST 送信したい場面は意外と多いです。

  • ボタンクリックだけで別画面へ POST 遷移させたい(GET だとパラメータが URL に出る)
  • SPA から外部サイトに POST で飛ばす(決済ゲートウェイ等)
  • 動的に生成したデータをそのまま送信
  • ブックマークレット / ユーザースクリプトから送信

最小コード

function postSubmit(url, params) {
  const form = document.createElement('form');
  form.method = 'POST';
  form.action = url;

  for (const key in params) {
    const input = document.createElement('input');
    input.type = 'hidden';
    input.name = key;
    input.value = params[key];
    form.appendChild(input);
  }

  document.body.appendChild(form);
  form.submit();
}

// 利用例
postSubmit('/order/confirm', {
  product_id: 123,
  qty: 2,
  coupon: 'SPRING20'
});

このコードを実行すると、ブラウザは /order/confirmPOST で画面遷移します。HTML の をクリックしたのと完全に同じ挙動です。

各プロパティの意味

プロパティ説明
form.methodPOST / GETHTTP メソッド。デフォルト GET
form.actionURL送信先。相対 / 絶対どちらも可
form.target_self / _blank送信結果の表示先。新タブで開きたいときは _blank
form.enctypeapplication/x-www-form-urlencoded / multipart/form-dataエンコード形式。ファイル送信は後者必須
form.acceptCharsetUTF-8文字コード

document.body.appendChild は必要か

はい、必要です。DOM ツリーに接続されていない form は submit できません(一部ブラウザで silently fail)。送信後すぐ削除する場合:

document.body.appendChild(form);
form.submit();
// submit() 後にすぐ navigation が始まるので不要だが、
// target=_blank で残したくない場合:
form.remove();

CSRF トークンを含める(Laravel / Rails)

Laravel は をレイアウトに置く慣習があります:

function postWithCsrf(url, params) {
  const token = document.querySelector('meta[name="csrf-token"]').content;

  const form = document.createElement('form');
  form.method = 'POST';
  form.action = url;

  // Laravel の _token フィールド
  const csrfInput = document.createElement('input');
  csrfInput.type = 'hidden';
  csrfInput.name = '_token';
  csrfInput.value = token;
  form.appendChild(csrfInput);

  for (const key in params) {
    const input = document.createElement('input');
    input.type = 'hidden';
    input.name = key;
    input.value = params[key];
    form.appendChild(input);
  }

  document.body.appendChild(form);
  form.submit();
}

Rails は authenticity_token という名前、Django は csrfmiddlewaretoken。フレームワークごとに名前が違うので注意。

PUT / DELETE / PATCH を送りたい

HTML 標準の form は GET / POST しか送信できません。Laravel / Rails では _method hidden field を使った method spoofing が定番:

function deleteForm(url) {
  const form = document.createElement('form');
  form.method = 'POST';
  form.action = url;

  // Laravel の method spoofing
  const methodInput = document.createElement('input');
  methodInput.type = 'hidden';
  methodInput.name = '_method';
  methodInput.value = 'DELETE';
  form.appendChild(methodInput);

  // CSRF も忘れずに
  const token = document.querySelector('meta[name="csrf-token"]').content;
  const csrf = document.createElement('input');
  csrf.type = 'hidden';
  csrf.name = '_token';
  csrf.value = token;
  form.appendChild(csrf);

  document.body.appendChild(form);
  form.submit();
}

ファイルを送信したい(FormData)

動的 form はバイナリを直接持てないため、fetch + FormData を使うのが現代的:

async function uploadFile(url, file, extraParams = {}) {
  const fd = new FormData();
  fd.append('file', file);
  for (const k in extraParams) fd.append(k, extraParams[k]);

  const token = document.querySelector('meta[name="csrf-token"]').content;

  const res = await fetch(url, {
    method: 'POST',
    headers: { 'X-CSRF-TOKEN': token },
    body: fd,
  });
  return res.json();
}

// 利用例(input[type=file] から取得)
const fileInput = document.querySelector('#file');
fileInput.addEventListener('change', e => {
  uploadFile('/upload', e.target.files[0], { user_id: 1 })
    .then(json => console.log(json));
});

form.submit() と fetch() の使い分け

用途form.submit()fetch()
ページ遷移○ 起きる× 起きない
サーバ応答を JS で受ける×
ファイル送信○ (multipart 設定要)○ (FormData)
クロスオリジン○ (リダイレクトは見れる)○ (CORS 設定要)
決済ゲートウェイ POST○ 推奨× CORS で弾かれる
SPA 内部 API×○ 推奨

配列パラメータの送信

PHP / Laravel は name="tags[]" 形式で配列を受け取ります:

function appendArray(form, name, values) {
  values.forEach(v => {
    const input = document.createElement('input');
    input.type = 'hidden';
    input.name = name + '[]'; // tags[]
    input.value = v;
    form.appendChild(input);
  });
}

const form = document.createElement('form');
form.method = 'POST';
form.action = '/search';
appendArray(form, 'tags', ['php', 'laravel', 'vue']);
document.body.appendChild(form);
form.submit();
// → POST /search  tags[]=php&tags[]=laravel&tags[]=vue

よくある落とし穴

  • form を appendChild しない → ブラウザによっては submit() が無視される
  • input.value に objet を入れる[object Object] の文字列になる。事前に JSON.stringify
  • 改行を含む値 → hidden input でも保持される。サーバ側で正しく受け取れているか確認
  • CSRF 忘れ → 419 Page Expired / 422 になる
  • 同じ name を 2 つ → 後勝ち。配列にしたければ name[]

FAQ

Q: GET でも同じやり方でできる?
A: できます。form.method = "GET" にするだけ。ただし GET は URL にパラメータが付くので、長すぎる値や秘匿情報は POST にすべき。

Q: window.location.href で POST はできる?
A: できません。location.href は GET 専用。POST 遷移したい場合は form を動的生成するこの方法が唯一の手段です。

Q: jQuery でも同じことできる?
A: $("").attr({method:"POST",action:url}).append(...).appendTo("body").submit() で同じ。ただし現代は Vanilla で十分。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 記述方法
  2. コメント
  3. 変数の宣言
  4. 関数
  5. 演算子
  6. 条件文
  7. 配列
  8. 連想配列
  9. ループ処理
  10. 非同期処理
  11. 同期処理
  12. 確認ウィンドウを表示する方法
  13. 文字の置換
  14. base urlを取得する方法
  15. formのsubmit前にjavascriptを呼び出す方法
  16. undefinedのイコール判定
  17. Javascript のみで form を post で submit する方法