The file input is one line of markup and one of the easiest things in HTML to get almost right. Almost, because a form containing one needs an attribute on the form element itself, and leaving it off produces the worst kind of bug: the submission succeeds, the field is present, and the file is not. Nothing warns you. Here is the markup, the attribute, and an honest account of what the receiving end has to do.
The enctype belongs on the form, not the input
A form defaults to encoding its values as URL-encoded key and value pairs, which has no way to carry the bytes of a file. What it carries instead is the filename, which is why the request looks healthy and the attachment is missing. Adding enctype="multipart/form-data" to the form element switches the encoding to one that carries each field as its own part, file contents included. The method has to be post as well, because a GET has no body to put the parts in. Both attributes are on the form, not on the input, which is exactly why they get missed.
What accept and multiple actually do
accept takes a list of MIME types or extensions and filters what the file picker offers, so somebody uploading a CV is not scrolling through photographs. multiple lets one field take several files, which then arrive as several parts under the same field name. Both are usability improvements and neither is a control: a person can switch the picker to show everything, and a script posting directly to your URL never opens a picker at all. Every real limit on type and size lives on the other end.
Size is decided somewhere you have not looked yet
A phone photograph is comfortably tens of megabytes, and the first real upload is usually the one that finds your limit. There is no attribute that caps file size in the browser, so the cap comes from whatever is receiving the request, and often from something in between: a proxy, a serverless platform's request limit, a gateway timeout on a slow connection. Decide the number you will accept, enforce it where the upload lands, and say it on the page, because a failure at this layer usually shows up to the visitor as nothing happening.
Storing the file, not just receiving it
A file is not a value you can put in a row and forget. It has to be written somewhere durable, tied to the submission it arrived with, given a name that is not the one the visitor chose, and served back later without letting anybody walk the storage. Emailing it as an attachment is not storage, because filters strip attachments and size limits are lower than you think. Whatever receives your form needs a place to put bytes and a record that connects them to the rest of the submission.
Questions people ask about html file input
Why does the file arrive empty?
The form is missing enctype="multipart/form-data". Without it the browser sends the file's name as an ordinary form value and no contents at all, and because the request itself succeeds there is no error to notice.
Can I limit the file size in HTML?
Not with an attribute. You can check the size in JavaScript before submitting, which is worth doing for the message it lets you show, but the enforceable limit is wherever the upload lands, and often lower than you expect because of a proxy or platform limit in between.
Is accept enough to stop the wrong file type?
No. It filters the picker and nothing more. Check the type where the file lands, and check it from the bytes rather than the filename, because an extension is whatever somebody typed.