<input type="file">で画像や特定形式のみをアップロードさせたいとき、accept属性を使ってファイル選択を制限しようとするのは一般的な実装です。
<input type="file" accept=".jpg,.jpeg,.png">しかし、実際にフォームを動かしてみると「指定していないはずのPDFやテキストファイルが選べてしまう」「acceptを書いたのにすり抜ける」といった問題に直面することがあります。
これはHTMLのバグではなく、accept属性が「検証(バリデーション)」ではなく「ファイル選択UIのヒント」として定義されているためです。
この記事では、acceptで対象外ファイルが選べてしまう理由から、正しい属性の書き方、JavaScript(File.typeや拡張子)によるフロントエンドでのチェック方法、そしてサーバー側検証との役割分担までを解説します。
accept属性はファイル形式を検証するものではない
accept属性の目的は、「ユーザーがファイル選択ダイアログを開いた際、どのファイル形式を選んでほしいかをブラウザやOSに伝えること」です。
MDNのドキュメントでも、acceptは選択されたファイルを検証する機能ではなく、適切なファイル形式へとユーザーを誘導するための選択補助(ヒント)であると明記されています。
accept属性:ファイル選択ダイアログにおける「UI上の選択補助・フィルター」- バリデーション:選択後(JavaScript)や送信後(サーバー側)に行う「ファイル形式の検証」
この2つを明確に分けて考えることが、実装トラブルを防ぐ第一歩になります。
acceptには拡張子・MIMEタイプ・ワイルドカードを指定できる
accept属性には、カンマ区切りで以下の形式を指定できます。
拡張子で指定
ドット(.)から始まる拡張子を指定します。
<input type="file" accept=".jpg,.jpeg,.png">MIMEタイプで指定
標準的なMIMEタイプを指定します。
<input type="file" accept="image/jpeg,image/png">画像全般を指定(ワイルドカード)
image/*、audio/*、video/*のように指定すると、該当するメディアタイプ全般を対象にできます。
<input type="file" accept="image/*">特定形式だけなら拡張子とMIMEタイプを両方指定する
WHATWGのHTML仕様では、特定のフォーマットを求める場合、対応するMIMEタイプと拡張子の両方を併記することが推奨されています。
<input
type="file"
accept=".jpg,.jpeg,.png,image/jpeg,image/png"
>ブラウザやOSによって拡張子ベースでフィルタリングするものとMIMEタイプベースで認識するものがあるため、両方を記述しておくことで、ファイル選択ダイアログで意図したフィルタが適用されやすくなります。
※ ただし、両方を併記した場合でも「セキュリティ上完全に制限できる」わけではない点には注意してください。
なぜacceptを指定しても対象外ファイルを選べるの?
accept属性を指定しているにもかかわらず対象外ファイルが選べる主な理由は、実際のファイル選択ダイアログ(ファイルピッカー)の制御がOSやブラウザの実装に委ねられているためです。
- ダイアログ側での制限解除:環境によっては、ファイル選択画面で「すべてのファイル」などへ切り替えることで、
acceptの条件に合わないファイルも選択できる場合があります。 - 独自のドラッグ&ドロップUI:
DataTransfer.filesなどでファイルを直接受け取る実装では、accept属性だけでファイル形式を検証することはできません。ドロップされたファイルについてもJavaScriptやサーバー側で別途チェックする必要があります。 - モバイル端末のUI差:スマートフォンなどの環境によっては、ファイル管理アプリ側の仕様で意図したグレーアウトや非表示が効かないケースがあります。
したがって、「対象外ファイルが選べる=HTMLの記述が壊れている」とは限りません。
JavaScriptで選択後にファイル形式を確認する
acceptで対象外ファイルが選択された場合に、その場でユーザーへエラーを伝えるには、changeイベント内でJavaScriptによるチェックを行います。
const fileInput = document.querySelector('#file');
fileInput.addEventListener('change', (event) => {
const file = event.target.files[0];
if (!file) return;
// 許可するMIMEタイプのリスト
const allowedTypes = [
'image/jpeg',
'image/png'
];
if (!allowedTypes.includes(file.type)) {
console.warn('対応していないファイル形式です:', file.type);
alert('JPGまたはPNG形式の画像を選択してください。');
// 選択状態をリセットする
event.target.value = '';
return;
}
console.log('利用可能なファイルです:', file.name);
});ファイル選択後にvalueをリセットする理由や、同じファイルを選び直したときにchangeが発火しないケースについては、こちらの記事で詳しく解説しています。
file.typeだけで完全に判定できるわけではない
ここで知っておくべき重要な注意点として、File.typeはブラウザがファイルの中身(バイナリ)を厳密に解析して判定しているとは限らないという点があります。
ブラウザは通常、ファイルのバイト列そのものを解析してFile.typeを確定しているわけではありません。拡張子などから推測されるため、実際のファイル内容と異なるMIMEタイプになることがあります。
拡張子が存在しないファイルや未知のファイル形式では、file.typeが空文字("")になることがあります。
そのため、file.type === 'image/png'だからといって、ファイル内容までPNGであることを保証できるわけではありません。file.typeのチェックも「フロントエンドでの早期フィードバック」の域を出ないことを理解しておく必要があります。
拡張子も確認した方がいい?
file.typeが空文字になるケースへの対策や、ユーザーへの親切なエラー通知として、ファイル名から拡張子を抽出して併せてチェックするのは実務的に有効です。
const fileName = file.name;
const extension = fileName.split('.').pop().toLowerCase();
const allowedExtensions = ['jpg', 'jpeg', 'png'];
if (!allowedExtensions.includes(extension)) {
console.warn('対応していない拡張子です:', extension);
}ただし拡張子も単なる文字列に過ぎず、簡単に変更できるため、これもUX向上のための補助手段となります。
本当に制限したい場合はサーバー側でも検証する
アップロードされたファイルを保存・処理するWebサイトやWebアプリケーションでは、フロントエンド(HTML/JS)のチェックを過信せず、必ずサーバー側でもファイル形式・サイズの検証を行います。
- フロントエンド(
acceptやJS):操作性の向上。ユーザーが間違ったファイルを選んだ瞬間にエラーを出し、無駄なアップロード通信を防ぐ。 - サーバー側(バックエンド):実際に受け付けるファイルを最終的に検証する。許可する拡張子、ファイルサイズ、検出したファイル形式やシグネチャなどを組み合わせ、用途に応じたチェックを行う。
安全なWebサイトを運用するためには、この多層的なチェック設計が不可欠です。
acceptが効かないときの確認点
意図した通りにダイアログで絞り込みが効かない場合、以下の記述ミスがないか確認してください。
1. 拡張子の先頭に「.」が付いているか
拡張子で指定する場合はドットが必要です。
- ✕
accept="png,jpg" - ◯
accept=".png,.jpg"
2. 複数指定をカンマ(,)で区切っているか
スペース区切りではなく、半角カンマで区切ります。
- ✕
accept=".png .jpg" - ◯
accept=".png,.jpg"
3. MIMEタイプの指定が正しいか
タイポや存在しないMIMEタイプを指定していないか確認します(例: image/jpgではなくimage/jpeg)。
4. image/* を指定してしまっていないか
accept="image/*"を指定すると、GIFやWebP、SVGなど、JPEG・PNG以外の画像形式も対象になり得ます。特定の形式に絞りたい場合はワイルドカードを使わず、個別指定を行ってください。
5. acceptを完全なバリデーションだと思っていないか
前述の通り、OSのファイル選択画面で「すべてのファイル」を選ばれた場合やすり抜けは仕様上防げません。JavaScriptでのエラーハンドリングが実装されているか確認してください。
accept・JavaScript・サーバー側チェックの役割の違い
ファイル受け入れ処理における各レイヤーの役割を整理すると以下のようになります。
| レイヤー | 実装手段 | 主な役割 |
|---|---|---|
| UI補助 | HTML(accept属性) | ファイル選択ダイアログで選んでほしい形式を案内・誘導する |
| フロント検証 | JavaScript(file.type / 拡張子) | 選択直後にエラーを伝え、誤送信や無駄な通信を防ぐ |
| 最終検証 | サーバー側処理 | 実際に受け付けるファイル形式を拡張子・サイズ・シグネチャ等から検証する |
まとめ
<input type="file">のaccept属性は、対象外ファイルを100%遮断する防壁ではなく、ユーザーを正しいファイル形式へ誘導するためのUIヒントです。
- UI補助として:拡張子とMIMEタイプを併記(例:
.png,image/png)してダイアログを適切に案内する - UX改善として:JavaScriptの
changeイベント内でfile.typeや拡張子をチェックし、早期にエラーを通知する - セキュリティとして:ファイルアップロードを受け付ける場合は、必ずサーバー側で多層的な検証を行う
役割の違いを正しく理解し、HTML・JavaScript・サーバーサイドを適切に組み合わせて実装しましょう。