Uplode Image to shopify with remix app template

Topic summary

A developer is encountering an error when trying to upload product images using a Shopify Remix app template. Products are being created successfully, but images fail to process with the error message “Media failed to process because the image could not be processed.”

Code Context:

  • Using Shopify’s staged uploads mutation (stagedUploadsCreate)
  • Implementing file upload via DropZone component from Polaris
  • Preparing files with metadata (filename, mimeType, resource type, fileSize)
  • Valid image types defined: GIF, JPEG, PNG

Key Issue:
The provided code snippet appears corrupted or improperly formatted in critical sections, particularly around the GraphQL mutation parameters and file processing logic. This formatting issue makes it difficult to identify the exact technical problem causing the upload failure.

Status:
The question remains unanswered with no solutions or troubleshooting suggestions provided yet. The developer is seeking code review to identify what’s preventing successful image uploads to Shopify products.

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

Hii can you please see my code and say what I am doing wrong hear the product is creating but the image is not uploading and shows Image: Media failed to process because the image could not be processed

import React, { useCallback, useEffect, useState } from "react";
import { json } from "@remix-run/node";
import { useActionData, useNavigation, useSubmit } from "@remix-run/react";
import {
  Page,
  Layout,
  Card,
  Button,
  BlockStack,
  Box,
  InlineStack,
  DropZone,
  LegacyStack,
  Thumbnail,
  Text,
} from "@shopify/polaris";
import { authenticate } from "../shopify.server";

export const loader = async ({ request }) => {
  await authenticate.admin(request);

  return null;
};

export const action = async ({ request }) => {
  const { admin } = await authenticate.admin(request);
  const color = ["Red", "Orange", "Yellow", "Green"][
    Math.floor(Math.random() * 4)
  ];
  const requestBody = await request.text();
  console.log(
    "start:----------------------------------------------------------------------------------",
  );

  const formData = new URLSearchParams(requestBody);
  const name = formData.get("filename");
  const type = formData.get("filetype");
  const size = formData.get("filesize");
  const files = [
    {
      name: name,
      type: type,
      size: size,
    },
  ];
  console.log("File Details:", files);
  const prepareFiles = (files) =>
    files.map((file) => ({
      filename: file.name,
      mimeType: file.type,
      resource: file.type.includes("image") ? "IMAGE" : "FILE",
      fileSize: file.size.toString(),
      httpMethod: "POST",
    }));

  const preparedFiles = prepareFiles(files);
  console.log("Prepared Files for Upload:", preparedFiles);

  const uploadFileResponse = await admin.graphql(
    `#graphql
    mutation stagedUploadsCreate($input: [StagedUploadInput!]!) {
      stagedUploadsCreate(input: $input) {
        stagedTargets {
          resourceUrl
          url
          parameters {
            name
            value
          }
        }
        userErrors {
          field
          message
        }
      }
    }
  `,
    { variables: { input: preparedFiles } },
  );

  console.log(
    "Upload File Response:",
    JSON.stringify(uploadFileResponse, null, 2),
  );
  const uplodeFileJson = await uploadFileResponse.json();
  if (uplodeFileJson.data.stagedUploadsCreate?.userErrors?.length) {
    console.log(
      "Upload Errors:",
      uplodeFileJson.data.stagedUploadsCreate.userErrors,
    );
  }
  const resourceurl =
    uplodeFileJson.data.stagedUploadsCreate?.stagedTargets[0]?.resourceUrl;

  const productResponse = await admin.graphql(
    `#graphql
      mutation populateProduct($input: ProductInput!) {
        productCreate(input: $input) {
          product {
            id
            title
            handle
            status
          }
        }
      }`,
    {
      variables: {
        input: {
          title: `${color} Snowboard`,
        },
      },
    },
  );
  console.log(
    "Product Creation Response:",
    JSON.stringify(productResponse, null, 2),
  );
  const productResponseJson = await productResponse.json();

  const productId = productResponseJson.data?.productCreate?.product?.id;

  // Add image to the product
  const imageResponse = await admin.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: "Image",
          mediaContentType: "IMAGE",
          originalSource: resourceurl,
        },
        productId: productId,
      },
    },
  );
  console.log("Image Response:", JSON.stringify(imageResponse, null, 2));
  const imageResponseJson = await imageResponse.json();
  console.log(
    "end:----------------------------------------------------------------------------------",
  );

  return json({
    product: {
      id: productId,
      imageResponseJson: imageResponseJson,
    },
  });
};

const Index = () => {
  const nav = useNavigation();
  const actionData = useActionData();
  const submit = useSubmit();
  const isLoading =
    ["loading", "submitting"].includes(nav.state) && nav.formMethod === "POST";
  const productId = actionData?.product?.id.replace(
    "gid://shopify/Product/",
    "",
  );

  useEffect(() => {
    if (productId) {
      shopify.toast.show("Product created");
    }
  }, [productId]);
  const [files, setFiles] = useState([]);

  const handleDropZoneDrop = useCallback(
    async (_dropFiles, acceptedFiles, _rejectedFiles) => {
      if (acceptedFiles.length) {
        setFiles(acceptedFiles);
      }
    },
    [],
  );
  const validImageTypes = ["image/gif", "image/jpeg", "image/png"];

  const fileUpload = !files.length && <DropZone.FileUpload />;
  const uploadedFiles = files.length > 0 && (
    <div style={{ padding: "0" }}>
      <LegacyStack vertical>
        {files.map((file, index) => (
          <LegacyStack alignment="center" key={index}>
            <Thumbnail
              size="small"
              alt={file.name}
              source={
                validImageTypes.includes(file.type)
                  ? window.URL.createObjectURL(file)
                  : ""
              }
            />
            <div>
              {file.name}{" "}
              <Text variant="bodySm" as="p">
                {file.size} bytes
              </Text>
            </div>
          </LegacyStack>
        ))}
      </LegacyStack>
    </div>
  );
  console.log(files);
  const generateProduct = () => {
    const filename = files[0]?.name;
    const filetype = files[0]?.type;
    const filesize = files[0]?.size;
    submit({ filename, filetype, filesize }, { replace: true, method: "POST" });
  };

  return (
    <Page>
      <BlockStack gap="500">
        <Layout>
          <Layout.Section>
            <Card>
              <InlineStack gap="300">
                <DropZone onDrop={handleDropZoneDrop}>
                  {uploadedFiles}
                  {fileUpload}
                </DropZone>
                <Button loading={isLoading} onClick={generateProduct}>
                  Generate a product
                </Button>
                {actionData?.product && (
                  <Button
                    url={`shopify:admin/products/${productId}`}
                    target="_blank"
                    variant="plain"
                  >
                    View product
                  </Button>
                )}
              </InlineStack>
              {actionData?.imageUserErrors && (
                <Box>
                  <p>Errors occurred while adding image:</p>
                  <ul>
                    {actionData.imageUserErrors.map((error, index) => (
                      <li key={index}>
                        <strong>{error.field}: </strong>
                        {error.message}
                      </li>
                    ))}
                  </ul>
                </Box>
              )}
            </Card>
          </Layout.Section>
        </Layout>
      </BlockStack>
    </Page>
  );
};

export default Index;