fileCreate flow with stagedUploadsCreate in GraphQL

fileCreate flow with stagedUploadsCreate in GraphQL

ninainventables
Visitor
1 0 0

I'm trying to upload various files(image, video, etc) with the fileCreate mutation after staging the upload with stagedUploadsCreate.

 

I saw https://community.shopify.com/c/shopify-apis-and-sdks/stageduploadscreate-graphql-api-creates-file-w... mentioned that the parameters need to be dynamically added, so I was wondering how I should do that with GraphQL. I am able to get stagedUploadsCreate to return a response with fileStatus of UPLOADED.

 

When I attempt the fileCreate mutation, I don't receive any errors within the response. I am only able to see that the file runs into a processing error when viewing files on my Shopify store in the dashboard.

 

Query for stagedUploadsCreate:

 
mutation ($input: [StagedUploadInput!]!) {
        stagedUploadsCreate(input: $input) {
          stagedTargets {
            parameters {
              name
              value
            }
            resourceUrl
            url
          }
          userErrors {
            field
            message
          }
        }
      }

input = { 
  filename: "image.jpg", 
  mimeType: "image/jpeg",
  httpMethod: "PUT", //I've also tried POST
  resource: IMAGE
}
 
Query for fileCreate:
mutation ($input: [FileCreateInput!]!) {
        fileCreate(files: $input) {
          files {
            alt
            createdAt
            fileErrors
            fileStatus
            ... on MediaImage {
              id
            }
            ... on Video {
              id
            }
            ... on GenericFile {
              id
              url
            }
          }
          userErrors {
            field
            message
          }
        }
      }


input = [{
   originalSource: image url from stagedUploadsCreate,
   contentType: "IMAGE"
}]
Replies 6 (6)

ShopifyDevSup
Shopify Staff
1453 238 511

Hey @ninainventables

Thanks for providing some excellent examples and references in your post, along with your questions. I did some digging, testing, and wanted to pass on a few insights. It appears from the post that you're using the correct mutations, `stagedUploadsCreate` and `fileCreate`, but there are a few additional steps that I wanted to highlight: 
 

  1. The response from a successful stagedUploadsCreate mutation - linked doc, includes an output of data, such as the url and parameters (name and value) for the staged target(s) it creates, these are required for the upload. 
     
  2. The `stagedTargets` object includes a `url` for the upload (note: this part of the process is decoupled from the Admin API endpoint)
     
  3. The media upload guide provides full examples, as well as insights on how to use these parameters formatted as headers or form inputs, depending on the media type and HTTP method (POST, PUT).
     
  4. The input for the `fileCreate` mutation will reflect the details returned from a successful file upload. Here is an example input from the fileCreate mutation - link docs.


I'd also recommend testing the full workflow with an API client, eg. Postman or Insomnia, and trying a few different valid media types. When testing on my end from the docs and guide shared above, there were no errors or issues through the process.

Hope this helps provide some clarity and resources to work from - Cheers! 
@awwdam  | Shopify Developer Support

Developer Support @ Shopify
- Was this reply helpful? Click Like to let us know!
- Was your question answered? Mark it as an Accepted Solution
- To learn more visit Shopify.dev or the Shopify Web Design and Development Blog

Rachid_98
Shopify Partner
12 1 0

@ShopifyDevSup  I'm having the same issue faced by @ninainventables I'd really appreciate it if you can help, I read somewhere that there needs to be a third step between the stagedUploadsCreate mutation and the fileCreate mutation, which is to prepare a form and commence uploading!
Here are the resources I checked:
Link1
Link2 
Link3 
I have tried it but it never works for me at all.

The staged response does indeed return a url and paameters but that's the last successful step that happens.

Here is what I'm trying to achieve, the client when using my Shopify app has a dropzone where he can upload images, I'm trying to save those images to the files in his store, I have tried @ninainventables 's approach but I keep getting the processor error when I check the files dashboard of the store.

Here is my client side code:
The following is supposed to communicate with the server the image file the client is uploading.

Here is the code in the server side:

 

app.post('/api/upload-endpoint', async (req, res) => {
  try {
    if (!req.files || !req.files.file) {
      return res.status(400).json({ error: 'No file uploaded' });
    }

    const uploadedFile = req.files.file;
    const formData = new FormData();
    // Define the stagedUploadsCreate mutation input
    const input = [
      {
        resource: 'IMAGE',
        httpMethod: 'POST',
        mimeType: uploadedFile.mimetype,
        filename: uploadedFile.name,
        fileSize: uploadedFile.size.toString(),
      },
    ];
  
    // Create a staged upload URL using stagedUploadsCreate mutation
    let stagedUploadResponse = await fetchUploadImageFile(res.locals.shopify.session, input);

    const stagedTarget = stagedUploadResponse.body.data.stagedUploadsCreate.stagedTargets[0];
//passing the parameters
    stagedTarget.parameters.forEach(({name, value}) =>
     {
      formData.append(name, value);
    });

//Passing the file as argument (should be last), I tried to pass only the file instead but that didn't change anything.
    formData.append('file', uploadedFile.data, {
      filename: uploadedFile.name,
      contentType: uploadedFile.mimetype,
    });

    // Now, we upload the file
    const fileCreateResponse = await fetch(stagedTarget.url, {
      method: 'POST',
      body: formData,
    });
  
      let myArrayOfFiles = new Array();
      const paramObj =
    {
        "alt": "Image Alt!",
        "originalSource": stagedTarget.resourceUrl,
        "contentType": "IMAGE",
    };
    myArrayOfFiles.push(paramObj);
   
 
    const myResultIs2 = await fetchUploadImageFileContinue(res.locals.shopify.session, myArrayOfFiles);


  } catch (error) {
    console.error('Error handling file upload:', error);
    res.status(500).json({ error: 'An error occurred' });
  }


});

 

 

 

const formData = new FormData();
  formData.append('file', filesArray.current[0]);

  const response = await fetch2('/api/upload-endpoint', {
    method: 'POST',
    body: formData,
  });

 

Here are the codes for the mutations:

 

 export default async function fetchUploadImageFile(
    session, myArr
  ) {
    const client = new shopify.api.clients.Graphql({ session });
    try {
  const myData = await client.query({
    data: {
      "query": `mutation stagedUploadsCreate($input: [StagedUploadInput!]!) {
        stagedUploadsCreate(input: $input) {
          stagedTargets {
            url
            resourceUrl
            parameters {
              name
              value
            }
          }
        }
      }`,
      "variables": 
      {
        "input": myArr
      },
    },
  });

  return myData;

    } catch (error) {
      if (error instanceof GraphqlQueryError) {
        throw new Error(
          `${error.message}\n${JSON.stringify(error.response, null, 2)}`
        );
      } else {
        throw error;
      }
    }

  }
  
 export default async function fetchUploadImageFileContinue(
    session, filesArray
  ) {
    const client = new shopify.api.clients.Graphql({ session });
    try {
  const myData = await client.query({
    data: {
      "query": `mutation fileCreate($files: [FileCreateInput!]!) {
        fileCreate(files: $files) {
          files {
            alt
            createdAt
          }
        }
      }`,
      "variables": {
        "files": filesArray
      },
    },
  });

  return myData;
     
    } catch (error) {
      if (error instanceof GraphqlQueryError) {
        throw new Error(
          `${error.message}\n${JSON.stringify(error.response, null, 2)}`
        );
      } else {
        throw error;
      }
    }

  }
  

 



ShopifyDevSup
Shopify Staff
1453 238 511

Hey @Rachid_98 - thanks for your patience on this. I can't say for certain why this is failing without a bit more info (for example an X-Request-ID from the Shopify response and a relative date/time stamp for the issue), but one thing that could potentially cause an issue like this is the content-type value that's being set in the parameter form for the API call where the product is uploaded after you create the staged upload path (if I'm reading your shared code correctly). 

I would confirm that the content-type is set to the correct value for the file being upload (for example: image/jpg, etc.) within the multi-part header params. There is a good breakdown on how this should be formatted here on shopify.dev.

If this doesn't help/resolve the issue, we're still happy to help out. I would recommend reaching out to our Partner Support team directly through the Support tool in your Partner Dashboard if you do encounter further issues. They can act as a liaison between you and some of our more technical teams to troubleshoot further. If you do reach out to Partner Support directly, I would share this thread with them and point to my message for extra context as well as include any relevant logging details that you can (the raw API calls you're making, any X-Request-IDs you receive from Shopify API response and their date/timestamps as well as the code you've already shared here). 

I can't guarantee exactly what the next steps would be, but it may help with getting some faster assistance to reach out to Partner Support directly. 

Hope this helps and hope to hear from you soon.

 

Developer Support @ Shopify
- Was this reply helpful? Click Like to let us know!
- Was your question answered? Mark it as an Accepted Solution
- To learn more visit Shopify.dev or the Shopify Web Design and Development Blog

Rachid_98
Shopify Partner
12 1 0

Hi @ShopifyDevSup Thanks for the reply, this has been an issue we're struggling with for a while.

Here is the 'X-Request-Id': [ '211ace7d-518f-438a-87d7-7fbb4146cbf0' ] and date:
Date: [ 'Wed, 23 Aug 2023 18:36:58 GMT' ],returned in the headers of the response from the stagedUploadsCreate mutation.
The content-type is 'image/jpeg' so it's correct, all parameters returned from the above mutation are correct. The issue I strongly believe comes from post request, perhaps something is missing and I don't know what is it but you mentioned that the code is correct.

Rachid_98_0-1692815487987.png

 

I already contacted the support but no luck so far in finding the issue, I'd be happy to schedule a quick call where I can show you the live demo, I already shared more info with the support, my ticket ID is 40969193. I'd be appreciative if you can help me solve it, I noticed that some other developers are running into the same issue too.

Rachid_98
Shopify Partner
12 1 0
@ShopifyDevSup  I found the issue, apparently this line was missing
import fetch from 'node-fetch';
Somehow this didn't throw any compilation errors because maybe it uses a fetch method used by another library. It's a weird behavior to not show error messages though.
Any idea how to scale this to work with multiple files instead of one and also returning the new url of the created files?
 
Thanks a lot again.

ShopifyDevSup
Shopify Staff
1453 238 511

Hey @Rachid_98 , thanks for following up. 

Since you're working with multiple files, using stagedUploadsCreate there isn't currently a workflow to upload multiple media files instead of one. 

One option to do this at scale though is if you have the files hosted URL (either the staged URL or hosted externally) then you can use bulk operations to create the products 

Here's an example of the variables
https://shopify.dev/docs/api/usage/bulk-operations/imports#create-a-jsonl-file-and-include-graphql-v... 

combined that with the 'Create a product with a new media' example near the bottom of this page for the ProductCreate mutation: https://shopify.dev/docs/api/admin-graphql/2023-07/mutations/productcreate#examples-Create_a_product...

Hope it helps! 

- Kyle

Developer Support @ Shopify
- Was this reply helpful? Click Like to let us know!
- Was your question answered? Mark it as an Accepted Solution
- To learn more visit Shopify.dev or the Shopify Web Design and Development Blog