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 reasosns 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.