ファイル添付を扱う
ファイル添付は「問い合わせフォーム連携」の応用です。type: 'file'
の項目はブラウザから直接バイナリを送る必要があるため、JSONではなく multipart/form-data
で扱います。SDKはFile・Blobを渡すだけで自動的にmultipartへ切り替わるので、自前サーバー側で特別なエンコード処理は不要です。
1. file 項目を見分ける
fields のうち type === 'file'
の項目だけ <input type="file"> を生成します。requiredはそのまま必須属性に渡せます。現在のSDKでは1つのfile項目につき1ファイル(Blob)を送る仕様なので、複数ファイルを受け付けたい場合はフォーム側にfile型の項目を複数用意してください(例:添付ファイル1、添付ファイル2)。
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への変換は不要です。
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のリクエストを組み立てます。
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上の絞り込みに過ぎないため、選択後にもう一段チェックします。
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ステータスで判定する。
- リトライ不可:同じファイルを再送信しても結果は変わらないため、自動リトライはせず別のファイルの選択を促す。
このページはお役に立ちましたか?
改善要望・不具合・ドキュメントの分かりにくい点など、お気軽にお寄せください。いただいた内容は今後の改善に役立てます。
お寄せいただきありがとうございます。今後の改善に役立てます。