File Upload๐Ÿ”—

In this section, we'll cover the everything necessary for adding support for file uploads to your GraphQL schema using the GraphQL multipart request specification.

Setup๐Ÿ”—

Undine supports file uploads, but they disabled by default due to security reasons. Specifically, since file uploads are sent using a multipart/form-data request, they may be sent without a CORS preflight request if the browser determines the requests meets the criteria for a "simple request".

Therefore, you should make sure CSRF protection is enabled on the GraphQL endpoint if you want to use file uploads. This can be done by making sure that

  1. The GraphQL view is decorated with @csrf_protect, OR
  2. CsrfViewMiddleware exists in your MIDDLEWARE setting (and the GraphQL view is not decorated with @csrf_exempt)
1
2
3
4
5
MIDDLEWARE = [
    # ...
    "django.middleware.csrf.CsrfViewMiddleware",
    # ...
]

Then you can enable file uploads by adding the following to your settings:

1
2
3
UNDINE = {
    "FILE_UPLOAD_ENABLED": True,
}

Uploading files๐Ÿ”—

Undine has two Scalars for uploading files: File and Image. They correspond to Django's FileField and ImageField respectively. The File scalar if for general files while the Image scalar validates that the file is an image file.

Like Django's ImageField, using the Image scalar requires the Pillow library to be installed. You can install it together with Undine using pip install undine[image].

Now, let's suppose you have the following Model.

1
2
3
4
5
6
7
from django.db import models


class Task(models.Model):
    name = models.CharField(max_length=255)

    image = models.ImageField()

If you add a create mutation to the GraphQL schema like this

from undine import Entrypoint, MutationType, QueryType, RootType

from .models import Task


class TaskType(QueryType[Task], auto=True): ...


class TaskCreateMutation(MutationType[Task], auto=True): ...


class Mutation(RootType):
    create_task = Entrypoint(TaskCreateMutation)

...the TaskCreateMutation mutation InputObjectType will look like this:

1
2
3
4
input TaskCreateMutation {
  name: String!
  image: Image!
}

Now the client can send a request that conforms to the GraphQL multipart request specification, and Undine's GraphQL view will parse the request and slot the files into the correct locations in the input data.

Example request
POST /graphql/ HTTP/1.1
Content-Type: multipart/form-data; boundary=--BoUnDaRyStRiNg
Content-Length: 490

--BoUnDaRyStRiNg
Content-Disposition: form-data; name="operations"

{"query": "mutation($input: TaskCreateMutation!) { createTask(input: $input) { pk } }", "variables": {"input": {"name": "Task", "image": null}}}
--BoUnDaRyStRiNg
Content-Disposition: form-data; name="map"

{"0": ["variables.input.image"]}
--BoUnDaRyStRiNg
Content-Disposition: form-data; name="0"; filename="image.png"

\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDATx\x01c```\x00\x00\x00\x04\x00\x01\xe4\x94\x84\x06\x00\x00\x00\x00IEND\xaeB`\x82
--BoUnDaRyStRiNg--

See the Implementations section in the GraphQL multipart request specification for client side libraries that support file uploads.