Laravel's validator has dedicated rules for file uploads that check more than just "is this field present" — file type, size, and (for images) dimensions can all be validated declaratively.
Basic file validation
$request->validate([
'document' => ['required', 'file', 'mimes:pdf,docx', 'max:5120'],
]);
max:5120 is in kilobytes (5120 KB = 5 MB) for file rules — a common mistake is assuming it's bytes.
Image-specific rules
$request->validate([
'avatar' => ['required', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048', 'dimensions:min_width=200,min_height=200'],
]);
The image rule is a shorthand that already checks for jpg/jpeg/png/bmp/gif/svg/webp — combining it with an explicit mimes: list narrows that down further if you want to exclude something like SVG (which can contain embedded scripts and is worth excluding from user-uploaded "images" unless you specifically need it).
mimes vs mimetypes — a real difference
mimes: checks the file's extension against a list of known extensions mapped to MIME types. mimetypes: instead checks the file's actual detected MIME type directly, independent of its extension. mimes: is more common and usually sufficient, but if you need to guard against a file renamed to a misleading extension, mimetypes: is the stricter check.
Why extension validation alone is not a security boundary
Validating mimes:jpg,png confirms the file's extension and Laravel's MIME sniffing agree it looks like an image — it does not guarantee the file's actual binary content is safe to process or that it can't contain something malicious disguised with valid image headers. If uploaded files are ever processed by another tool (an image library, a PDF renderer), that tool's own vulnerabilities are a separate concern validation rules don't cover — this is a real defense-in-depth topic outside what a validation rule can fully solve on its own.
Storing the validated file
$path = $request->file('avatar')->store('avatars', 'public');