Skip to content

Instantly share code, notes, and snippets.

@alexkubica
Last active December 13, 2023 15:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexkubica/6625de2a6ef704340c4c8060f60bfd5c to your computer and use it in GitHub Desktop.
Save alexkubica/6625de2a6ef704340c4c8060f60bfd5c to your computer and use it in GitHub Desktop.
Mass create products with images on Shopify
import { createAdminApiClient } from '@shopify/admin-api-client';
import { readFile, statSync } from 'node:fs';
import * as https from 'https';
import * as path from 'path';
import axios from 'axios';
import * as FormData from 'form-data';
import { createTypeReferenceDirectiveResolutionCache } from 'typescript';
// Go to Settings->Apps and sales channels->Develop apps->Create an app
// and enable those scopes:
// write_products, read_products, write_files, read_files
const ACCESS_TOKEN = 'XXX';
const STORE_DOMAIN = 'your-shop.myshopify.com'
const client = createAdminApiClient({
storeDomain: STORE_DOMAIN,
apiVersion: '2023-10',
accessToken: ACCESS_TOKEN,
});
async function createFile(stagedData, file, fileSize) {
// Save the target info.
const target = stagedData.stagedUploadsCreate.stagedTargets[0];
const params = target.parameters; // Parameters contain all the sensitive info we'll need to interact with the aws bucket.
const url = target.url; // This is the url you'll use to post data to aws. It's a generic s3 url that when combined with the params sends your data to the right place.
const resourceUrl = target.resourceUrl; // This is the specific url that will contain your image data after you've uploaded the file to the aws staged target.
/*------------------------
Post to temp target.
---
A temp target is a url hosted on Shopify's AWS servers.
------------------------*/
// Generate a form, add the necessary params and append the file.
// Must use the FormData library to create form data via the server.
const form = new FormData();
// Add each of the params we received from Shopify to the form. this will ensure our ajax request has the proper permissions and s3 location data.
params.forEach(({ name, value }) => {
form.append(name, value);
});
// Add the file to the form.
form.append("file", file);
// Post the file data to shopify's aws s3 bucket. After posting, we'll be able to use the resource url to create the file in Shopify.
const res = await axios.post(url, form, {
headers: {
// kubica check this
...form.getHeaders(), // Pass the headers generated by FormData library. It'll contain content-type: multipart/form-data. It's necessary to specify this when posting to aws.
// "Content-Length": fileSize + 5000, // AWS requires content length to be included in the headers. This may not be automatically passed so you'll need to specify. And ... add 5000 to ensure the upload works. Or else there will be an error saying the data isn't formatted properly.
},
});
/*------------------------
Create the file.
Now that the file is prepared and accessible on the staged target, use the resource url from aws to create the file.
------------------------*/
// Query
const operation = `mutation fileCreate($files: [FileCreateInput!]!) {
fileCreate(files: $files) {
files {
alt
}
userErrors {
field
message
}
}
}`;
const variables = {
files: {
alt: "alt-tag",
contentType: "IMAGE",
originalSource: resourceUrl, // Pass the resource url we generated above as the original source. Shopify will do the work of parsing that url and adding it to files.
},
};
const { data, errors, extensions } = await client.request(operation, {
variables
});
}
async function uploadImage(imagePath: string) {
const operation = `
mutation stagedUploadsCreate($input: [StagedUploadInput!]!) {
stagedUploadsCreate(input: $input) {
stagedTargets {
parameters {
name
value
}
resourceUrl
url
}
userErrors {
field
message
}
}
}
`;
const variables = {
"input": [
{
"filename": imagePath,
httpMethod: "POST",
"mimeType": "image/png",
"resource": "FILE"
}
]
};
const { data, errors, extensions } = await client.request(operation, {
variables
});
const file = await (new Promise((resolve, reject) => {
readFile(imagePath, (err, buffer) => {
if (err) reject(err); else resolve(buffer);
});
}));
const fileSize = statSync(imagePath).size;
await createFile(data, file, fileSize);
return data.stagedUploadsCreate.stagedTargets[0].resourceUrl;;
}
async function createProduct(productName, stagedUrl: string) {
const operation = `
mutation productCreate($input: ProductInput!, $media: [CreateMediaInput!]) {
productCreate(input: $input, media: $media) {
product {
id
}
userErrors {
field
message
}
}
}
`;
const variables = {
"input": {
// "bodyHtml": "",
"claimOwnership": {
"bundles": true
},
"collectionsToJoin": [
// your collection id, get it from collection's page's url
"gid://shopify/Collection/436366180638"
],
// "collectionsToLeave": [
// ""
// ],
// "customProductType": "",
// "descriptionHtml": "",
// "giftCard": true,
// "giftCardTemplateSuffix": "",
// "handle": "",
// "id": "",
// "images": [
// {
// "altText": "",
// "id": "",
// "src": ""
// }
// ],
// "metafields": [
// {
// "description": "",
// "id": "",
// "key": "",
// "namespace": "",
// "type": "",
// "value": ""
// }
// ],
// "options": [
// ""
// ],
// "privateMetafields": [
// {
// "key": "",
// "namespace": "",
// "owner": "",
// "valueInput": {
// "value": "",
// "valueType": ""
// }
// }
// ],
// "productCategory": {
// "productTaxonomyNodeId": ""
// },
// "productPublications": [
// {
// "channelHandle": "",
// "channelId": "",
// "publicationId": "",
// "publishDate": ""
// }
// ],
// "productType": "",
// "publications": [
// {
// "channelHandle": "",
// "channelId": "",
// "publicationId": "",
// "publishDate": ""
// }
// ],
// "publishDate": "",
// "publishOn": "",
// "published": true,
// "publishedAt": "",
// "redirectNewHandle": true,
// "requiresSellingPlan": true,
// "seo": {
// "description": "",
// "title": ""
// },
// "standardizedProductType": {
// "productTaxonomyNodeId": ""
// },
// "status": "",
// "tags": [
// ""
// ],
// "templateSuffix": "",
"title": productName,
// "variants": [
// {
// "barcode": "",
// "compareAtPrice": "",
// "fulfillmentServiceId": "",
// "harmonizedSystemCode": "",
// "id": "",
// "imageId": "",
// "imageSrc": "",
// "inventoryItem": {
// "cost": "",
// "tracked": true
// },
// "inventoryManagement": "",
// "inventoryPolicy": "",
// "inventoryQuantities": [
// {
// "availableQuantity": 1,
// "locationId": ""
// }
// ],
// "mediaId": "",
// "mediaSrc": [
// ""
// ],
// "metafields": [
// {
// "description": "",
// "id": "",
// "key": "",
// "namespace": "",
// "type": "",
// "value": ""
// }
// ],
// "options": [
// ""
// ],
// "position": 1,
// "price": "",
// "privateMetafields": [
// {
// "key": "",
// "namespace": "",
// "owner": "",
// "valueInput": {
// "value": "",
// "valueType": ""
// }
// }
// ],
// "productId": "",
// "requiresComponents": true,
// "requiresShipping": true,
// "sku": "",
// "taxCode": "",
// "taxable": true,
// "title": "",
// "weight": 1.1,
// "weightUnit": ""
// }
// ],
// "vendor": ""
},
"media": [
{
"alt": "",
"mediaContentType": "IMAGE",
// kubica get from graphql response
"originalSource": stagedUrl
}
]
};
const { data, errors, extensions } = await client.request(operation, {
variables
});
}
async function getAllProducts() {
const query = `query {
products(first: 150, reverse: true) {
edges {
node {
id
title
handle
resourcePublicationOnCurrentPublication {
publication {
name
id
}
publishDate
isPublished
}
}
}
}
}`
const { data, errors, extensions } = await client.request(query);
return data;
};
(async function () {
for (let i = 1; i <= 100; i++) {
try {
// assuming you have images 1.png to 100.png in the same folder
console.log(`upload image #${i} start`);
const stagedUrl = await uploadImage(`${i}.png`);
console.log(`upload image #${i} done`);
try {
console.log(`create product #${i} start`);
await createProduct(`${i}`, stagedUrl);
console.log(`create product #${i} end`);
} catch (e) {
console.log(`failed to create product #{i}`, e)
continue;
}
} catch (e) {
console.log(`failed to create product #{i}`, e)
}
}
})()
{
"name": "upload-to-shopify",
"version": "1.0.0",
"description": "",
"main": "main.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@shopify/admin-api-client": "^0.1.0",
"@shopify/shopify-api": "^8.1.1",
"axios": "^1.6.2",
"form-data": "^4.0.0"
},
"devDependencies": {
"@types/form-data": "^2.5.0",
"typescript": "^5.3.3"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment