Skip to content

# File uploads

Handling files are no different from other parameters.

    from ninja import NinjaAPI, File
    from ninja.files import UploadedFile

    @api.post("/upload")
    def upload(request, file: File[UploadedFile]):
        data = file.read()
        return {'name': file.name, 'len': len(data)}

`UploadedFile` is an alias to Django's UploadFile and has all the methods and attributes to access the uploaded file:

- read()
- multiple_chunks(chunk_size=None)
- chunks(chunk_size=None)
- name
- size
- content_type
- content_type_extra
- charset
- etc.

## Uploading array of files

To upload several files at the same time, just declare a `List` of `UploadedFile`:

    from typing import List
    from ninja import NinjaAPI, File
    from ninja.files import UploadedFile

    @api.post("/upload-many")
    def upload_many(request, files: File[List[UploadedFile]]):
        return [f.name for f in files]

## Uploading files with extra fields

Note: The HTTP protocol does not allow you to send files in `application/json` format by default (unless you encode it somehow to JSON on client side)

To send files along with some extra attributes, you need to send bodies with `multipart/form-data` encoding. You can do it by simply marking fields with `Form`:

    from ninja import NinjaAPI, Schema, UploadedFile, Form, File
    from datetime import date

    api = NinjaAPI()

    class UserDetails(Schema):
        first_name: str
        last_name: str
        birthdate: date

    @api.post('/users')
    def create_user(request, details: Form[UserDetails], file: File[UploadedFile]):
        return [details.dict(), file.name]

Note: in this case all fields should be send as form fields

You can as well send payload in single field as JSON - just remove the Form mark from:

    @api.post('/users')
    def create_user(request, details: UserDetails, file: File[UploadedFile]):
        return [details.dict(), file.name]

this will expect from the client side to send data as `multipart/form-data with 2 fields:

- details: JSON as string
- file: file

### List of files with extra info

    @api.post('/users')
    def create_user(request, details: Form[UserDetails], files: File[list[UploadedFile]]):
        return [details.dict(), [f.name for f in files]]

### Optional file input

If you would like the file input to be optional, all that you have to do is to pass `None` to the `File` type, like so:

    @api.post('/users')
    def create_user(request, details: Form[UserDetails], avatar: File[UploadedFile] = None):
        user = add_user_to_database(details)
        if avatar is not None:
            set_user_avatar(user)

## Handling request.FILES in PUT/PATCH Requests

Problem

    @api.put("/upload") # !!!!
    def upload(request, file: File[UploadedFile]):
       ...

For some historical reasons Django’s `request.FILES` is populated only for POST requests by default. When using HTTP PUT or PATCH methods with file uploads (e.g., multipart/form-data), request.FILES will not contain uploaded files. This is a known Django behavior, not specific to Django Ninja.

As a result, views expecting files in PUT or PATCH requests may not behave correctly, since request.FILES will be empty.

Solution

Django Ninja provides a built-in middleware to automatically fix this behavior: `ninja.compatibility.files.fix_request_files_middleware`

This middleware will manually parse multipart/form-data for PUT and PATCH requests and populate request.FILES, making file uploads work as expected across all HTTP methods.

Usage

To enable the middleware, add the following to your Django settings:

    MIDDLEWARE = [
        # ... your existing middleware ...
        "ninja.compatibility.files.fix_request_files_middleware",
    ]

Auto-detection

When Django Ninja detects a PUT or PATCH etc methods with multipart/form-data and expected FILES - it will throw an error message suggesting you install the compatibility middleware:

Note: This middleware does not interfere with normal POST behavior or any other methods.