@KyleG-Shopify Thank you for your attention to this matter 
Here are the steps which I am taking:
- Create the staged upload
mutation stagedUploadsCreate($input: [StagedUploadInput!]!) {
stagedUploadsCreate(input: $input) {
stagedTargets {
url
resourceUrl
parameters {
name
value
}
}
}
}
with input:
{
"input": [
{
"filename": "video-test.mp4",
"mimeType": "video/mp4",
"fileSize": "6110491",
"resource": "VIDEO",
"httpMethod": "POST"
}
]
}
I’ve confirmed the file size here is accurate:
➜ ~ stat -f %z ~/Downloads/video-test.mp4
6110491
This returns the following payload:
{
"data": {
"stagedUploadsCreate": {
"stagedTargets": [
{
"url": "https://shopify-video-production-core-originals.storage.googleapis.com",
"resourceUrl": "https://shopify-video-production-core-originals.storage.googleapis.com?external_video_id=49753950",
"parameters": [
{
"name": "GoogleAccessId",
"value": "video-production@video-production-225115.iam.gserviceaccount.com"
},
{
"name": "key",
"value": "c/o/v/cc3f187e8d42445f83c80e304ce76235.mp4"
},
{
"name": "policy",
"value": "eyJjb25kaXRpb25zIjpbWy..."
},
{
"name": "signature",
"value": "myIjP3t9LuVtQ7Un+R3HfVZjJ7kG/mbd4LviUE++2Fw..."
}
]
}
]
}
}
}
with x-request-id: 0f12cbc5-eb18-4b8b-b8d0-b3bcf0d420f2-1750432491
- Create the file with the
fileCreate
mutation, using the resourceUrl
from the stagedTarget
mutation fileCreate($files: [FileCreateInput!]!) {
fileCreate(files: $files) {
files {
id
fileErrors {
details
}
fileStatus
}
}
}
With input:
{
"files": [
{
"alt": null,
"contentType": "VIDEO",
"originalSource": "https://shopify-video-production-core-originals.storage.googleapis.com?external_video_id=49753950"
}
]
}
(Note here, if you provide the filename
parameter in the files
input, the request will fail and return files: []
, but that’s not my current issue)
This returns the following payload:
{
"data": {
"fileCreate": {
"files": [
{
"id": "gid://shopify/Video/33339025162402",
"fileErrors": [],
"fileStatus": "UPLOADED"
}
]
}
}
}
with x-request-id: 8b0fb991-9386-439d-8cdf-8e50d1a8ab4c-1750432225
- Upload the file to the staged upload endpoint with cURL
curl -v \
-F "GoogleAccessId=video-production@video-production-225115.iam.gserviceaccount.com" \
-F "key=c/o/v/cc3f187e8d42445f83c80e304ce76235.mp4" \
-F "policy=eyJjb25kaXRpb25zIjpbWyJlc..." \
-F "signature=myIjP3t9LuVtQ7Un+..." \
-F "file=@/Users/my-username/Downloads/video-test.mp4" \
"https://shopify-video-production-core-originals.storage.googleapis.com"
Which returns
* Trying 172.217.165.27:443...
* Connected to shopify-video-production-core-originals.storage.googleapis.com (172.217.165.27) port 443 (#0)
* ALPN: offers h2
* ALPN: offers http/1.1
* CAfile: /etc/ssl/cert.pem
* CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN: server accepted h2
* Server certificate:
* subject: CN=*.storage.googleapis.com
* start date: Jun 2 08:36:24 2025 GMT
* expire date: Aug 25 08:36:23 2025 GMT
* subjectAltName: host "shopify-video-production-core-originals.storage.googleapis.com" matched cert's "*.storage.googleapis.com"
* issuer: C=US; O=Google Trust Services; CN=WR2
* SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* h2h3 [:method: POST]
* h2h3 [:path: /]
* h2h3 [:scheme: https]
* h2h3 [:authority: shopify-video-production-core-originals.storage.googleapis.com]
* h2h3 [user-agent: curl/7.86.0]
* h2h3 [accept: */*]
* h2h3 [content-length: 6111819]
* h2h3 [content-type: multipart/form-data; boundary=------------------------73bb7c0ac999999f]
* Using Stream ID: 1 (easy handle 0x12880c600)
> POST / HTTP/2
> Host: shopify-video-production-core-originals.storage.googleapis.com
> user-agent: curl/7.86.0
> accept: */*
> content-length: 6111819
> content-type: multipart/form-data; boundary=------------------------73bb7c0ac999999f
>
* We are completely uploaded and fine
< HTTP/2 204
< x-guploader-uploadid: ABgVH8-4Mu2LkpZU6xZZtfuAK4hujInzuSHs-hy0_8jugfahu4cWZ1f86JiYUE2iwEqo16_M
< vary: Origin
< date: Fri, 20 Jun 2025 15:21:06 GMT
< server: UploadServer
< alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
<
* Connection #0 to host shopify-video-production-core-originals.storage.googleapis.com left intact
I suspect that the issue might be the order of steps here. According to the documentation, it seems that you can call fileCreate
with the staged upload URL, then separately you can upload the file. This approach is convenient for me because it means that I can perform the upload step asynchronously.
As I was typing this, I tried to perform these steps with #3 and #2 swapped (upload first, create file second) and it seemed to work properly… Is this the expected behaviour?
This exact set of steps works properly for GenericFile
type metafields. Uploading a PDF in this order works properly. Seems not to work for Video
and Model3d
Eric