Storing files on S3

Beside operations for your data, there are mutations related to S3 compatible storage. Contember itself doesn't store your files, but it can help you with signing URLs for uploading (or reading) those files.

S3 server#

In Contember development stack, there is bundled Minio server, which is S3 compatible storage server, therefore you don't have to setup anything on localhost as it is running out of box. For production see our guide

Signing upload URL#

Use GraphQL generateUploadUrl mutation to generate a presigned S3 upload URL. This is a secure way how to access your S3 bucket without exposing credentials to a client application, because only Contember server knows a secret key, using which it can sign one time URL.

Execute following mutation in your application:

mutation {
signedUpload: generateUploadUrl(contentType: "image/jpeg") {
headers {

The url and other fields can be used to construct a request, which uploads a file directly to S3 storage from an application:

await fetch(
method: signedUpload.method,
headers: Object.fromEntries({ key, value }) => [key, value])),
body: content,

S3 diagram

generateUploadUrl mutation has few optional arguments

  • expiration (default 3600) - URL expiration in seconds
  • acl (default corresponds S3 provider, usually it is "PUBLIC_READ") - object ACL
    • PUBLIC_READ: anyone who knows public URL is allowed to read an object
    • PRIVATE: object is not accessible using its public URL
    • NONE: explicit object ACL is not set, leaving ACL to bucket policies
    • note that some S3 providers does not support object level ACL, so this argument will not be available at all

Signing read url#

When you set an object ACL to PRIVATE, you can use this mutation to sign a read URL:

mutation {
generateReadUrl(objectKey: "images/5b934fe1-0e30-4761-9e00-d4bfabbddf34.png") {

If you store public URL instead of object key, don't worry. Contember will recognize it and parses an object key from it.

You can also optionally set an expiration of the URL (default 3600 seconds)

Configuring S3 ACL#

To use S3 functionality, each role in the ACL schema must have defined ACL rules. This is the most simple rule, which allows both operations on any key:

const adminRole = {
s3: {
'**': {
read: true,
upload: true,
// ... variables, stage, entities...

In a key you define a glob-like pattern for an S3 object key and in a value you define allowed operations (read and upload). We use picomatch library for a pattern matching.

Example patterns#

  • ** - matches anything
  • images/** - matches any object with an images key prefix
  • **.jpg - matches any object with a jpg extension

ACL evaluation#

Contember will try to find any rule for an object key, which allows given operation. This means that if you first define a specific rule for private/**, which does NOT allow an upload, and later you define a generic rule ** , which allows upload, then this rule will override previous one.

read option only affects generateReadUrl mutation and does not affect upload ACL (PUBLIC_READ, PRIVATE) nor public read URL in any way.