ファイル添付を扱う

中級 約15分 最終更新 2026-09-08

ファイル添付は「問い合わせフォーム連携」の応用です。type: 'file' の項目はブラウザから直接バイナリを送る必要があるため、JSONではなく multipart/form-data で扱います。SDKはFile・Blobを渡すだけで自動的にmultipartへ切り替わるので、自前サーバー側で特別なエンコード処理は不要です。

前提:フォーム定義の取得・基本的な入力UIの組み立ては問い合わせフォーム連携を先に読んでいること。

1. file 項目を見分ける

fields のうち type === 'file' の項目だけ <input type="file"> を生成します。requiredはそのまま必須属性に渡せます。現在のSDKでは1つのfile項目につき1ファイル(Blob)を送る仕様なので、複数ファイルを受け付けたい場合はフォーム側にfile型の項目を複数用意してください(例:添付ファイル1、添付ファイル2)。

src/components/FileFieldInput.tsx
type FormField = {
  type: 'text' | 'email' | 'tel' | 'textarea' | 'radio' | 'checkbox' | 'select' | 'file';
  name: string;
  label: string;
  required: boolean;
  options?: { label: string; value: string }[] | null;
};

function FileFieldInput({ field }: { field: FormField }) {
  return (
    <div>
      <label>
        {field.label}
        {field.required && ' *'}
      </label>
      <input type="file" name={field.name} required={field.required} accept=".pdf,.jpg,.jpeg,.png" />
    </div>
  );
}

export function FormFields({ fields }: { fields: FormField[] }) {
  return (
    <>
      {fields.map((f) =>
        f.type === 'file' ? (
          <FileFieldInput key={f.name} field={f} />
        ) : (
          <div key={f.name} /> /* text・select等は「問い合わせフォーム連携」を参照 */
        ),
      )}
    </>
  );
}

2. File を values に渡す(multipart 自動)

三段構成(ブラウザ→自前サーバー→SDK)そのままに、ブラウザはFormDataをそのまま自前サーバーへPOSTします。JSONへの変換は不要です。

src/components/ContactForm.tsx
async function handleSubmit(e: SubmitEvent) {
  e.preventDefault();
  const form = e.currentTarget as HTMLFormElement;
  const body = new FormData(form); // <input type="file"> の値も自動的に含まれる

  const res = await fetch('/api/forms/contact-with-attachment', {
    method: 'POST',
    body, // Content-Type は指定しない。ブラウザがboundary付きmultipartを自動設定する
  });

  if (!res.ok) {
    const { message } = await res.json();
    setErrorMessage(message);
    return;
  }
  const { id, submitted_at } = await res.json();
  setSubmitted({ id, submitted_at });
}

自前サーバーでは request.formData() を読み、各項目を valuesに載せて cms.forms.submit() を呼びます。file項目の値は File(Blobを継承)のまま渡せば、SDKが自動的にmultipart/form-dataのリクエストを組み立てます。

src/pages/api/forms/[key].ts
import type { APIRoute } from 'astro';
import { cms } from '../../../lib/cms';

export const prerender = false;

export const POST: APIRoute = async ({ params, request }) => {
  const formData = await request.formData();

  const values: Record<string, string | Blob> = {};
  for (const key of formData.keys()) {
    values[key] = formData.get(key) as string | Blob; // type=file の項目は File(Blobを継承)
  }

  const result = await cms.forms.submit(params.key!, { values });
  return new Response(JSON.stringify(result), { status: 201 });
};

3. 拡張子・サイズを事前チェック

送信前にブラウザ側で拡張子・サイズを検査すると、無駄なアップロード待ちを避けられます。inputのaccept属性はUI上の絞り込みに過ぎないため、選択後にもう一段チェックします。

src/lib/validateFile.ts
const ACCEPT_EXT = ['.pdf', '.jpg', '.jpeg', '.png'];
const MAX_SIZE = 5 * 1024 * 1024; // 5MB

function validateFile(file: File): string | null {
  const ext = `.${file.name.split('.').pop()?.toLowerCase() ?? ''}`;
  if (!ACCEPT_EXT.includes(ext)) {
    return `対応していない形式です(${ACCEPT_EXT.join(' / ')})`;
  }
  if (file.size > MAX_SIZE) {
    return 'ファイルサイズは5MB以下にしてください';
  }
  return null;
}

4. 送信エラーの扱い

  • 422(サイズ超過・非対応形式・不正なファイル):isValidationError — CMS側のファイル検証(サイズ上限・許可形式・内容)はいずれも422で返る。detailsの項目別メッセージをファイル欄の下に表示する。
  • 413(リクエストサイズ上限):自前サーバーやホスティング基盤側のリクエストサイズ上限で、CMSに届く前に返ることがある。SDKのエラーにはならないため、中継サーバーのHTTPステータスで判定する。
  • リトライ不可:同じファイルを再送信しても結果は変わらないため、自動リトライはせず別のファイルの選択を促す。
最終判定はサーバー側:許可拡張子・サイズ上限のクライアント側チェックはUX向上のためのものです。実際の検証はCMS側で必ず行われるため、フォーム設定の上限値とクライアント側の値は揃えておいてください。
FEEDBACK

このページはお役に立ちましたか?

改善要望・不具合・ドキュメントの分かりにくい点など、お気軽にお寄せください。いただいた内容は今後の改善に役立てます。

返信をご希望の場合のみご記入ください。

スパム対策(レートリミット・ハニーポット・送信速度チェック)はサーバ側で自動適用されます。