Full process for uploading files to the Files API

Topic summary

Core Issue Addressed:
A comprehensive guide for uploading files to Shopify’s Files API using Node.js, filling gaps in official documentation.

Complete Upload Flow:

  1. Download/prepare the file locally
  2. Create a staged target using stagedUploadsCreate mutation
  3. Upload file to the staged target (AWS S3 or Google Cloud Storage)
  4. Create the file in Shopify using fileCreate mutation with the resource URL

Key Technical Details:

  • Must use FormData library to properly format multipart/form-data requests
  • Include all AWS/Google parameters exactly as received from Shopify
  • Add Content-Length header (+5000 buffer) for AWS uploads, but exclude it for Google Cloud uploads
  • File size limit: Original code works for files under 2MB; for larger files, append filename to FormData: form.append("file", file, "filename.extension")

Platform Evolution:
Shopify migrated from AWS S3 to Google Cloud Storage for staged uploads. Check the returned URL to determine which headers to include.

Common Issues Resolved:

  • “Invalid AWS4-HMAC-SHA256” errors: caused by incorrect parameter formatting
  • Processing errors in Shopify dashboard: ensure resource argument matches content type (IMAGE vs FILE)
  • TypeScript/FormData compatibility: use proper type definitions for getHeaders()

Alternative Implementations:
Community members shared Ruby on Rails and PowerShell versions. Some developers bypass staged uploads entirely by hosting files on their own S3 buckets and passing URLs directly to productCreateMedia.

Status: Ongoing discussion with active troubleshooting for edge cases like 3D model uploads and buffer handling.

Summarized with AI on November 7. AI used: claude-sonnet-4-5-20250929.

I used the productCreateMedia in the manner given below, but receiving this error

“message”:"Invalid Model 3d

would be great if you could suggest a solution.

const fileCreateResponse = await admin!.graphql(
      `#graphql
      mutation productCreateMedia($media: [CreateMediaInput!]!, $productId: ID!) {
        productCreateMedia(media: $media, productId: $productId) {
          media {
            alt
            mediaContentType
            status
          }
          mediaUserErrors {
            field
            message
          }
          product {
            id
            title
          }
        }
    
      }`,
      {
        variables: {
          media:[{
            alt: "3d models",
            mediaContentType: "MODEL_3D",
            originalSource: "https://d18bonzukonoby.cloudfront.net/testshopify/xyz.usdz",
          }] ,
          productId: "gid://shopify/Product/723xxxxxxxx",
        },
      },
    );

    console.log(fileCreateResponse, "fileCreateResponse");

    const productData = await fileCreateResponse.json();

    return json({
      data: productData.data,
    });
  } catch (error: any) {
    console.error("Error:", error);
    return json({ error: error.message }, { status: 500 });
  }
};