GraphQL

File uploads

Create a direct-upload target with createFileUpload, PUT the bytes to S3, then pass signedId into a later mutation.

Files are not posted to /graphql as multipart. You create an Active Storage blob, upload bytes to the returned URL, then attach the blob's signedId on a subsequent mutation.

1. Create the upload

createFileUpload is an organization mutation.

mutation CreateFileUpload(
  $organizationId: ID!
  $input: CreateFileUploadInput!
) {
  organization(id: $organizationId) {
    createFileUpload(input: $input) {
      uploadUrl
      uploadHeaders
      signedId
      errors
    }
  }
}

Input:

FieldTypeDescription
fileNameString!Original file name, including extension
byteSizeInt!Size in bytes
checksumString!Base64-encoded MD5 of the file body (Active Storage checksum)
contentTypeStringMIME type, for example application/pdf

Checksum in Ruby is Digest::MD5.base64digest(file_body). In Node:

import { createHash } from 'node:crypto';

const checksum = createHash('md5').update(bytes).digest('base64');

2. PUT the file

uploadUrl is a pre-signed object-storage URL. uploadHeaders is a JSON object of headers you must send with the PUT. Include every header the payload returns; the signature depends on them.

PUT <uploadUrl>
<each key/value from uploadHeaders>
Content-Length: <byteSize>

<raw file bytes>

A successful PUT is 2xx. Do not send a GraphQL token to the storage URL.

3. Attach signedId

Later mutations accept the blob through signed-id fields, for example signedMediaAttachmentFileIds on comments or scoped attachment mutations such as createProjectScopedAttachment / createOrganizationScopedAttachment.

Keep the signedId secret. It is a capability token for that blob.

Scoped attachments

The public schema exposes scoped attachment mutations, not legacy per-record attachment mutations. Use the scoped create/delete fields on OrganizationMutations and ProjectMutations when attaching files to records that support them. Internal names such as createBidAttachment are not part of the public API and return a GraphQL error if you call them.