- Published on
Building a Shopify Remix App Tackling the Image Upload Conundrum
- Authors
- Name
- Entaice Braintrust
Building a Shopify Remix App: Tackling the Image Upload Conundrum
I remember the first time I built a Shopify app. It was like assembling IKEA furniture without the instructions—everything seemed right until you realized the table had five legs, and one of them was your coffee table. I was full of optimism, slightly overwhelmed, and certainly puzzled by every error message bravely blinking back at me. Creating a product on Shopify, I’ve come to realize, is like trying to grab candy from a candy crane machine—the prize is there for the taking, but only if you master the mechanics of the claw.
Today, we're going on an adventure through the labyrinth of a Shopify Remix app, where image uploads aren't behaving, and errors have us scratching our heads in unison. Picture this: a well-laid plan to let customers waltz into your store and create their dazzling products. All was going swimmingly until—halt! Image previews refused to play along.
Identifying The Sneaky Culprit
We need to start by looking under the hood of our Remix app’s code. It’s like being a digital mechanic, listening to the tiny engine ticks, searching for what’s going awry. We have a neat little onsubmit
function painting our form modal’s landscape. It's supposed to gather all the product details with the gusto of a kid collecting seashells—title, tags, and most importantly, images—and then toss them over to our endpoint like a well-trained dog fetching a Frisbee.
document.querySelector('.modal-form').onsubmit = async function (event) {
event.preventDefault()
const form = event.target
const formData = new FormData()
formData.append('productTitle', document.getElementById('productTitle').value)
formData.append('productTag', `email_${document.getElementById('productTag').value}`)
formData.append('image1', document.getElementById('image1').files[0])
if (document.getElementById('image2').files[0]) {
formData.append('image2', document.getElementById('image2').files[0])
}
if (document.getElementById('image3').files[0]) {
formData.append('image3', document.getElementById('image3').files[0])
}
try {
const response = await fetch('/apps/greetabl/template_manager', {
method: 'POST',
body: formData,
})
const result = await response.json()
if (result.success) {
alert('Product created successfully!')
} else {
alert(`Error: ${result.error}`)
}
} catch (error) {
console.error('Submission error:', error)
alert('An error occurred. Please try again.')
}
}
This code is our trusty courier—running errands, fetching ingredients—but it might not be properly equipped for the journey. Particularly, the images are proving elusive to grasp.
Rethinking Our Strategy: Stage One of Uploads
Imagine image uploads as a heist movie plot—we need to ensure all our pieces are in place and our steps are sleek and precise. Stage one is to acquire the elusive signed upload URLs from Shopify. These URLs are like personalized greeting cards saying, "Yes, you may upload your image here—only here."
To kick off this party, we use GraphQL mutations. To the uninitiated, it might sound like a fancy transformer move, but it’s actually quite elegant. In your code, start by preparing the 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',
}))
Then we get Shopify to play ball by using GraphQL to ask for those particular signed URLs:
async function uploadImagesToShopify(files) {
const preparedFiles = prepareFiles(files)
const uploadMutation = `
mutation stagedUploadsCreate($input: [StagedUploadInput!]!) {
stagedUploadsCreate(input: $input) {
stagedTargets {
url
resourceUrl
parameters {
name
value
}
}
userErrors {
field
message
}
}
}`
// Request those signed upload URLs from Shopify
const result = await fetch(
`https://${process.env.SHOPIFY_STORE_DOMAIN}/admin/api/2023-10/graphql.json`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': process.env.SHOPIFY_ADMIN_API_TOKEN,
},
body: JSON.stringify({ query: uploadMutation, variables: { input: preparedFiles } }),
}
)
const response = await result.json()
// Handle any errors while fetching URLs
if (response.errors) {
throw new Error(response.errors.map((err) => err.message).join(', '))
}
return response
}
Navigating Forward: File Upload Mission
We’ve just gained access to our potential upload targets. Now it’s time to actually upload our images—and this is where things can wobble if you're not careful. We construct FormData
to send our images to the URLs provided by Shopify, anchoring our efforts on precision.
And this part often feels like orchestrating a carefully crafted symphony; every instrument (parameter) must be in tune or chaos ensues:
files.forEach((file, index) => {
const url = response.data.stagedUploadsCreate.stagedTargets[index].url
const params = response.data.stagedUploadsCreate.stagedTargets[index].parameters
const formData = new FormData()
params.forEach((param) => {
formData.append(param.name, param.value)
})
formData.append('file', file)
const promise = fetch(url, {
method: 'POST',
body: formData,
})
promises.push(promise)
})
await Promise.all(promises)
The Grand Finale: Creating the Product
Once the uploads succeed, we’re left wondering why the product creation step isn’t panning out as smoothly as planned. Remember, when calling the GraphQL mutation to create a product, we must ensure every little detail is properly encapsulated—including image URLs previously obtained.
const input = {
title: productTitle,
tags: updatedTags,
images: uploadedImages.map((image) => ({ src: image.imageUrl })),
variants: [
{
price: '10.00',
},
],
}
const response = await fetch(
`https://${process.env.SHOPIFY_STORE_DOMAIN}/admin/api/2023-10/graphql.json`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': process.env.SHOPIFY_ADMIN_API_TOKEN,
},
body: JSON.stringify({ query, variables: { input } }),
}
)
const result = await response.json()
Make sure your images aren’t being left in the dust without URLs, for a product without pictures is akin to a theater without performances. If any error obstructs your path, it’s time to revisit your handling of uploaded image details and that they seamlessly integrate into your product creation query.
This journey may feel like weaving through a treacherous digital jungle, but each step is forging our pathway to success. Just imagine the triumphant sound of success - probably a bit like hitting the victory bell at a carnival game. Remember, patience and precision are your most trusted allies on this digital quest.