_155 require("dotenv").config();
_155 const { WebflowClient } = require("webflow-api");
_155 const crypto = require("crypto");
_155 const axios = require("axios");
_155 const FormData = require("form-data");
_155 const webflow = new WebflowClient({
_155 accessToken: process.env.WEBFLOW_API_TOKEN,
_155 // Organize folders and assets
_155 const assetDetails = [
_155 folderName: "Universal Assets",
_155 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_155 folderName: "English Assets",
_155 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_155 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_155 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_155 folderName: "French Assets",
_155 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_155 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_155 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
_155 // Function to hash file data from URL
_155 async function getFileHashFromUrl(assetUrl) {
_155 // Create a promise to handle asynchronous hashing of the file data
_155 return new Promise(async (resolve, reject) => {
_155 const response = await axios.get(assetUrl, { responseType: "stream" });
_155 // Create a SHA-256 hash instance to generate the file hash
_155 const hash = crypto.createHash("sha256");
_155 // Update the hash with each chunk of data received from the stream
_155 response.data.on("data", (data) => hash.update(data));
_155 // Finalize the hash calculation and resolve the promise with the hash value
_155 response.data.on("end", () => resolve(hash.digest("hex")));
_155 response.data.on("error", reject);
_155 // Function to create an asset folder
_155 async function createAssetFolder(siteId, folderName) {
_155 // Check if the folder already exists
_155 const existingFolders = await webflow.assets.listFolders(siteId);
_155 const existingFolder = existingFolders.assetFolders.find(
_155 (folder) => folder.displayName === folderName
_155 if (existingFolder) {
_155 `Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_155 return existingFolder.id;
_155 // Create the folder if it does not exist
_155 console.log(`Creating folder: ${folderName}`);
_155 const response = await webflow.assets.createFolder(siteId, {
_155 displayName: folderName,
_155 console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_155 return response.id; // Return folder ID for further use
_155 `Error creating or retrieving asset folder '${folderName}':`,
_155 // Function to upload an asset to Webflow via S3
_155 async function uploadAsset(siteId, folderId, assetUrl) {
_155 // Generate the file hash for validation
_155 const fileHash = await getFileHashFromUrl(assetUrl);
_155 const fileName = assetUrl.split("/").pop();
_155 // Step 1: Initialize the upload
_155 const uploadInit = await webflow.assets.create(siteId, {
_155 parentFolder: folderId,
_155 fileName: fileName + `.jpeg`,
_155 const { uploadUrl, uploadDetails } = uploadInit;
_155 // Create form data for S3 upload
_155 const form = new FormData();
_155 // Append all required fields to the form
_155 form.append("acl", uploadDetails.acl);
_155 form.append("bucket", uploadDetails.bucket);
_155 form.append("X-Amz-Algorithm", uploadDetails.xAmzAlgorithm);
_155 form.append("X-Amz-Credential", uploadDetails.xAmzCredential);
_155 form.append("X-Amz-Date", uploadDetails.xAmzDate);
_155 form.append("key", uploadDetails.key);
_155 form.append("Policy", uploadDetails.policy);
_155 form.append("X-Amz-Signature", uploadDetails.xAmzSignature);
_155 form.append("success_action_status", uploadDetails.successActionStatus);
_155 form.append("Content-Type", uploadDetails.contentType);
_155 form.append("Cache-Control", uploadDetails.cacheControl);
_155 // Append the file to be uploaded
_155 const response = await axios.get(assetUrl, { responseType: "stream" });
_155 form.append("file", response.data, {
_155 contentType: uploadDetails.contentType,
_155 console.log(response);
_155 // Step 2: Upload to the provided S3 URL
_155 const uploadResponse = await axios.post(uploadUrl, form, {
_155 ...form.getHeaders(),
_155 if (uploadResponse.status === 201) {
_155 console.log(`Successfully uploaded ${fileName} to Webflow.`);
_155 `Failed to upload ${fileName}. Response status: ${uploadResponse.status}`
_155 console.error(`Error uploading asset from ${assetUrl}:`, error);
_155 // Main function to execute the upload
_155 const siteId = process.env.SITE_ID; // Replace with your actual site ID
_155 for (const { folderName, assets } of assetDetails) {
_155 const folderId = await createAssetFolder(siteId, folderName);
_155 for (const assetUrl of assets) {
_155 await uploadAsset(siteId, folderId, assetUrl);
Set up your development environment 1. Create a new Node.js project First, set up the basic Node.js environment for your project. Start by creating a new directory and initializing a Node.js project:
$ mkdir webflow-asset-uploader
$ cd webflow-asset-uploader
$ npm init -y
This will create a new directory called webflow-asset-uploader
and set up a package.json
file, which will manage the project's dependencies and configurations.
2. Create a new .env
file Next, create a .env
file to securely store environment variables like your API token. These values are crucial for authenticating requests to the Webflow API.
Open the .env
file in your editor and add the following variables:
_2 WEBFLOW_API_TOKEN=YOUR_WEBFLOW_API_TOKEN
Replace YOUR_WEBFLOW_API_TOKEN
and YOUR_SITE_ID
with your Webflow credentials.
3. Install necessary packages To build this asset upload script, you'll need several Node.js packages. Use npm to install these dependencies.
$ npm install dotenv webflow-api axios form-data crypto
dotenv : Loads environment variables from your .env
file.
webflow-api : The official Webflow API client for interacting with Webflow resources.
axios : A promise-based HTTP client for making requests to external APIs.
form-data : Helps you create form data for uploading files.
crypto : A built-in Node.js module for generating hashes, which will help verify file integrity during uploads.
Once installed, these packages will enable you to interact with the Webflow API and upload assets to your projects.
4. Create assets.js
Next, create a JavaScript file named assets.js
—this is where we’ll build out the code to handle asset uploads to Webflow.
5. Import packages Open the assets.js
file in your editor and import the packages you installed.
These imports will give you access to the tools you need for interacting with the Webflow API, making HTTP requests, and working with form data.
_5 require("dotenv").config();
_5 const { WebflowClient } = require("webflow-api");
_5 const crypto = require("crypto");
_5 const axios = require("axios");
_5 const FormData = require("form-data");
6. Initialize the Webflow client Initialize the Webflow client using the API token from your .env
file.
_10 require("dotenv").config();
_10 const { WebflowClient } = require("webflow-api");
_10 const crypto = require("crypto");
_10 const axios = require("axios");
_10 const FormData = require("form-data");
_10 // Initialize Webflow client
_10 const webflow = new WebflowClient({
_10 accessToken: process.env.WEBFLOW_API_TOKEN,
The Webflow client will allow you to interact with Webflow's resources, such as creating asset folders and uploading assets.
7. Define asset folders and files For this example, we'll define a set of folders and assets to upload. In practice, you might be pulling these from another location or database, but for this guide, we'll use a hardcoded list.
_34 require("dotenv").config();
_34 const { WebflowClient } = require("webflow-api");
_34 const crypto = require("crypto");
_34 const axios = require("axios");
_34 const FormData = require("form-data");
_34 // Initialize Webflow client
_34 const webflow = new WebflowClient({
_34 accessToken: process.env.WEBFLOW_API_TOKEN,
_34 // Organize folders and assets
_34 const assetDetails = [
_34 folderName: "Universal Assets",
_34 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_34 folderName: "English Assets",
_34 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_34 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_34 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_34 folderName: "French Assets",
_34 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_34 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_34 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
Create asset folders _77 require("dotenv").config();
_77 const { WebflowClient } = require("webflow-api");
_77 const crypto = require("crypto");
_77 const axios = require("axios");
_77 const FormData = require("form-data");
_77 // Initialize Webflow client
_77 const webflow = new WebflowClient({
_77 accessToken: process.env.WEBFLOW_API_TOKEN,
_77 // Organize folders and assets
_77 const assetDetails = [
_77 folderName: "Universal Assets",
_77 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_77 folderName: "English Assets",
_77 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_77 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_77 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_77 folderName: "French Assets",
_77 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_77 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_77 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
_77 // Function to create an asset folder
_77 async function createAssetFolder(siteId, folderName) {
_77 // Check if the folder already exists
_77 const existingFolders = await webflow.assets.listFolders(siteId);
_77 const existingFolder = existingFolders.assetFolders.find(
_77 (folder) => folder.displayName === folderName
_77 if (existingFolder) {
_77 `Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_77 return existingFolder.id;
_77 // Create the folder if it does not exist
_77 console.log(`Creating folder: ${folderName}`);
_77 const response = await webflow.assets.createFolder(siteId, {
_77 displayName: folderName,
_77 console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_77 return response.id; // Return folder ID for further use
_77 `Error creating or retrieving asset folder '${folderName}':`,
_77 // Main function to execute the folder creation and asset upload
_77 const siteId = process.env.SITE_ID;
_77 for (const { folderName, assets } of assetDetails) {
_77 const folderId = await createAssetFolder(siteId, folderName);
_77 for (const assetUrl of assets) {
_77 // TO ADD: Upload logic
Now that we have defined our folders and assets, the next step is to create asset folders on the Webflow site.
1. Create function createAssetFolder()
_77 require("dotenv").config();
_77 const { WebflowClient } = require("webflow-api");
_77 const crypto = require("crypto");
_77 const axios = require("axios");
_77 const FormData = require("form-data");
_77 // Initialize Webflow client
_77 const webflow = new WebflowClient({
_77 accessToken: process.env.WEBFLOW_API_TOKEN,
_77 // Organize folders and assets
_77 const assetDetails = [
_77 folderName: "Universal Assets",
_77 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_77 folderName: "English Assets",
_77 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_77 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_77 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_77 folderName: "French Assets",
_77 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_77 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_77 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
_77 // Function to create an asset folder
_77 async function createAssetFolder(siteId, folderName) {
_77 // Check if the folder already exists
_77 const existingFolders = await webflow.assets.listFolders(siteId);
_77 const existingFolder = existingFolders.assetFolders.find(
_77 (folder) => folder.displayName === folderName
_77 if (existingFolder) {
_77 `Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_77 return existingFolder.id;
_77 // Create the folder if it does not exist
_77 console.log(`Creating folder: ${folderName}`);
_77 const response = await webflow.assets.createFolder(siteId, {
_77 displayName: folderName,
_77 console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_77 return response.id; // Return folder ID for further use
_77 `Error creating or retrieving asset folder '${folderName}':`,
_77 // Main function to execute the folder creation and asset upload
_77 const siteId = process.env.SITE_ID;
_77 for (const { folderName, assets } of assetDetails) {
_77 const folderId = await createAssetFolder(siteId, folderName);
_77 for (const assetUrl of assets) {
_77 // TO ADD: Upload logic
This function, createAssetFolder()
, takes the site ID and folder name as arguments.
2. Check for existing asset folders _77 require("dotenv").config();
_77 const { WebflowClient } = require("webflow-api");
_77 const crypto = require("crypto");
_77 const axios = require("axios");
_77 const FormData = require("form-data");
_77 // Initialize Webflow client
_77 const webflow = new WebflowClient({
_77 accessToken: process.env.WEBFLOW_API_TOKEN,
_77 // Organize folders and assets
_77 const assetDetails = [
_77 folderName: "Universal Assets",
_77 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_77 folderName: "English Assets",
_77 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_77 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_77 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_77 folderName: "French Assets",
_77 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_77 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_77 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
_77 // Function to create an asset folder
_77 async function createAssetFolder(siteId, folderName) {
_77 // Check if the folder already exists
_77 const existingFolders = await webflow.assets.listFolders(siteId);
_77 const existingFolder = existingFolders.assetFolders.find(
_77 (folder) => folder.displayName === folderName
_77 if (existingFolder) {
_77 `Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_77 return existingFolder.id;
_77 // Create the folder if it does not exist
_77 console.log(`Creating folder: ${folderName}`);
_77 const response = await webflow.assets.createFolder(siteId, {
_77 displayName: folderName,
_77 console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_77 return response.id; // Return folder ID for further use
_77 `Error creating or retrieving asset folder '${folderName}':`,
_77 // Main function to execute the folder creation and asset upload
_77 const siteId = process.env.SITE_ID;
_77 for (const { folderName, assets } of assetDetails) {
_77 const folderId = await createAssetFolder(siteId, folderName);
_77 for (const assetUrl of assets) {
_77 // TO ADD: Upload logic
Webflow only allows unique names for asset folders, so the first step is to check if a folder with the specified name already exists. We do this using the List Asset Folders endpoint. If a folder with the given name is found, the function returns the folder's ID.
3. Create new asset folder if one doesn't exist _77 require("dotenv").config();
_77 const { WebflowClient } = require("webflow-api");
_77 const crypto = require("crypto");
_77 const axios = require("axios");
_77 const FormData = require("form-data");
_77 // Initialize Webflow client
_77 const webflow = new WebflowClient({
_77 accessToken: process.env.WEBFLOW_API_TOKEN,
_77 // Organize folders and assets
_77 const assetDetails = [
_77 folderName: "Universal Assets",
_77 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_77 folderName: "English Assets",
_77 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_77 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_77 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_77 folderName: "French Assets",
_77 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_77 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_77 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
_77 // Function to create an asset folder
_77 async function createAssetFolder(siteId, folderName) {
_77 // Check if the folder already exists
_77 const existingFolders = await webflow.assets.listFolders(siteId);
_77 const existingFolder = existingFolders.assetFolders.find(
_77 (folder) => folder.displayName === folderName
_77 if (existingFolder) {
_77 `Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_77 return existingFolder.id;
_77 // Create the folder if it does not exist
_77 console.log(`Creating folder: ${folderName}`);
_77 const response = await webflow.assets.createFolder(siteId, {
_77 displayName: folderName,
_77 console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_77 return response.id; // Return folder ID for further use
_77 `Error creating or retrieving asset folder '${folderName}':`,
_77 // Main function to execute the folder creation and asset upload
_77 const siteId = process.env.SITE_ID;
_77 for (const { folderName, assets } of assetDetails) {
_77 const folderId = await createAssetFolder(siteId, folderName);
_77 for (const assetUrl of assets) {
_77 // TO ADD: Upload logic
If a folder does not already exist, create one using the Create Asset Folder endpoint. Once successfully created, the function returns the ID of the newly created folder.
4. Create main function _77 require("dotenv").config();
_77 const { WebflowClient } = require("webflow-api");
_77 const crypto = require("crypto");
_77 const axios = require("axios");
_77 const FormData = require("form-data");
_77 // Initialize Webflow client
_77 const webflow = new WebflowClient({
_77 accessToken: process.env.WEBFLOW_API_TOKEN,
_77 // Organize folders and assets
_77 const assetDetails = [
_77 folderName: "Universal Assets",
_77 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_77 folderName: "English Assets",
_77 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_77 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_77 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_77 folderName: "French Assets",
_77 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_77 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_77 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
_77 // Function to create an asset folder
_77 async function createAssetFolder(siteId, folderName) {
_77 // Check if the folder already exists
_77 const existingFolders = await webflow.assets.listFolders(siteId);
_77 const existingFolder = existingFolders.assetFolders.find(
_77 (folder) => folder.displayName === folderName
_77 if (existingFolder) {
_77 `Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_77 return existingFolder.id;
_77 // Create the folder if it does not exist
_77 console.log(`Creating folder: ${folderName}`);
_77 const response = await webflow.assets.createFolder(siteId, {
_77 displayName: folderName,
_77 console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_77 return response.id; // Return folder ID for further use
_77 `Error creating or retrieving asset folder '${folderName}':`,
_77 // Main function to execute the folder creation and asset upload
_77 const siteId = process.env.SITE_ID;
_77 for (const { folderName, assets } of assetDetails) {
_77 const folderId = await createAssetFolder(siteId, folderName);
_77 for (const assetUrl of assets) {
_77 // TO ADD: Upload logic
To execute the createAssetFolder()
function, create an anonymous asynchronous function that iterates through the list of folders we defined earlier and calls createAssetFolder()
for each one. This ensures that all required folders are created.
Generate a file hash _102 require("dotenv").config();
_102 const { WebflowClient } = require("webflow-api");
_102 const crypto = require("crypto");
_102 const axios = require("axios");
_102 const FormData = require("form-data");
_102 // Initialize Webflow client
_102 const webflow = new WebflowClient({
_102 accessToken: process.env.WEBFLOW_API_TOKEN,
_102 // Organize folders and assets
_102 const assetDetails = [
_102 folderName: "Universal Assets",
_102 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_102 folderName: "English Assets",
_102 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_102 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_102 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_102 folderName: "French Assets",
_102 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_102 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_102 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
_102 // Function to create an asset folder
_102 async function createAssetFolder(siteId, folderName) {
_102 // Check if the folder already exists
_102 const existingFolders = await webflow.assets.listFolders(siteId);
_102 const existingFolder = existingFolders.assetFolders.find(
_102 (folder) => folder.displayName === folderName
_102 if (existingFolder) {
_102 `Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_102 return existingFolder.id;
_102 // Create the folder if it does not exist
_102 console.log(`Creating folder: ${folderName}`);
_102 const response = await webflow.assets.createFolder(siteId, {
_102 displayName: folderName,
_102 console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_102 return response.id; // Return folder ID for further use
_102 `Error creating or retrieving asset folder '${folderName}':`,
_102 // Function to hash file data from URL
_102 async function getFileHashFromUrl(assetUrl) {
_102 // Create a promise to handle asynchronous hashing of the file data
_102 return new Promise(async (resolve, reject) => {
_102 // Fetch the file as a stream
_102 const response = await axios.get(assetUrl, { responseType: "stream" });
_102 // Initialize SHA-256 hash
_102 const hash = crypto.createHash("sha256");
_102 // Update the hash with each chunk of data received from the stream
_102 response.data.on("data", (data) => hash.update(data));
_102 // Finalize the hash calculation and resolve the promise with the hash value
_102 response.data.on("end", () => resolve(hash.digest("hex")));
_102 response.data.on("error", reject);
_102 // Main function to execute the folder creation and asset upload
_102 const siteId = process.env.SITE_ID;
_102 for (const { folderName, assets } of assetDetails) {
_102 const folderId = await createAssetFolder(siteId, folderName);
_102 for (const assetUrl of assets) {
_102 // TO ADD: Upload logic
When uploading a file, Webflow requires you to include a cryptographic hash generated from the contents of that file.
This hash helps ensure data integrity and security, providing a unique "fingerprint" of the file that guarantees it hasn't been altered or tampered with during the upload process.
Before uploading an asset, we'll need to generate its hash using the SHA-256 algorithm.
1. Create getFileHashFromUrl
function _102 require("dotenv").config();
_102 const { WebflowClient } = require("webflow-api");
_102 const crypto = require("crypto");
_102 const axios = require("axios");
_102 const FormData = require("form-data");
_102 // Initialize Webflow client
_102 const webflow = new WebflowClient({
_102 accessToken: process.env.WEBFLOW_API_TOKEN,
_102 // Organize folders and assets
_102 const assetDetails = [
_102 folderName: "Universal Assets",
_102 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_102 folderName: "English Assets",
_102 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_102 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_102 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_102 folderName: "French Assets",
_102 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_102 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_102 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
_102 // Function to create an asset folder
_102 async function createAssetFolder(siteId, folderName) {
_102 // Check if the folder already exists
_102 const existingFolders = await webflow.assets.listFolders(siteId);
_102 const existingFolder = existingFolders.assetFolders.find(
_102 (folder) => folder.displayName === folderName
_102 if (existingFolder) {
_102 `Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_102 return existingFolder.id;
_102 // Create the folder if it does not exist
_102 console.log(`Creating folder: ${folderName}`);
_102 const response = await webflow.assets.createFolder(siteId, {
_102 displayName: folderName,
_102 console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_102 return response.id; // Return folder ID for further use
_102 `Error creating or retrieving asset folder '${folderName}':`,
_102 // Function to hash file data from URL
_102 async function getFileHashFromUrl(assetUrl) {
_102 // Create a promise to handle asynchronous hashing of the file data
_102 return new Promise(async (resolve, reject) => {
_102 // Fetch the file as a stream
_102 const response = await axios.get(assetUrl, { responseType: "stream" });
_102 // Initialize SHA-256 hash
_102 const hash = crypto.createHash("sha256");
_102 // Update the hash with each chunk of data received from the stream
_102 response.data.on("data", (data) => hash.update(data));
_102 // Finalize the hash calculation and resolve the promise with the hash value
_102 response.data.on("end", () => resolve(hash.digest("hex")));
_102 response.data.on("error", reject);
_102 // Main function to execute the folder creation and asset upload
_102 const siteId = process.env.SITE_ID;
_102 for (const { folderName, assets } of assetDetails) {
_102 const folderId = await createAssetFolder(siteId, folderName);
_102 for (const assetUrl of assets) {
_102 // TO ADD: Upload logic
To generate the file hash, create a function named getFileHashFromUrl()
. This function will:
Use axios to retrieve the file from the provided URL with a GET request.
Use crypto to calculate the hash with a stream-based approach, which efficiently handles larger files.
2. Create the Promise _102 require("dotenv").config();
_102 const { WebflowClient } = require("webflow-api");
_102 const crypto = require("crypto");
_102 const axios = require("axios");
_102 const FormData = require("form-data");
_102 // Initialize Webflow client
_102 const webflow = new WebflowClient({
_102 accessToken: process.env.WEBFLOW_API_TOKEN,
_102 // Organize folders and assets
_102 const assetDetails = [
_102 folderName: "Universal Assets",
_102 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_102 folderName: "English Assets",
_102 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_102 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_102 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_102 folderName: "French Assets",
_102 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_102 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_102 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
_102 // Function to create an asset folder
_102 async function createAssetFolder(siteId, folderName) {
_102 // Check if the folder already exists
_102 const existingFolders = await webflow.assets.listFolders(siteId);
_102 const existingFolder = existingFolders.assetFolders.find(
_102 (folder) => folder.displayName === folderName
_102 if (existingFolder) {
_102 `Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_102 return existingFolder.id;
_102 // Create the folder if it does not exist
_102 console.log(`Creating folder: ${folderName}`);
_102 const response = await webflow.assets.createFolder(siteId, {
_102 displayName: folderName,
_102 console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_102 return response.id; // Return folder ID for further use
_102 `Error creating or retrieving asset folder '${folderName}':`,
_102 // Function to hash file data from URL
_102 async function getFileHashFromUrl(assetUrl) {
_102 // Create a promise to handle asynchronous hashing of the file data
_102 return new Promise(async (resolve, reject) => {
_102 // Fetch the file as a stream
_102 const response = await axios.get(assetUrl, { responseType: "stream" });
_102 // Initialize SHA-256 hash
_102 const hash = crypto.createHash("sha256");
_102 // Update the hash with each chunk of data received from the stream
_102 response.data.on("data", (data) => hash.update(data));
_102 // Finalize the hash calculation and resolve the promise with the hash value
_102 response.data.on("end", () => resolve(hash.digest("hex")));
_102 response.data.on("error", reject);
_102 // Main function to execute the folder creation and asset upload
_102 const siteId = process.env.SITE_ID;
_102 for (const { folderName, assets } of assetDetails) {
_102 const folderId = await createAssetFolder(siteId, folderName);
_102 for (const assetUrl of assets) {
_102 // TO ADD: Upload logic
Wrap the function logic in a new Promise
to handle the asynchronous hashing. Use resolve
to return the hash value once calculated, and reject
to handle any errors that occur.
3. Fetch the file as a stream _102 require("dotenv").config();
_102 const { WebflowClient } = require("webflow-api");
_102 const crypto = require("crypto");
_102 const axios = require("axios");
_102 const FormData = require("form-data");
_102 // Initialize Webflow client
_102 const webflow = new WebflowClient({
_102 accessToken: process.env.WEBFLOW_API_TOKEN,
_102 // Organize folders and assets
_102 const assetDetails = [
_102 folderName: "Universal Assets",
_102 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_102 folderName: "English Assets",
_102 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_102 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_102 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_102 folderName: "French Assets",
_102 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_102 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_102 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
_102 // Function to create an asset folder
_102 async function createAssetFolder(siteId, folderName) {
_102 // Check if the folder already exists
_102 const existingFolders = await webflow.assets.listFolders(siteId);
_102 const existingFolder = existingFolders.assetFolders.find(
_102 (folder) => folder.displayName === folderName
_102 if (existingFolder) {
_102 `Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_102 return existingFolder.id;
_102 // Create the folder if it does not exist
_102 console.log(`Creating folder: ${folderName}`);
_102 const response = await webflow.assets.createFolder(siteId, {
_102 displayName: folderName,
_102 console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_102 return response.id; // Return folder ID for further use
_102 `Error creating or retrieving asset folder '${folderName}':`,
_102 // Function to hash file data from URL
_102 async function getFileHashFromUrl(assetUrl) {
_102 // Create a promise to handle asynchronous hashing of the file data
_102 return new Promise(async (resolve, reject) => {
_102 // Fetch the file as a stream
_102 const response = await axios.get(assetUrl, { responseType: "stream" });
_102 // Initialize SHA-256 hash
_102 const hash = crypto.createHash("sha256");
_102 // Update the hash with each chunk of data received from the stream
_102 response.data.on("data", (data) => hash.update(data));
_102 // Finalize the hash calculation and resolve the promise with the hash value
_102 response.data.on("end", () => resolve(hash.digest("hex")));
_102 response.data.on("error", reject);
_102 // Main function to execute the folder creation and asset upload
_102 const siteId = process.env.SITE_ID;
_102 for (const { folderName, assets } of assetDetails) {
_102 const folderId = await createAssetFolder(siteId, folderName);
_102 for (const assetUrl of assets) {
_102 // TO ADD: Upload logic
Use axios to download the file as a stream from the provided URL. Streaming is particularly helpful for large files since it avoids loading the entire file into memory.
4. Hash the file _102 require("dotenv").config();
_102 const { WebflowClient } = require("webflow-api");
_102 const crypto = require("crypto");
_102 const axios = require("axios");
_102 const FormData = require("form-data");
_102 // Initialize Webflow client
_102 const webflow = new WebflowClient({
_102 accessToken: process.env.WEBFLOW_API_TOKEN,
_102 // Organize folders and assets
_102 const assetDetails = [
_102 folderName: "Universal Assets",
_102 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_102 folderName: "English Assets",
_102 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_102 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_102 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_102 folderName: "French Assets",
_102 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_102 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_102 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
_102 // Function to create an asset folder
_102 async function createAssetFolder(siteId, folderName) {
_102 // Check if the folder already exists
_102 const existingFolders = await webflow.assets.listFolders(siteId);
_102 const existingFolder = existingFolders.assetFolders.find(
_102 (folder) => folder.displayName === folderName
_102 if (existingFolder) {
_102 `Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_102 return existingFolder.id;
_102 // Create the folder if it does not exist
_102 console.log(`Creating folder: ${folderName}`);
_102 const response = await webflow.assets.createFolder(siteId, {
_102 displayName: folderName,
_102 console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_102 return response.id; // Return folder ID for further use
_102 `Error creating or retrieving asset folder '${folderName}':`,
_102 // Function to hash file data from URL
_102 async function getFileHashFromUrl(assetUrl) {
_102 // Create a promise to handle asynchronous hashing of the file data
_102 return new Promise(async (resolve, reject) => {
_102 // Fetch the file as a stream
_102 const response = await axios.get(assetUrl, { responseType: "stream" });
_102 // Initialize SHA-256 hash
_102 const hash = crypto.createHash("sha256");
_102 // Update the hash with each chunk of data received from the stream
_102 response.data.on("data", (data) => hash.update(data));
_102 // Finalize the hash calculation and resolve the promise with the hash value
_102 response.data.on("end", () => resolve(hash.digest("hex")));
_102 response.data.on("error", reject);
_102 // Main function to execute the folder creation and asset upload
_102 const siteId = process.env.SITE_ID;
_102 for (const { folderName, assets } of assetDetails) {
_102 const folderId = await createAssetFolder(siteId, folderName);
_102 for (const assetUrl of assets) {
_102 // TO ADD: Upload logic
Create a SHA-256 hash instance to compute the hash of the file. Update the hash with each data chunk as it is streamed. Once the entire file has been streamed, resolve the promise with the final hash value.
Upload a new asset _160 require("dotenv").config();
_160 const { WebflowClient } = require("webflow-api");
_160 const crypto = require("crypto");
_160 const axios = require("axios");
_160 const FormData = require("form-data");
_160 // Initialize Webflow client
_160 const webflow = new WebflowClient({
_160 accessToken: process.env.WEBFLOW_API_TOKEN,
_160 // Organize folders and assets
_160 const assetDetails = [
_160 folderName: "Universal Assets",
_160 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_160 folderName: "English Assets",
_160 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_160 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_160 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_160 folderName: "French Assets",
_160 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_160 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_160 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
_160 // Function to create an asset folder
_160 async function createAssetFolder(siteId, folderName) {
_160 // Check if the folder already exists
_160 const existingFolders = await webflow.assets.listFolders(siteId);
_160 const existingFolder = existingFolders.assetFolders.find(
_160 (folder) => folder.displayName === folderName
_160 if (existingFolder) {
_160 `Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_160 return existingFolder.id;
_160 // Create the folder if it does not exist
_160 console.log(`Creating folder: ${folderName}`);
_160 const response = await webflow.assets.createFolder(siteId, {
_160 displayName: folderName,
_160 console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_160 return response.id; // Return folder ID for further use
_160 `Error creating or retrieving asset folder '${folderName}':`,
_160 // Function to hash file data from URL
_160 async function getFileHashFromUrl(assetUrl) {
_160 // Create a promise to handle asynchronous hashing of the file data
_160 return new Promise(async (resolve, reject) => {
_160 // Fetch the file as a stream
_160 const response = await axios.get(assetUrl, { responseType: "stream" });
_160 // Initialize SHA-256 hash
_160 const hash = crypto.createHash("sha256");
_160 // Update the hash with each chunk of data received from the stream
_160 response.data.on("data", (data) => hash.update(data));
_160 // Finalize the hash calculation and resolve the promise with the hash value
_160 response.data.on("end", () => resolve(hash.digest("hex")));
_160 response.data.on("error", reject);
_160 // Function to upload an asset to Webflow via S3
_160 async function uploadAsset(siteId, folderId, assetUrl) {
_160 // Generate the file hash for validation
_160 const fileHash = await getFileHashFromUrl(assetUrl);
_160 const fileName = assetUrl.split("/").pop();
_160 // Step 1: Initialize the upload
_160 const uploadInit = await webflow.assets.create(siteId, {
_160 parentFolder: folderId,
_160 fileName: fileName + `.jpeg`,
_160 const { uploadUrl, uploadDetails } = uploadInit;
_160 // Create form data for S3 upload
_160 const form = new FormData();
_160 // Append all required fields to the form
_160 form.append("acl", uploadDetails.acl);
_160 form.append("bucket", uploadDetails.bucket);
_160 form.append("X-Amz-Algorithm", uploadDetails.xAmzAlgorithm);
_160 form.append("X-Amz-Credential", uploadDetails.xAmzCredential);
_160 form.append("X-Amz-Date", uploadDetails.xAmzDate);
_160 form.append("key", uploadDetails.key);
_160 form.append("Policy", uploadDetails.policy);
_160 form.append("X-Amz-Signature", uploadDetails.xAmzSignature);
_160 form.append("success_action_status", uploadDetails.successActionStatus);
_160 form.append("Content-Type", uploadDetails.contentType);
_160 form.append("Cache-Control", uploadDetails.cacheControl);
_160 // Append the file to be uploaded
_160 const response = await axios.get(assetUrl, { responseType: "stream" });
_160 form.append("file", response.data, {
_160 contentType: uploadDetails.contentType,
_160 console.log(response);
_160 // Step 2: Upload to the provided S3 URL
_160 const uploadResponse = await axios.post(uploadUrl, form, {
_160 ...form.getHeaders(),
_160 if (uploadResponse.status === 201) {
_160 console.log(`Successfully uploaded ${fileName} to Webflow.`);
_160 `Failed to upload ${fileName}. Response status: ${uploadResponse.status}`
_160 console.error(`Error uploading asset from ${assetUrl}:`, error);
_160 // Main function to execute the folder creation and asset upload
_160 const siteId = process.env.SITE_ID;
_160 for (const { folderName, assets } of assetDetails) {
_160 const folderId = await createAssetFolder(siteId, folderName);
_160 for (const assetUrl of assets) {
_160 await uploadAsset(siteId, folderId, assetUrl);
Now that we have set up the necessary folders, defined the files, and implemented the file hashing logic, it's time to upload these assets to Webflow. This process consists of two main steps:
Update Metadata to Webflow: Send metadata to Webflow to initiate the upload. Webflow will respond with the necessary details to proceed with uploading the file to Amazon S3.
Upload the File to S3: Use the provided URL and Amazon headers to upload the actual file to Amazon S3.
1. Create uploadAsset()
function _160 require("dotenv").config();
_160 const { WebflowClient } = require("webflow-api");
_160 const crypto = require("crypto");
_160 const axios = require("axios");
_160 const FormData = require("form-data");
_160 // Initialize Webflow client
_160 const webflow = new WebflowClient({
_160 accessToken: process.env.WEBFLOW_API_TOKEN,
_160 // Organize folders and assets
_160 const assetDetails = [
_160 folderName: "Universal Assets",
_160 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_160 folderName: "English Assets",
_160 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_160 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_160 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_160 folderName: "French Assets",
_160 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_160 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_160 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
_160 // Function to create an asset folder
_160 async function createAssetFolder(siteId, folderName) {
_160 // Check if the folder already exists
_160 const existingFolders = await webflow.assets.listFolders(siteId);
_160 const existingFolder = existingFolders.assetFolders.find(
_160 (folder) => folder.displayName === folderName
_160 if (existingFolder) {
_160 `Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_160 return existingFolder.id;
_160 // Create the folder if it does not exist
_160 console.log(`Creating folder: ${folderName}`);
_160 const response = await webflow.assets.createFolder(siteId, {
_160 displayName: folderName,
_160 console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_160 return response.id; // Return folder ID for further use
_160 `Error creating or retrieving asset folder '${folderName}':`,
_160 // Function to hash file data from URL
_160 async function getFileHashFromUrl(assetUrl) {
_160 // Create a promise to handle asynchronous hashing of the file data
_160 return new Promise(async (resolve, reject) => {
_160 // Fetch the file as a stream
_160 const response = await axios.get(assetUrl, { responseType: "stream" });
_160 // Initialize SHA-256 hash
_160 const hash = crypto.createHash("sha256");
_160 // Update the hash with each chunk of data received from the stream
_160 response.data.on("data", (data) => hash.update(data));
_160 // Finalize the hash calculation and resolve the promise with the hash value
_160 response.data.on("end", () => resolve(hash.digest("hex")));
_160 response.data.on("error", reject);
_160 // Function to upload an asset to Webflow via S3
_160 async function uploadAsset(siteId, folderId, assetUrl) {
_160 // Generate the file hash for validation
_160 const fileHash = await getFileHashFromUrl(assetUrl);
_160 const fileName = assetUrl.split("/").pop();
_160 // Step 1: Initialize the upload
_160 const uploadInit = await webflow.assets.create(siteId, {
_160 parentFolder: folderId,
_160 fileName: fileName + `.jpeg`,
_160 const { uploadUrl, uploadDetails } = uploadInit;
_160 // Create form data for S3 upload
_160 const form = new FormData();
_160 // Append all required fields to the form
_160 form.append("acl", uploadDetails.acl);
_160 form.append("bucket", uploadDetails.bucket);
_160 form.append("X-Amz-Algorithm", uploadDetails.xAmzAlgorithm);
_160 form.append("X-Amz-Credential", uploadDetails.xAmzCredential);
_160 form.append("X-Amz-Date", uploadDetails.xAmzDate);
_160 form.append("key", uploadDetails.key);
_160 form.append("Policy", uploadDetails.policy);
_160 form.append("X-Amz-Signature", uploadDetails.xAmzSignature);
_160 form.append("success_action_status", uploadDetails.successActionStatus);
_160 form.append("Content-Type", uploadDetails.contentType);
_160 form.append("Cache-Control", uploadDetails.cacheControl);
_160 // Append the file to be uploaded
_160 const response = await axios.get(assetUrl, { responseType: "stream" });
_160 form.append("file", response.data, {
_160 contentType: uploadDetails.contentType,
_160 console.log(response);
_160 // Step 2: Upload to the provided S3 URL
_160 const uploadResponse = await axios.post(uploadUrl, form, {
_160 ...form.getHeaders(),
_160 if (uploadResponse.status === 201) {
_160 console.log(`Successfully uploaded ${fileName} to Webflow.`);
_160 `Failed to upload ${fileName}. Response status: ${uploadResponse.status}`
_160 console.error(`Error uploading asset from ${assetUrl}:`, error);
_160 // Main function to execute the folder creation and asset upload
_160 const siteId = process.env.SITE_ID;
_160 for (const { folderName, assets } of assetDetails) {
_160 const folderId = await createAssetFolder(siteId, folderName);
_160 for (const assetUrl of assets) {
_160 await uploadAsset(siteId, folderId, assetUrl);
Define an asynchronous function called uploadAsset()
that takes the following arguments: siteId
, folderId
, and assetUrl
.
2. Get file hash and file name _160 require("dotenv").config();
_160 const { WebflowClient } = require("webflow-api");
_160 const crypto = require("crypto");
_160 const axios = require("axios");
_160 const FormData = require("form-data");
_160 // Initialize Webflow client
_160 const webflow = new WebflowClient({
_160 accessToken: process.env.WEBFLOW_API_TOKEN,
_160 // Organize folders and assets
_160 const assetDetails = [
_160 folderName: "Universal Assets",
_160 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_160 folderName: "English Assets",
_160 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_160 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_160 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_160 folderName: "French Assets",
_160 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_160 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_160 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
_160 // Function to create an asset folder
_160 async function createAssetFolder(siteId, folderName) {
_160 // Check if the folder already exists
_160 const existingFolders = await webflow.assets.listFolders(siteId);
_160 const existingFolder = existingFolders.assetFolders.find(
_160 (folder) => folder.displayName === folderName
_160 if (existingFolder) {
_160 `Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_160 return existingFolder.id;
_160 // Create the folder if it does not exist
_160 console.log(`Creating folder: ${folderName}`);
_160 const response = await webflow.assets.createFolder(siteId, {
_160 displayName: folderName,
_160 console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_160 return response.id; // Return folder ID for further use
_160 `Error creating or retrieving asset folder '${folderName}':`,
_160 // Function to hash file data from URL
_160 async function getFileHashFromUrl(assetUrl) {
_160 // Create a promise to handle asynchronous hashing of the file data
_160 return new Promise(async (resolve, reject) => {
_160 // Fetch the file as a stream
_160 const response = await axios.get(assetUrl, { responseType: "stream" });
_160 // Initialize SHA-256 hash
_160 const hash = crypto.createHash("sha256");
_160 // Update the hash with each chunk of data received from the stream
_160 response.data.on("data", (data) => hash.update(data));
_160 // Finalize the hash calculation and resolve the promise with the hash value
_160 response.data.on("end", () => resolve(hash.digest("hex")));
_160 response.data.on("error", reject);
_160 // Function to upload an asset to Webflow via S3
_160 async function uploadAsset(siteId, folderId, assetUrl) {
_160 // Generate the file hash for validation
_160 const fileHash = await getFileHashFromUrl(assetUrl);
_160 const fileName = assetUrl.split("/").pop();
_160 // Step 1: Initialize the upload
_160 const uploadInit = await webflow.assets.create(siteId, {
_160 parentFolder: folderId,
_160 fileName: fileName + `.jpeg`,
_160 const { uploadUrl, uploadDetails } = uploadInit;
_160 // Create form data for S3 upload
_160 const form = new FormData();
_160 // Append all required fields to the form
_160 form.append("acl", uploadDetails.acl);
_160 form.append("bucket", uploadDetails.bucket);
_160 form.append("X-Amz-Algorithm", uploadDetails.xAmzAlgorithm);
_160 form.append("X-Amz-Credential", uploadDetails.xAmzCredential);
_160 form.append("X-Amz-Date", uploadDetails.xAmzDate);
_160 form.append("key", uploadDetails.key);
_160 form.append("Policy", uploadDetails.policy);
_160 form.append("X-Amz-Signature", uploadDetails.xAmzSignature);
_160 form.append("success_action_status", uploadDetails.successActionStatus);
_160 form.append("Content-Type", uploadDetails.contentType);
_160 form.append("Cache-Control", uploadDetails.cacheControl);
_160 // Append the file to be uploaded
_160 const response = await axios.get(assetUrl, { responseType: "stream" });
_160 form.append("file", response.data, {
_160 contentType: uploadDetails.contentType,
_160 console.log(response);
_160 // Step 2: Upload to the provided S3 URL
_160 const uploadResponse = await axios.post(uploadUrl, form, {
_160 ...form.getHeaders(),
_160 if (uploadResponse.status === 201) {
_160 console.log(`Successfully uploaded ${fileName} to Webflow.`);
_160 `Failed to upload ${fileName}. Response status: ${uploadResponse.status}`
_160 console.error(`Error uploading asset from ${assetUrl}:`, error);
_160 // Main function to execute the folder creation and asset upload
_160 const siteId = process.env.SITE_ID;
_160 for (const { folderName, assets } of assetDetails) {
_160 const folderId = await createAssetFolder(siteId, folderName);
_160 for (const assetUrl of assets) {
_160 await uploadAsset(siteId, folderId, assetUrl);
Use the getFileHashFromUrl()
function to generate the file hash. Extract the file name from the URL.
3. Upload Metadata to Webflow _160 require("dotenv").config();
_160 const { WebflowClient } = require("webflow-api");
_160 const crypto = require("crypto");
_160 const axios = require("axios");
_160 const FormData = require("form-data");
_160 // Initialize Webflow client
_160 const webflow = new WebflowClient({
_160 accessToken: process.env.WEBFLOW_API_TOKEN,
_160 // Organize folders and assets
_160 const assetDetails = [
_160 folderName: "Universal Assets",
_160 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_160 folderName: "English Assets",
_160 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_160 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_160 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_160 folderName: "French Assets",
_160 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_160 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_160 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
_160 // Function to create an asset folder
_160 async function createAssetFolder(siteId, folderName) {
_160 // Check if the folder already exists
_160 const existingFolders = await webflow.assets.listFolders(siteId);
_160 const existingFolder = existingFolders.assetFolders.find(
_160 (folder) => folder.displayName === folderName
_160 if (existingFolder) {
_160 `Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_160 return existingFolder.id;
_160 // Create the folder if it does not exist
_160 console.log(`Creating folder: ${folderName}`);
_160 const response = await webflow.assets.createFolder(siteId, {
_160 displayName: folderName,
_160 console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_160 return response.id; // Return folder ID for further use
_160 `Error creating or retrieving asset folder '${folderName}':`,
_160 // Function to hash file data from URL
_160 async function getFileHashFromUrl(assetUrl) {
_160 // Create a promise to handle asynchronous hashing of the file data
_160 return new Promise(async (resolve, reject) => {
_160 // Fetch the file as a stream
_160 const response = await axios.get(assetUrl, { responseType: "stream" });
_160 // Initialize SHA-256 hash
_160 const hash = crypto.createHash("sha256");
_160 // Update the hash with each chunk of data received from the stream
_160 response.data.on("data", (data) => hash.update(data));
_160 // Finalize the hash calculation and resolve the promise with the hash value
_160 response.data.on("end", () => resolve(hash.digest("hex")));
_160 response.data.on("error", reject);
_160 // Function to upload an asset to Webflow via S3
_160 async function uploadAsset(siteId, folderId, assetUrl) {
_160 // Generate the file hash for validation
_160 const fileHash = await getFileHashFromUrl(assetUrl);
_160 const fileName = assetUrl.split("/").pop();
_160 // Step 1: Initialize the upload
_160 const uploadInit = await webflow.assets.create(siteId, {
_160 parentFolder: folderId,
_160 fileName: fileName + `.jpeg`,
_160 const { uploadUrl, uploadDetails } = uploadInit;
_160 // Create form data for S3 upload
_160 const form = new FormData();
_160 // Append all required fields to the form
_160 form.append("acl", uploadDetails.acl);
_160 form.append("bucket", uploadDetails.bucket);
_160 form.append("X-Amz-Algorithm", uploadDetails.xAmzAlgorithm);
_160 form.append("X-Amz-Credential", uploadDetails.xAmzCredential);
_160 form.append("X-Amz-Date", uploadDetails.xAmzDate);
_160 form.append("key", uploadDetails.key);
_160 form.append("Policy", uploadDetails.policy);
_160 form.append("X-Amz-Signature", uploadDetails.xAmzSignature);
_160 form.append("success_action_status", uploadDetails.successActionStatus);
_160 form.append("Content-Type", uploadDetails.contentType);
_160 form.append("Cache-Control", uploadDetails.cacheControl);
_160 // Append the file to be uploaded
_160 const response = await axios.get(assetUrl, { responseType: "stream" });
_160 form.append("file", response.data, {
_160 contentType: uploadDetails.contentType,
_160 console.log(response);
_160 // Step 2: Upload to the provided S3 URL
_160 const uploadResponse = await axios.post(uploadUrl, form, {
_160 ...form.getHeaders(),
_160 if (uploadResponse.status === 201) {
_160 console.log(`Successfully uploaded ${fileName} to Webflow.`);
_160 `Failed to upload ${fileName}. Response status: ${uploadResponse.status}`
_160 console.error(`Error uploading asset from ${assetUrl}:`, error);
_160 // Main function to execute the folder creation and asset upload
_160 const siteId = process.env.SITE_ID;
_160 for (const { folderName, assets } of assetDetails) {
_160 const folderId = await createAssetFolder(siteId, folderName);
_160 for (const assetUrl of assets) {
_160 await uploadAsset(siteId, folderId, assetUrl);
Use the webflow.assets.create()
method to send the siteId
, parentFolder
, fileName,
and fileHash
to Webflow.
The response from Webflow includes:
uploadUrl
: The Amazon S3 URL to upload the file.
uploadDetails
: Amazon-specific headers and parameters required for the file upload.
4. Prepare form data for Amazon S3 upload _160 require("dotenv").config();
_160 const { WebflowClient } = require("webflow-api");
_160 const crypto = require("crypto");
_160 const axios = require("axios");
_160 const FormData = require("form-data");
_160 // Initialize Webflow client
_160 const webflow = new WebflowClient({
_160 accessToken: process.env.WEBFLOW_API_TOKEN,
_160 // Organize folders and assets
_160 const assetDetails = [
_160 folderName: "Universal Assets",
_160 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_160 folderName: "English Assets",
_160 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_160 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_160 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_160 folderName: "French Assets",
_160 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_160 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_160 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
_160 // Function to create an asset folder
_160 async function createAssetFolder(siteId, folderName) {
_160 // Check if the folder already exists
_160 const existingFolders = await webflow.assets.listFolders(siteId);
_160 const existingFolder = existingFolders.assetFolders.find(
_160 (folder) => folder.displayName === folderName
_160 if (existingFolder) {
_160 `Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_160 return existingFolder.id;
_160 // Create the folder if it does not exist
_160 console.log(`Creating folder: ${folderName}`);
_160 const response = await webflow.assets.createFolder(siteId, {
_160 displayName: folderName,
_160 console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_160 return response.id; // Return folder ID for further use
_160 `Error creating or retrieving asset folder '${folderName}':`,
_160 // Function to hash file data from URL
_160 async function getFileHashFromUrl(assetUrl) {
_160 // Create a promise to handle asynchronous hashing of the file data
_160 return new Promise(async (resolve, reject) => {
_160 // Fetch the file as a stream
_160 const response = await axios.get(assetUrl, { responseType: "stream" });
_160 // Initialize SHA-256 hash
_160 const hash = crypto.createHash("sha256");
_160 // Update the hash with each chunk of data received from the stream
_160 response.data.on("data", (data) => hash.update(data));
_160 // Finalize the hash calculation and resolve the promise with the hash value
_160 response.data.on("end", () => resolve(hash.digest("hex")));
_160 response.data.on("error", reject);
_160 // Function to upload an asset to Webflow via S3
_160 async function uploadAsset(siteId, folderId, assetUrl) {
_160 // Generate the file hash for validation
_160 const fileHash = await getFileHashFromUrl(assetUrl);
_160 const fileName = assetUrl.split("/").pop();
_160 // Step 1: Initialize the upload
_160 const uploadInit = await webflow.assets.create(siteId, {
_160 parentFolder: folderId,
_160 fileName: fileName + `.jpeg`,
_160 const { uploadUrl, uploadDetails } = uploadInit;
_160 // Create form data for S3 upload
_160 const form = new FormData();
_160 // Append all required fields to the form
_160 form.append("acl", uploadDetails.acl);
_160 form.append("bucket", uploadDetails.bucket);
_160 form.append("X-Amz-Algorithm", uploadDetails.xAmzAlgorithm);
_160 form.append("X-Amz-Credential", uploadDetails.xAmzCredential);
_160 form.append("X-Amz-Date", uploadDetails.xAmzDate);
_160 form.append("key", uploadDetails.key);
_160 form.append("Policy", uploadDetails.policy);
_160 form.append("X-Amz-Signature", uploadDetails.xAmzSignature);
_160 form.append("success_action_status", uploadDetails.successActionStatus);
_160 form.append("Content-Type", uploadDetails.contentType);
_160 form.append("Cache-Control", uploadDetails.cacheControl);
_160 // Append the file to be uploaded
_160 const response = await axios.get(assetUrl, { responseType: "stream" });
_160 form.append("file", response.data, {
_160 contentType: uploadDetails.contentType,
_160 console.log(response);
_160 // Step 2: Upload to the provided S3 URL
_160 const uploadResponse = await axios.post(uploadUrl, form, {
_160 ...form.getHeaders(),
_160 if (uploadResponse.status === 201) {
_160 console.log(`Successfully uploaded ${fileName} to Webflow.`);
_160 `Failed to upload ${fileName}. Response status: ${uploadResponse.status}`
_160 console.error(`Error uploading asset from ${assetUrl}:`, error);
_160 // Main function to execute the folder creation and asset upload
_160 const siteId = process.env.SITE_ID;
_160 for (const { folderName, assets } of assetDetails) {
_160 const folderId = await createAssetFolder(siteId, folderName);
_160 for (const assetUrl of assets) {
_160 await uploadAsset(siteId, folderId, assetUrl);
Use the FormData
library to create a form object needed for the Amazon S3 upload
Refer to the Amazon S3 Upload Documentation for more details on each parameter.
Append Amazon S3 Headers Append the required Amazon S3 headers from uploadDetails
to the form. These headers are provided by Webflow and are necessary for the upload authorization.
Append File Data Fetch the file using axios with a responseType
of stream to handle large files efficiently. Append the file to the form data.
5. Upload to Amazon S3 _160 require("dotenv").config();
_160 const { WebflowClient } = require("webflow-api");
_160 const crypto = require("crypto");
_160 const axios = require("axios");
_160 const FormData = require("form-data");
_160 // Initialize Webflow client
_160 const webflow = new WebflowClient({
_160 accessToken: process.env.WEBFLOW_API_TOKEN,
_160 // Organize folders and assets
_160 const assetDetails = [
_160 folderName: "Universal Assets",
_160 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_160 folderName: "English Assets",
_160 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_160 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_160 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_160 folderName: "French Assets",
_160 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_160 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_160 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
_160 // Function to create an asset folder
_160 async function createAssetFolder(siteId, folderName) {
_160 // Check if the folder already exists
_160 const existingFolders = await webflow.assets.listFolders(siteId);
_160 const existingFolder = existingFolders.assetFolders.find(
_160 (folder) => folder.displayName === folderName
_160 if (existingFolder) {
_160 `Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_160 return existingFolder.id;
_160 // Create the folder if it does not exist
_160 console.log(`Creating folder: ${folderName}`);
_160 const response = await webflow.assets.createFolder(siteId, {
_160 displayName: folderName,
_160 console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_160 return response.id; // Return folder ID for further use
_160 `Error creating or retrieving asset folder '${folderName}':`,
_160 // Function to hash file data from URL
_160 async function getFileHashFromUrl(assetUrl) {
_160 // Create a promise to handle asynchronous hashing of the file data
_160 return new Promise(async (resolve, reject) => {
_160 // Fetch the file as a stream
_160 const response = await axios.get(assetUrl, { responseType: "stream" });
_160 // Initialize SHA-256 hash
_160 const hash = crypto.createHash("sha256");
_160 // Update the hash with each chunk of data received from the stream
_160 response.data.on("data", (data) => hash.update(data));
_160 // Finalize the hash calculation and resolve the promise with the hash value
_160 response.data.on("end", () => resolve(hash.digest("hex")));
_160 response.data.on("error", reject);
_160 // Function to upload an asset to Webflow via S3
_160 async function uploadAsset(siteId, folderId, assetUrl) {
_160 // Generate the file hash for validation
_160 const fileHash = await getFileHashFromUrl(assetUrl);
_160 const fileName = assetUrl.split("/").pop();
_160 // Step 1: Initialize the upload
_160 const uploadInit = await webflow.assets.create(siteId, {
_160 parentFolder: folderId,
_160 fileName: fileName + `.jpeg`,
_160 const { uploadUrl, uploadDetails } = uploadInit;
_160 // Create form data for S3 upload
_160 const form = new FormData();
_160 // Append all required fields to the form
_160 form.append("acl", uploadDetails.acl);
_160 form.append("bucket", uploadDetails.bucket);
_160 form.append("X-Amz-Algorithm", uploadDetails.xAmzAlgorithm);
_160 form.append("X-Amz-Credential", uploadDetails.xAmzCredential);
_160 form.append("X-Amz-Date", uploadDetails.xAmzDate);
_160 form.append("key", uploadDetails.key);
_160 form.append("Policy", uploadDetails.policy);
_160 form.append("X-Amz-Signature", uploadDetails.xAmzSignature);
_160 form.append("success_action_status", uploadDetails.successActionStatus);
_160 form.append("Content-Type", uploadDetails.contentType);
_160 form.append("Cache-Control", uploadDetails.cacheControl);
_160 // Append the file to be uploaded
_160 const response = await axios.get(assetUrl, { responseType: "stream" });
_160 form.append("file", response.data, {
_160 contentType: uploadDetails.contentType,
_160 console.log(response);
_160 // Step 2: Upload to the provided S3 URL
_160 const uploadResponse = await axios.post(uploadUrl, form, {
_160 ...form.getHeaders(),
_160 if (uploadResponse.status === 201) {
_160 console.log(`Successfully uploaded ${fileName} to Webflow.`);
_160 `Failed to upload ${fileName}. Response status: ${uploadResponse.status}`
_160 console.error(`Error uploading asset from ${assetUrl}:`, error);
_160 // Main function to execute the folder creation and asset upload
_160 const siteId = process.env.SITE_ID;
_160 for (const { folderName, assets } of assetDetails) {
_160 const folderId = await createAssetFolder(siteId, folderName);
_160 for (const assetUrl of assets) {
_160 await uploadAsset(siteId, folderId, assetUrl);
Use axios to make a POST request to the provided uploadUrl
. The URL, along with the specific headers from uploadDetails
, allows the file to be uploaded directly to AWS S3.
Call the main function _160 require("dotenv").config();
_160 const { WebflowClient } = require("webflow-api");
_160 const crypto = require("crypto");
_160 const axios = require("axios");
_160 const FormData = require("form-data");
_160 // Initialize Webflow client
_160 const webflow = new WebflowClient({
_160 accessToken: process.env.WEBFLOW_API_TOKEN,
_160 // Organize folders and assets
_160 const assetDetails = [
_160 folderName: "Universal Assets",
_160 assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_160 folderName: "English Assets",
_160 "https://images.unsplash.com/photo-1543109740-4bdb38fda756",
_160 "https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
_160 "https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
_160 folderName: "French Assets",
_160 "https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
_160 "https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
_160 "https://images.unsplash.com/photo-1528717663417-3742fee05a29",
_160 // Function to create an asset folder
_160 async function createAssetFolder(siteId, folderName) {
_160 // Check if the folder already exists
_160 const existingFolders = await webflow.assets.listFolders(siteId);
_160 const existingFolder = existingFolders.assetFolders.find(
_160 (folder) => folder.displayName === folderName
_160 if (existingFolder) {
_160 `Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_160 return existingFolder.id;
_160 // Create the folder if it does not exist
_160 console.log(`Creating folder: ${folderName}`);
_160 const response = await webflow.assets.createFolder(siteId, {
_160 displayName: folderName,
_160 console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_160 return response.id; // Return folder ID for further use
_160 `Error creating or retrieving asset folder '${folderName}':`,
_160 // Function to hash file data from URL
_160 async function getFileHashFromUrl(assetUrl) {
_160 // Create a promise to handle asynchronous hashing of the file data
_160 return new Promise(async (resolve, reject) => {
_160 // Fetch the file as a stream
_160 const response = await axios.get(assetUrl, { responseType: "stream" });
_160 // Initialize SHA-256 hash
_160 const hash = crypto.createHash("sha256");
_160 // Update the hash with each chunk of data received from the stream
_160 response.data.on("data", (data) => hash.update(data));
_160 // Finalize the hash calculation and resolve the promise with the hash value
_160 response.data.on("end", () => resolve(hash.digest("hex")));
_160 response.data.on("error", reject);
_160 // Function to upload an asset to Webflow via S3
_160 async function uploadAsset(siteId, folderId, assetUrl) {
_160 // Generate the file hash for validation
_160 const fileHash = await getFileHashFromUrl(assetUrl);
_160 const fileName = assetUrl.split("/").pop();
_160 // Step 1: Initialize the upload
_160 const uploadInit = await webflow.assets.create(siteId, {
_160 parentFolder: folderId,
_160 fileName: fileName + `.jpeg`,
_160 const { uploadUrl, uploadDetails } = uploadInit;
_160 // Create form data for S3 upload
_160 const form = new FormData();
_160 // Append all required fields to the form
_160 form.append("acl", uploadDetails.acl);
_160 form.append("bucket", uploadDetails.bucket);
_160 form.append("X-Amz-Algorithm", uploadDetails.xAmzAlgorithm);
_160 form.append("X-Amz-Credential", uploadDetails.xAmzCredential);
_160 form.append("X-Amz-Date", uploadDetails.xAmzDate);
_160 form.append("key", uploadDetails.key);
_160 form.append("Policy", uploadDetails.policy);
_160 form.append("X-Amz-Signature", uploadDetails.xAmzSignature);
_160 form.append("success_action_status", uploadDetails.successActionStatus);
_160 form.append("Content-Type", uploadDetails.contentType);
_160 form.append("Cache-Control", uploadDetails.cacheControl);
_160 // Append the file to be uploaded
_160 const response = await axios.get(assetUrl, { responseType: "stream" });
_160 form.append("file", response.data, {
_160 contentType: uploadDetails.contentType,
_160 console.log(response);
_160 // Step 2: Upload to the provided S3 URL
_160 const uploadResponse = await axios.post(uploadUrl, form, {
_160 ...form.getHeaders(),
_160 if (uploadResponse.status === 201) {
_160 console.log(`Successfully uploaded ${fileName} to Webflow.`);
_160 `Failed to upload ${fileName}. Response status: ${uploadResponse.status}`
_160 console.error(`Error uploading asset from ${assetUrl}:`, error);
_160 // Main function to execute the folder creation and asset upload
_160 const siteId = process.env.SITE_ID;
_160 for (const { folderName, assets } of assetDetails) {
_160 const folderId = await createAssetFolder(siteId, folderName);
_160 for (const assetUrl of assets) {
_160 await uploadAsset(siteId, folderId, assetUrl);
Now that we have defined all the necessary functions (createAssetFolder
, getFileHashFromUrl
, and uploadAsset
), it's time to call the main function and complete the process of uploading assets to Webflow.
1. Update the main function In assets.js
, make sure the main function is properly set up to create the asset folders and upload the files. Update it to include the uploadAsset()
function call for each asset URL.
This will:
Iterate through each folder in assetDetails
.
Create the folder in Webflow if it doesn't already exist.
Upload each asset to the corresponding folder using the uploadAsset()
function.
2. Run the script To execute the script and upload the assets, run assets.js
using Node.js in the terminal:
3. Verify successful upload If everything is configured correctly, you should see output in the terminal that indicates the successful creation of folders and uploads of assets.
Additionally, you can refresh the Designer and open the Assets Panel in the Webflow designer to see your newly created assets.
If there are any errors (e.g., issues with the API token, invalid URLs, or upload failures), they will be logged in the console, providing useful feedback to troubleshoot.