assets.js
.env

_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
_155
const webflow = new WebflowClient({
_155
accessToken: process.env.WEBFLOW_API_TOKEN,
_155
});
_155
_155
// Organize folders and assets
_155
const assetDetails = [
_155
{
_155
folderName: "Universal Assets",
_155
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_155
},
_155
{
_155
folderName: "English Assets",
_155
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
],
_155
},
_155
{
_155
folderName: "French Assets",
_155
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
],
_155
},
_155
];
_155
_155
// Function to hash file data from URL
_155
async function getFileHashFromUrl(assetUrl) {
_155
_155
// Create a promise to handle asynchronous hashing of the file data
_155
return new Promise(async (resolve, reject) => {
_155
try {
_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
} catch (error) {
_155
reject(error);
_155
}
_155
});
_155
}
_155
_155
// Function to create an asset folder
_155
async function createAssetFolder(siteId, folderName) {
_155
try {
_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
);
_155
if (existingFolder) {
_155
console.log(
_155
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_155
);
_155
return existingFolder.id;
_155
}
_155
_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
});
_155
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_155
return response.id; // Return folder ID for further use
_155
} catch (error) {
_155
console.error(
_155
`Error creating or retrieving asset folder '${folderName}':`,
_155
error
_155
);
_155
}
_155
}
_155
_155
// Function to upload an asset to Webflow via S3
_155
async function uploadAsset(siteId, folderId, assetUrl) {
_155
try {
_155
// Generate the file hash for validation
_155
const fileHash = await getFileHashFromUrl(assetUrl);
_155
const fileName = assetUrl.split("/").pop();
_155
_155
// Step 1: Initialize the upload
_155
const uploadInit = await webflow.assets.create(siteId, {
_155
parentFolder: folderId,
_155
fileName: fileName + `.jpeg`,
_155
fileHash: fileHash,
_155
});
_155
const { uploadUrl, uploadDetails } = uploadInit;
_155
_155
// Create form data for S3 upload
_155
const form = new FormData();
_155
_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
_155
// Append the file to be uploaded
_155
const response = await axios.get(assetUrl, { responseType: "stream" });
_155
form.append("file", response.data, {
_155
filename: fileName,
_155
contentType: uploadDetails.contentType,
_155
});
_155
console.log(response);
_155
_155
// Step 2: Upload to the provided S3 URL
_155
const uploadResponse = await axios.post(uploadUrl, form, {
_155
headers: {
_155
...form.getHeaders(),
_155
},
_155
});
_155
_155
if (uploadResponse.status === 201) {
_155
console.log(`Successfully uploaded ${fileName} to Webflow.`);
_155
} else {
_155
console.error(
_155
`Failed to upload ${fileName}. Response status: ${uploadResponse.status}`
_155
);
_155
}
_155
} catch (error) {
_155
console.error(`Error uploading asset from ${assetUrl}:`, error);
_155
}
_155
}
_155
_155
// Main function to execute the upload
_155
(async () => {
_155
const siteId = process.env.SITE_ID; // Replace with your actual site ID
_155
_155
for (const { folderName, assets } of assetDetails) {
_155
const folderId = await createAssetFolder(siteId, folderName);
_155
if (folderId) {
_155
for (const assetUrl of assets) {
_155
await uploadAsset(siteId, folderId, assetUrl);
_155
}
_155
}
_155
}
_155
})();

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:

assets.js
.env

_2
WEBFLOW_API_TOKEN=YOUR_WEBFLOW_API_TOKEN
_2
SITE_ID=YOUR_SITE_ID

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.

$ touch assets.js

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.

assets.js
.env

_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.

assets.js
.env

_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
_10
// Initialize Webflow client
_10
const webflow = new WebflowClient({
_10
accessToken: process.env.WEBFLOW_API_TOKEN,
_10
});

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.

assets.js
.env

_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
_34
// Initialize Webflow client
_34
const webflow = new WebflowClient({
_34
accessToken: process.env.WEBFLOW_API_TOKEN,
_34
});
_34
_34
// Organize folders and assets
_34
const assetDetails = [
_34
{
_34
folderName: "Universal Assets",
_34
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_34
},
_34
{
_34
folderName: "English Assets",
_34
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
],
_34
},
_34
{
_34
folderName: "French Assets",
_34
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",
_34
],
_34
},
_34
];

Create asset folders

assets.js
.env

_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
_77
// Initialize Webflow client
_77
const webflow = new WebflowClient({
_77
accessToken: process.env.WEBFLOW_API_TOKEN,
_77
});
_77
_77
// Organize folders and assets
_77
const assetDetails = [
_77
{
_77
folderName: "Universal Assets",
_77
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_77
},
_77
{
_77
folderName: "English Assets",
_77
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
],
_77
},
_77
{
_77
folderName: "French Assets",
_77
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
],
_77
},
_77
];
_77
_77
// Function to create an asset folder
_77
async function createAssetFolder(siteId, folderName) {
_77
try {
_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
);
_77
if (existingFolder) {
_77
console.log(
_77
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_77
);
_77
return existingFolder.id;
_77
}
_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
});
_77
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_77
return response.id; // Return folder ID for further use
_77
} catch (error) {
_77
console.error(
_77
`Error creating or retrieving asset folder '${folderName}':`,
_77
error
_77
);
_77
}
_77
}
_77
_77
// Main function to execute the folder creation and asset upload
_77
(async () => {
_77
const siteId = process.env.SITE_ID;
_77
_77
for (const { folderName, assets } of assetDetails) {
_77
const folderId = await createAssetFolder(siteId, folderName);
_77
if (folderId) {
_77
for (const assetUrl of assets) {
_77
// TO ADD: Upload logic
_77
}
_77
}
_77
}
_77
})();

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()

assets.js
.env

_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
_77
// Initialize Webflow client
_77
const webflow = new WebflowClient({
_77
accessToken: process.env.WEBFLOW_API_TOKEN,
_77
});
_77
_77
// Organize folders and assets
_77
const assetDetails = [
_77
{
_77
folderName: "Universal Assets",
_77
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_77
},
_77
{
_77
folderName: "English Assets",
_77
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
],
_77
},
_77
{
_77
folderName: "French Assets",
_77
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
],
_77
},
_77
];
_77
_77
// Function to create an asset folder
_77
async function createAssetFolder(siteId, folderName) {
_77
try {
_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
);
_77
if (existingFolder) {
_77
console.log(
_77
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_77
);
_77
return existingFolder.id;
_77
}
_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
});
_77
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_77
return response.id; // Return folder ID for further use
_77
} catch (error) {
_77
console.error(
_77
`Error creating or retrieving asset folder '${folderName}':`,
_77
error
_77
);
_77
}
_77
}
_77
_77
// Main function to execute the folder creation and asset upload
_77
(async () => {
_77
const siteId = process.env.SITE_ID;
_77
_77
for (const { folderName, assets } of assetDetails) {
_77
const folderId = await createAssetFolder(siteId, folderName);
_77
if (folderId) {
_77
for (const assetUrl of assets) {
_77
// TO ADD: Upload logic
_77
}
_77
}
_77
}
_77
})();

This function, createAssetFolder(), takes the site ID and folder name as arguments.

2. Check for existing asset folders

assets.js
.env

_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
_77
// Initialize Webflow client
_77
const webflow = new WebflowClient({
_77
accessToken: process.env.WEBFLOW_API_TOKEN,
_77
});
_77
_77
// Organize folders and assets
_77
const assetDetails = [
_77
{
_77
folderName: "Universal Assets",
_77
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_77
},
_77
{
_77
folderName: "English Assets",
_77
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
],
_77
},
_77
{
_77
folderName: "French Assets",
_77
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
],
_77
},
_77
];
_77
_77
// Function to create an asset folder
_77
async function createAssetFolder(siteId, folderName) {
_77
try {
_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
);
_77
if (existingFolder) {
_77
console.log(
_77
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_77
);
_77
return existingFolder.id;
_77
}
_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
});
_77
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_77
return response.id; // Return folder ID for further use
_77
} catch (error) {
_77
console.error(
_77
`Error creating or retrieving asset folder '${folderName}':`,
_77
error
_77
);
_77
}
_77
}
_77
_77
// Main function to execute the folder creation and asset upload
_77
(async () => {
_77
const siteId = process.env.SITE_ID;
_77
_77
for (const { folderName, assets } of assetDetails) {
_77
const folderId = await createAssetFolder(siteId, folderName);
_77
if (folderId) {
_77
for (const assetUrl of assets) {
_77
// TO ADD: Upload logic
_77
}
_77
}
_77
}
_77
})();

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

assets.js
.env

_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
_77
// Initialize Webflow client
_77
const webflow = new WebflowClient({
_77
accessToken: process.env.WEBFLOW_API_TOKEN,
_77
});
_77
_77
// Organize folders and assets
_77
const assetDetails = [
_77
{
_77
folderName: "Universal Assets",
_77
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_77
},
_77
{
_77
folderName: "English Assets",
_77
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
],
_77
},
_77
{
_77
folderName: "French Assets",
_77
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
],
_77
},
_77
];
_77
_77
// Function to create an asset folder
_77
async function createAssetFolder(siteId, folderName) {
_77
try {
_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
);
_77
if (existingFolder) {
_77
console.log(
_77
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_77
);
_77
return existingFolder.id;
_77
}
_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
});
_77
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_77
return response.id; // Return folder ID for further use
_77
} catch (error) {
_77
console.error(
_77
`Error creating or retrieving asset folder '${folderName}':`,
_77
error
_77
);
_77
}
_77
}
_77
_77
// Main function to execute the folder creation and asset upload
_77
(async () => {
_77
const siteId = process.env.SITE_ID;
_77
_77
for (const { folderName, assets } of assetDetails) {
_77
const folderId = await createAssetFolder(siteId, folderName);
_77
if (folderId) {
_77
for (const assetUrl of assets) {
_77
// TO ADD: Upload logic
_77
}
_77
}
_77
}
_77
})();

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

assets.js
.env

_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
_77
// Initialize Webflow client
_77
const webflow = new WebflowClient({
_77
accessToken: process.env.WEBFLOW_API_TOKEN,
_77
});
_77
_77
// Organize folders and assets
_77
const assetDetails = [
_77
{
_77
folderName: "Universal Assets",
_77
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_77
},
_77
{
_77
folderName: "English Assets",
_77
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
],
_77
},
_77
{
_77
folderName: "French Assets",
_77
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
],
_77
},
_77
];
_77
_77
// Function to create an asset folder
_77
async function createAssetFolder(siteId, folderName) {
_77
try {
_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
);
_77
if (existingFolder) {
_77
console.log(
_77
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_77
);
_77
return existingFolder.id;
_77
}
_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
});
_77
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_77
return response.id; // Return folder ID for further use
_77
} catch (error) {
_77
console.error(
_77
`Error creating or retrieving asset folder '${folderName}':`,
_77
error
_77
);
_77
}
_77
}
_77
_77
// Main function to execute the folder creation and asset upload
_77
(async () => {
_77
const siteId = process.env.SITE_ID;
_77
_77
for (const { folderName, assets } of assetDetails) {
_77
const folderId = await createAssetFolder(siteId, folderName);
_77
if (folderId) {
_77
for (const assetUrl of assets) {
_77
// TO ADD: Upload logic
_77
}
_77
}
_77
}
_77
})();

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

assets.js
.env

_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
_102
// Initialize Webflow client
_102
const webflow = new WebflowClient({
_102
accessToken: process.env.WEBFLOW_API_TOKEN,
_102
});
_102
_102
// Organize folders and assets
_102
const assetDetails = [
_102
{
_102
folderName: "Universal Assets",
_102
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_102
},
_102
{
_102
folderName: "English Assets",
_102
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
],
_102
},
_102
{
_102
folderName: "French Assets",
_102
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
],
_102
},
_102
];
_102
_102
// Function to create an asset folder
_102
async function createAssetFolder(siteId, folderName) {
_102
try {
_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
);
_102
if (existingFolder) {
_102
console.log(
_102
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_102
);
_102
return existingFolder.id;
_102
}
_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
});
_102
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_102
return response.id; // Return folder ID for further use
_102
} catch (error) {
_102
console.error(
_102
`Error creating or retrieving asset folder '${folderName}':`,
_102
error
_102
);
_102
}
_102
}
_102
_102
// Function to hash file data from URL
_102
async function getFileHashFromUrl(assetUrl) {
_102
_102
// Create a promise to handle asynchronous hashing of the file data
_102
return new Promise(async (resolve, reject) => {
_102
try {
_102
_102
// Fetch the file as a stream
_102
const response = await axios.get(assetUrl, { responseType: "stream" });
_102
_102
// Initialize SHA-256 hash
_102
const hash = crypto.createHash("sha256");
_102
_102
// Update the hash with each chunk of data received from the stream
_102
response.data.on("data", (data) => hash.update(data));
_102
_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
} catch (error) {
_102
reject(error);
_102
}
_102
});
_102
}
_102
_102
// Main function to execute the folder creation and asset upload
_102
(async () => {
_102
const siteId = process.env.SITE_ID;
_102
_102
for (const { folderName, assets } of assetDetails) {
_102
const folderId = await createAssetFolder(siteId, folderName);
_102
if (folderId) {
_102
for (const assetUrl of assets) {
_102
// TO ADD: Upload logic
_102
}
_102
}
_102
}
_102
})();

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

assets.js
.env

_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
_102
// Initialize Webflow client
_102
const webflow = new WebflowClient({
_102
accessToken: process.env.WEBFLOW_API_TOKEN,
_102
});
_102
_102
// Organize folders and assets
_102
const assetDetails = [
_102
{
_102
folderName: "Universal Assets",
_102
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_102
},
_102
{
_102
folderName: "English Assets",
_102
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
],
_102
},
_102
{
_102
folderName: "French Assets",
_102
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
],
_102
},
_102
];
_102
_102
// Function to create an asset folder
_102
async function createAssetFolder(siteId, folderName) {
_102
try {
_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
);
_102
if (existingFolder) {
_102
console.log(
_102
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_102
);
_102
return existingFolder.id;
_102
}
_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
});
_102
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_102
return response.id; // Return folder ID for further use
_102
} catch (error) {
_102
console.error(
_102
`Error creating or retrieving asset folder '${folderName}':`,
_102
error
_102
);
_102
}
_102
}
_102
_102
// Function to hash file data from URL
_102
async function getFileHashFromUrl(assetUrl) {
_102
_102
// Create a promise to handle asynchronous hashing of the file data
_102
return new Promise(async (resolve, reject) => {
_102
try {
_102
_102
// Fetch the file as a stream
_102
const response = await axios.get(assetUrl, { responseType: "stream" });
_102
_102
// Initialize SHA-256 hash
_102
const hash = crypto.createHash("sha256");
_102
_102
// Update the hash with each chunk of data received from the stream
_102
response.data.on("data", (data) => hash.update(data));
_102
_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
} catch (error) {
_102
reject(error);
_102
}
_102
});
_102
}
_102
_102
// Main function to execute the folder creation and asset upload
_102
(async () => {
_102
const siteId = process.env.SITE_ID;
_102
_102
for (const { folderName, assets } of assetDetails) {
_102
const folderId = await createAssetFolder(siteId, folderName);
_102
if (folderId) {
_102
for (const assetUrl of assets) {
_102
// TO ADD: Upload logic
_102
}
_102
}
_102
}
_102
})();

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

assets.js
.env

_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
_102
// Initialize Webflow client
_102
const webflow = new WebflowClient({
_102
accessToken: process.env.WEBFLOW_API_TOKEN,
_102
});
_102
_102
// Organize folders and assets
_102
const assetDetails = [
_102
{
_102
folderName: "Universal Assets",
_102
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_102
},
_102
{
_102
folderName: "English Assets",
_102
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
],
_102
},
_102
{
_102
folderName: "French Assets",
_102
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
],
_102
},
_102
];
_102
_102
// Function to create an asset folder
_102
async function createAssetFolder(siteId, folderName) {
_102
try {
_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
);
_102
if (existingFolder) {
_102
console.log(
_102
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_102
);
_102
return existingFolder.id;
_102
}
_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
});
_102
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_102
return response.id; // Return folder ID for further use
_102
} catch (error) {
_102
console.error(
_102
`Error creating or retrieving asset folder '${folderName}':`,
_102
error
_102
);
_102
}
_102
}
_102
_102
// Function to hash file data from URL
_102
async function getFileHashFromUrl(assetUrl) {
_102
_102
// Create a promise to handle asynchronous hashing of the file data
_102
return new Promise(async (resolve, reject) => {
_102
try {
_102
_102
// Fetch the file as a stream
_102
const response = await axios.get(assetUrl, { responseType: "stream" });
_102
_102
// Initialize SHA-256 hash
_102
const hash = crypto.createHash("sha256");
_102
_102
// Update the hash with each chunk of data received from the stream
_102
response.data.on("data", (data) => hash.update(data));
_102
_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
} catch (error) {
_102
reject(error);
_102
}
_102
});
_102
}
_102
_102
// Main function to execute the folder creation and asset upload
_102
(async () => {
_102
const siteId = process.env.SITE_ID;
_102
_102
for (const { folderName, assets } of assetDetails) {
_102
const folderId = await createAssetFolder(siteId, folderName);
_102
if (folderId) {
_102
for (const assetUrl of assets) {
_102
// TO ADD: Upload logic
_102
}
_102
}
_102
}
_102
})();

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

assets.js
.env

_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
_102
// Initialize Webflow client
_102
const webflow = new WebflowClient({
_102
accessToken: process.env.WEBFLOW_API_TOKEN,
_102
});
_102
_102
// Organize folders and assets
_102
const assetDetails = [
_102
{
_102
folderName: "Universal Assets",
_102
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_102
},
_102
{
_102
folderName: "English Assets",
_102
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
],
_102
},
_102
{
_102
folderName: "French Assets",
_102
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
],
_102
},
_102
];
_102
_102
// Function to create an asset folder
_102
async function createAssetFolder(siteId, folderName) {
_102
try {
_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
);
_102
if (existingFolder) {
_102
console.log(
_102
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_102
);
_102
return existingFolder.id;
_102
}
_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
});
_102
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_102
return response.id; // Return folder ID for further use
_102
} catch (error) {
_102
console.error(
_102
`Error creating or retrieving asset folder '${folderName}':`,
_102
error
_102
);
_102
}
_102
}
_102
_102
// Function to hash file data from URL
_102
async function getFileHashFromUrl(assetUrl) {
_102
_102
// Create a promise to handle asynchronous hashing of the file data
_102
return new Promise(async (resolve, reject) => {
_102
try {
_102
_102
// Fetch the file as a stream
_102
const response = await axios.get(assetUrl, { responseType: "stream" });
_102
_102
// Initialize SHA-256 hash
_102
const hash = crypto.createHash("sha256");
_102
_102
// Update the hash with each chunk of data received from the stream
_102
response.data.on("data", (data) => hash.update(data));
_102
_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
} catch (error) {
_102
reject(error);
_102
}
_102
});
_102
}
_102
_102
// Main function to execute the folder creation and asset upload
_102
(async () => {
_102
const siteId = process.env.SITE_ID;
_102
_102
for (const { folderName, assets } of assetDetails) {
_102
const folderId = await createAssetFolder(siteId, folderName);
_102
if (folderId) {
_102
for (const assetUrl of assets) {
_102
// TO ADD: Upload logic
_102
}
_102
}
_102
}
_102
})();

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

assets.js
.env

_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
_102
// Initialize Webflow client
_102
const webflow = new WebflowClient({
_102
accessToken: process.env.WEBFLOW_API_TOKEN,
_102
});
_102
_102
// Organize folders and assets
_102
const assetDetails = [
_102
{
_102
folderName: "Universal Assets",
_102
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_102
},
_102
{
_102
folderName: "English Assets",
_102
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
],
_102
},
_102
{
_102
folderName: "French Assets",
_102
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
],
_102
},
_102
];
_102
_102
// Function to create an asset folder
_102
async function createAssetFolder(siteId, folderName) {
_102
try {
_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
);
_102
if (existingFolder) {
_102
console.log(
_102
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_102
);
_102
return existingFolder.id;
_102
}
_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
});
_102
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_102
return response.id; // Return folder ID for further use
_102
} catch (error) {
_102
console.error(
_102
`Error creating or retrieving asset folder '${folderName}':`,
_102
error
_102
);
_102
}
_102
}
_102
_102
// Function to hash file data from URL
_102
async function getFileHashFromUrl(assetUrl) {
_102
_102
// Create a promise to handle asynchronous hashing of the file data
_102
return new Promise(async (resolve, reject) => {
_102
try {
_102
_102
// Fetch the file as a stream
_102
const response = await axios.get(assetUrl, { responseType: "stream" });
_102
_102
// Initialize SHA-256 hash
_102
const hash = crypto.createHash("sha256");
_102
_102
// Update the hash with each chunk of data received from the stream
_102
response.data.on("data", (data) => hash.update(data));
_102
_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
} catch (error) {
_102
reject(error);
_102
}
_102
});
_102
}
_102
_102
// Main function to execute the folder creation and asset upload
_102
(async () => {
_102
const siteId = process.env.SITE_ID;
_102
_102
for (const { folderName, assets } of assetDetails) {
_102
const folderId = await createAssetFolder(siteId, folderName);
_102
if (folderId) {
_102
for (const assetUrl of assets) {
_102
// TO ADD: Upload logic
_102
}
_102
}
_102
}
_102
})();

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

assets.js
.env

_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
_160
// Initialize Webflow client
_160
const webflow = new WebflowClient({
_160
accessToken: process.env.WEBFLOW_API_TOKEN,
_160
});
_160
_160
// Organize folders and assets
_160
const assetDetails = [
_160
{
_160
folderName: "Universal Assets",
_160
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_160
},
_160
{
_160
folderName: "English Assets",
_160
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
],
_160
},
_160
{
_160
folderName: "French Assets",
_160
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
],
_160
},
_160
];
_160
_160
// Function to create an asset folder
_160
async function createAssetFolder(siteId, folderName) {
_160
try {
_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
);
_160
if (existingFolder) {
_160
console.log(
_160
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_160
);
_160
return existingFolder.id;
_160
}
_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
});
_160
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_160
return response.id; // Return folder ID for further use
_160
} catch (error) {
_160
console.error(
_160
`Error creating or retrieving asset folder '${folderName}':`,
_160
error
_160
);
_160
}
_160
}
_160
_160
// Function to hash file data from URL
_160
async function getFileHashFromUrl(assetUrl) {
_160
_160
// Create a promise to handle asynchronous hashing of the file data
_160
return new Promise(async (resolve, reject) => {
_160
try {
_160
_160
// Fetch the file as a stream
_160
const response = await axios.get(assetUrl, { responseType: "stream" });
_160
_160
// Initialize SHA-256 hash
_160
const hash = crypto.createHash("sha256");
_160
_160
// Update the hash with each chunk of data received from the stream
_160
response.data.on("data", (data) => hash.update(data));
_160
_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
} catch (error) {
_160
reject(error);
_160
}
_160
});
_160
}
_160
_160
// Function to upload an asset to Webflow via S3
_160
async function uploadAsset(siteId, folderId, assetUrl) {
_160
try {
_160
// Generate the file hash for validation
_160
const fileHash = await getFileHashFromUrl(assetUrl);
_160
const fileName = assetUrl.split("/").pop();
_160
_160
// Step 1: Initialize the upload
_160
const uploadInit = await webflow.assets.create(siteId, {
_160
parentFolder: folderId,
_160
fileName: fileName + `.jpeg`,
_160
fileHash: fileHash,
_160
});
_160
const { uploadUrl, uploadDetails } = uploadInit;
_160
_160
// Create form data for S3 upload
_160
const form = new FormData();
_160
_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
_160
// Append the file to be uploaded
_160
const response = await axios.get(assetUrl, { responseType: "stream" });
_160
form.append("file", response.data, {
_160
filename: fileName,
_160
contentType: uploadDetails.contentType,
_160
});
_160
console.log(response);
_160
_160
// Step 2: Upload to the provided S3 URL
_160
const uploadResponse = await axios.post(uploadUrl, form, {
_160
headers: {
_160
...form.getHeaders(),
_160
},
_160
});
_160
_160
if (uploadResponse.status === 201) {
_160
console.log(`Successfully uploaded ${fileName} to Webflow.`);
_160
} else {
_160
console.error(
_160
`Failed to upload ${fileName}. Response status: ${uploadResponse.status}`
_160
);
_160
}
_160
} catch (error) {
_160
console.error(`Error uploading asset from ${assetUrl}:`, error);
_160
}
_160
}
_160
_160
// Main function to execute the folder creation and asset upload
_160
(async () => {
_160
const siteId = process.env.SITE_ID;
_160
_160
for (const { folderName, assets } of assetDetails) {
_160
const folderId = await createAssetFolder(siteId, folderName);
_160
if (folderId) {
_160
for (const assetUrl of assets) {
_160
await uploadAsset(siteId, folderId, assetUrl);
_160
}
_160
}
_160
}
_160
})();

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:

  1. 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.
  2. Upload the File to S3: Use the provided URL and Amazon headers to upload the actual file to Amazon S3.

1. Create uploadAsset() function

assets.js
.env

_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
_160
// Initialize Webflow client
_160
const webflow = new WebflowClient({
_160
accessToken: process.env.WEBFLOW_API_TOKEN,
_160
});
_160
_160
// Organize folders and assets
_160
const assetDetails = [
_160
{
_160
folderName: "Universal Assets",
_160
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_160
},
_160
{
_160
folderName: "English Assets",
_160
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
],
_160
},
_160
{
_160
folderName: "French Assets",
_160
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
],
_160
},
_160
];
_160
_160
// Function to create an asset folder
_160
async function createAssetFolder(siteId, folderName) {
_160
try {
_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
);
_160
if (existingFolder) {
_160
console.log(
_160
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_160
);
_160
return existingFolder.id;
_160
}
_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
});
_160
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_160
return response.id; // Return folder ID for further use
_160
} catch (error) {
_160
console.error(
_160
`Error creating or retrieving asset folder '${folderName}':`,
_160
error
_160
);
_160
}
_160
}
_160
_160
// Function to hash file data from URL
_160
async function getFileHashFromUrl(assetUrl) {
_160
_160
// Create a promise to handle asynchronous hashing of the file data
_160
return new Promise(async (resolve, reject) => {
_160
try {
_160
_160
// Fetch the file as a stream
_160
const response = await axios.get(assetUrl, { responseType: "stream" });
_160
_160
// Initialize SHA-256 hash
_160
const hash = crypto.createHash("sha256");
_160
_160
// Update the hash with each chunk of data received from the stream
_160
response.data.on("data", (data) => hash.update(data));
_160
_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
} catch (error) {
_160
reject(error);
_160
}
_160
});
_160
}
_160
_160
// Function to upload an asset to Webflow via S3
_160
async function uploadAsset(siteId, folderId, assetUrl) {
_160
try {
_160
// Generate the file hash for validation
_160
const fileHash = await getFileHashFromUrl(assetUrl);
_160
const fileName = assetUrl.split("/").pop();
_160
_160
// Step 1: Initialize the upload
_160
const uploadInit = await webflow.assets.create(siteId, {
_160
parentFolder: folderId,
_160
fileName: fileName + `.jpeg`,
_160
fileHash: fileHash,
_160
});
_160
const { uploadUrl, uploadDetails } = uploadInit;
_160
_160
// Create form data for S3 upload
_160
const form = new FormData();
_160
_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
_160
// Append the file to be uploaded
_160
const response = await axios.get(assetUrl, { responseType: "stream" });
_160
form.append("file", response.data, {
_160
filename: fileName,
_160
contentType: uploadDetails.contentType,
_160
});
_160
console.log(response);
_160
_160
// Step 2: Upload to the provided S3 URL
_160
const uploadResponse = await axios.post(uploadUrl, form, {
_160
headers: {
_160
...form.getHeaders(),
_160
},
_160
});
_160
_160
if (uploadResponse.status === 201) {
_160
console.log(`Successfully uploaded ${fileName} to Webflow.`);
_160
} else {
_160
console.error(
_160
`Failed to upload ${fileName}. Response status: ${uploadResponse.status}`
_160
);
_160
}
_160
} catch (error) {
_160
console.error(`Error uploading asset from ${assetUrl}:`, error);
_160
}
_160
}
_160
_160
// Main function to execute the folder creation and asset upload
_160
(async () => {
_160
const siteId = process.env.SITE_ID;
_160
_160
for (const { folderName, assets } of assetDetails) {
_160
const folderId = await createAssetFolder(siteId, folderName);
_160
if (folderId) {
_160
for (const assetUrl of assets) {
_160
await uploadAsset(siteId, folderId, assetUrl);
_160
}
_160
}
_160
}
_160
})();

Define an asynchronous function called uploadAsset() that takes the following arguments: siteId, folderId, and assetUrl.

2. Get file hash and file name

assets.js
.env

_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
_160
// Initialize Webflow client
_160
const webflow = new WebflowClient({
_160
accessToken: process.env.WEBFLOW_API_TOKEN,
_160
});
_160
_160
// Organize folders and assets
_160
const assetDetails = [
_160
{
_160
folderName: "Universal Assets",
_160
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_160
},
_160
{
_160
folderName: "English Assets",
_160
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
],
_160
},
_160
{
_160
folderName: "French Assets",
_160
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
],
_160
},
_160
];
_160
_160
// Function to create an asset folder
_160
async function createAssetFolder(siteId, folderName) {
_160
try {
_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
);
_160
if (existingFolder) {
_160
console.log(
_160
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_160
);
_160
return existingFolder.id;
_160
}
_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
});
_160
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_160
return response.id; // Return folder ID for further use
_160
} catch (error) {
_160
console.error(
_160
`Error creating or retrieving asset folder '${folderName}':`,
_160
error
_160
);
_160
}
_160
}
_160
_160
// Function to hash file data from URL
_160
async function getFileHashFromUrl(assetUrl) {
_160
_160
// Create a promise to handle asynchronous hashing of the file data
_160
return new Promise(async (resolve, reject) => {
_160
try {
_160
_160
// Fetch the file as a stream
_160
const response = await axios.get(assetUrl, { responseType: "stream" });
_160
_160
// Initialize SHA-256 hash
_160
const hash = crypto.createHash("sha256");
_160
_160
// Update the hash with each chunk of data received from the stream
_160
response.data.on("data", (data) => hash.update(data));
_160
_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
} catch (error) {
_160
reject(error);
_160
}
_160
});
_160
}
_160
_160
// Function to upload an asset to Webflow via S3
_160
async function uploadAsset(siteId, folderId, assetUrl) {
_160
try {
_160
// Generate the file hash for validation
_160
const fileHash = await getFileHashFromUrl(assetUrl);
_160
const fileName = assetUrl.split("/").pop();
_160
_160
// Step 1: Initialize the upload
_160
const uploadInit = await webflow.assets.create(siteId, {
_160
parentFolder: folderId,
_160
fileName: fileName + `.jpeg`,
_160
fileHash: fileHash,
_160
});
_160
const { uploadUrl, uploadDetails } = uploadInit;
_160
_160
// Create form data for S3 upload
_160
const form = new FormData();
_160
_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
_160
// Append the file to be uploaded
_160
const response = await axios.get(assetUrl, { responseType: "stream" });
_160
form.append("file", response.data, {
_160
filename: fileName,
_160
contentType: uploadDetails.contentType,
_160
});
_160
console.log(response);
_160
_160
// Step 2: Upload to the provided S3 URL
_160
const uploadResponse = await axios.post(uploadUrl, form, {
_160
headers: {
_160
...form.getHeaders(),
_160
},
_160
});
_160
_160
if (uploadResponse.status === 201) {
_160
console.log(`Successfully uploaded ${fileName} to Webflow.`);
_160
} else {
_160
console.error(
_160
`Failed to upload ${fileName}. Response status: ${uploadResponse.status}`
_160
);
_160
}
_160
} catch (error) {
_160
console.error(`Error uploading asset from ${assetUrl}:`, error);
_160
}
_160
}
_160
_160
// Main function to execute the folder creation and asset upload
_160
(async () => {
_160
const siteId = process.env.SITE_ID;
_160
_160
for (const { folderName, assets } of assetDetails) {
_160
const folderId = await createAssetFolder(siteId, folderName);
_160
if (folderId) {
_160
for (const assetUrl of assets) {
_160
await uploadAsset(siteId, folderId, assetUrl);
_160
}
_160
}
_160
}
_160
})();

Use the getFileHashFromUrl() function to generate the file hash. Extract the file name from the URL.

3. Upload Metadata to Webflow

assets.js
.env

_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
_160
// Initialize Webflow client
_160
const webflow = new WebflowClient({
_160
accessToken: process.env.WEBFLOW_API_TOKEN,
_160
});
_160
_160
// Organize folders and assets
_160
const assetDetails = [
_160
{
_160
folderName: "Universal Assets",
_160
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_160
},
_160
{
_160
folderName: "English Assets",
_160
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
],
_160
},
_160
{
_160
folderName: "French Assets",
_160
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
],
_160
},
_160
];
_160
_160
// Function to create an asset folder
_160
async function createAssetFolder(siteId, folderName) {
_160
try {
_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
);
_160
if (existingFolder) {
_160
console.log(
_160
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_160
);
_160
return existingFolder.id;
_160
}
_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
});
_160
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_160
return response.id; // Return folder ID for further use
_160
} catch (error) {
_160
console.error(
_160
`Error creating or retrieving asset folder '${folderName}':`,
_160
error
_160
);
_160
}
_160
}
_160
_160
// Function to hash file data from URL
_160
async function getFileHashFromUrl(assetUrl) {
_160
_160
// Create a promise to handle asynchronous hashing of the file data
_160
return new Promise(async (resolve, reject) => {
_160
try {
_160
_160
// Fetch the file as a stream
_160
const response = await axios.get(assetUrl, { responseType: "stream" });
_160
_160
// Initialize SHA-256 hash
_160
const hash = crypto.createHash("sha256");
_160
_160
// Update the hash with each chunk of data received from the stream
_160
response.data.on("data", (data) => hash.update(data));
_160
_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
} catch (error) {
_160
reject(error);
_160
}
_160
});
_160
}
_160
_160
// Function to upload an asset to Webflow via S3
_160
async function uploadAsset(siteId, folderId, assetUrl) {
_160
try {
_160
// Generate the file hash for validation
_160
const fileHash = await getFileHashFromUrl(assetUrl);
_160
const fileName = assetUrl.split("/").pop();
_160
_160
// Step 1: Initialize the upload
_160
const uploadInit = await webflow.assets.create(siteId, {
_160
parentFolder: folderId,
_160
fileName: fileName + `.jpeg`,
_160
fileHash: fileHash,
_160
});
_160
const { uploadUrl, uploadDetails } = uploadInit;
_160
_160
// Create form data for S3 upload
_160
const form = new FormData();
_160
_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
_160
// Append the file to be uploaded
_160
const response = await axios.get(assetUrl, { responseType: "stream" });
_160
form.append("file", response.data, {
_160
filename: fileName,
_160
contentType: uploadDetails.contentType,
_160
});
_160
console.log(response);
_160
_160
// Step 2: Upload to the provided S3 URL
_160
const uploadResponse = await axios.post(uploadUrl, form, {
_160
headers: {
_160
...form.getHeaders(),
_160
},
_160
});
_160
_160
if (uploadResponse.status === 201) {
_160
console.log(`Successfully uploaded ${fileName} to Webflow.`);
_160
} else {
_160
console.error(
_160
`Failed to upload ${fileName}. Response status: ${uploadResponse.status}`
_160
);
_160
}
_160
} catch (error) {
_160
console.error(`Error uploading asset from ${assetUrl}:`, error);
_160
}
_160
}
_160
_160
// Main function to execute the folder creation and asset upload
_160
(async () => {
_160
const siteId = process.env.SITE_ID;
_160
_160
for (const { folderName, assets } of assetDetails) {
_160
const folderId = await createAssetFolder(siteId, folderName);
_160
if (folderId) {
_160
for (const assetUrl of assets) {
_160
await uploadAsset(siteId, folderId, assetUrl);
_160
}
_160
}
_160
}
_160
})();

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

assets.js
.env

_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
_160
// Initialize Webflow client
_160
const webflow = new WebflowClient({
_160
accessToken: process.env.WEBFLOW_API_TOKEN,
_160
});
_160
_160
// Organize folders and assets
_160
const assetDetails = [
_160
{
_160
folderName: "Universal Assets",
_160
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_160
},
_160
{
_160
folderName: "English Assets",
_160
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
],
_160
},
_160
{
_160
folderName: "French Assets",
_160
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
],
_160
},
_160
];
_160
_160
// Function to create an asset folder
_160
async function createAssetFolder(siteId, folderName) {
_160
try {
_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
);
_160
if (existingFolder) {
_160
console.log(
_160
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_160
);
_160
return existingFolder.id;
_160
}
_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
});
_160
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_160
return response.id; // Return folder ID for further use
_160
} catch (error) {
_160
console.error(
_160
`Error creating or retrieving asset folder '${folderName}':`,
_160
error
_160
);
_160
}
_160
}
_160
_160
// Function to hash file data from URL
_160
async function getFileHashFromUrl(assetUrl) {
_160
_160
// Create a promise to handle asynchronous hashing of the file data
_160
return new Promise(async (resolve, reject) => {
_160
try {
_160
_160
// Fetch the file as a stream
_160
const response = await axios.get(assetUrl, { responseType: "stream" });
_160
_160
// Initialize SHA-256 hash
_160
const hash = crypto.createHash("sha256");
_160
_160
// Update the hash with each chunk of data received from the stream
_160
response.data.on("data", (data) => hash.update(data));
_160
_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
} catch (error) {
_160
reject(error);
_160
}
_160
});
_160
}
_160
_160
// Function to upload an asset to Webflow via S3
_160
async function uploadAsset(siteId, folderId, assetUrl) {
_160
try {
_160
// Generate the file hash for validation
_160
const fileHash = await getFileHashFromUrl(assetUrl);
_160
const fileName = assetUrl.split("/").pop();
_160
_160
// Step 1: Initialize the upload
_160
const uploadInit = await webflow.assets.create(siteId, {
_160
parentFolder: folderId,
_160
fileName: fileName + `.jpeg`,
_160
fileHash: fileHash,
_160
});
_160
const { uploadUrl, uploadDetails } = uploadInit;
_160
_160
// Create form data for S3 upload
_160
const form = new FormData();
_160
_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
_160
// Append the file to be uploaded
_160
const response = await axios.get(assetUrl, { responseType: "stream" });
_160
form.append("file", response.data, {
_160
filename: fileName,
_160
contentType: uploadDetails.contentType,
_160
});
_160
console.log(response);
_160
_160
// Step 2: Upload to the provided S3 URL
_160
const uploadResponse = await axios.post(uploadUrl, form, {
_160
headers: {
_160
...form.getHeaders(),
_160
},
_160
});
_160
_160
if (uploadResponse.status === 201) {
_160
console.log(`Successfully uploaded ${fileName} to Webflow.`);
_160
} else {
_160
console.error(
_160
`Failed to upload ${fileName}. Response status: ${uploadResponse.status}`
_160
);
_160
}
_160
} catch (error) {
_160
console.error(`Error uploading asset from ${assetUrl}:`, error);
_160
}
_160
}
_160
_160
// Main function to execute the folder creation and asset upload
_160
(async () => {
_160
const siteId = process.env.SITE_ID;
_160
_160
for (const { folderName, assets } of assetDetails) {
_160
const folderId = await createAssetFolder(siteId, folderName);
_160
if (folderId) {
_160
for (const assetUrl of assets) {
_160
await uploadAsset(siteId, folderId, assetUrl);
_160
}
_160
}
_160
}
_160
})();

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

assets.js
.env

_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
_160
// Initialize Webflow client
_160
const webflow = new WebflowClient({
_160
accessToken: process.env.WEBFLOW_API_TOKEN,
_160
});
_160
_160
// Organize folders and assets
_160
const assetDetails = [
_160
{
_160
folderName: "Universal Assets",
_160
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_160
},
_160
{
_160
folderName: "English Assets",
_160
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
],
_160
},
_160
{
_160
folderName: "French Assets",
_160
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
],
_160
},
_160
];
_160
_160
// Function to create an asset folder
_160
async function createAssetFolder(siteId, folderName) {
_160
try {
_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
);
_160
if (existingFolder) {
_160
console.log(
_160
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_160
);
_160
return existingFolder.id;
_160
}
_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
});
_160
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_160
return response.id; // Return folder ID for further use
_160
} catch (error) {
_160
console.error(
_160
`Error creating or retrieving asset folder '${folderName}':`,
_160
error
_160
);
_160
}
_160
}
_160
_160
// Function to hash file data from URL
_160
async function getFileHashFromUrl(assetUrl) {
_160
_160
// Create a promise to handle asynchronous hashing of the file data
_160
return new Promise(async (resolve, reject) => {
_160
try {
_160
_160
// Fetch the file as a stream
_160
const response = await axios.get(assetUrl, { responseType: "stream" });
_160
_160
// Initialize SHA-256 hash
_160
const hash = crypto.createHash("sha256");
_160
_160
// Update the hash with each chunk of data received from the stream
_160
response.data.on("data", (data) => hash.update(data));
_160
_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
} catch (error) {
_160
reject(error);
_160
}
_160
});
_160
}
_160
_160
// Function to upload an asset to Webflow via S3
_160
async function uploadAsset(siteId, folderId, assetUrl) {
_160
try {
_160
// Generate the file hash for validation
_160
const fileHash = await getFileHashFromUrl(assetUrl);
_160
const fileName = assetUrl.split("/").pop();
_160
_160
// Step 1: Initialize the upload
_160
const uploadInit = await webflow.assets.create(siteId, {
_160
parentFolder: folderId,
_160
fileName: fileName + `.jpeg`,
_160
fileHash: fileHash,
_160
});
_160
const { uploadUrl, uploadDetails } = uploadInit;
_160
_160
// Create form data for S3 upload
_160
const form = new FormData();
_160
_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
_160
// Append the file to be uploaded
_160
const response = await axios.get(assetUrl, { responseType: "stream" });
_160
form.append("file", response.data, {
_160
filename: fileName,
_160
contentType: uploadDetails.contentType,
_160
});
_160
console.log(response);
_160
_160
// Step 2: Upload to the provided S3 URL
_160
const uploadResponse = await axios.post(uploadUrl, form, {
_160
headers: {
_160
...form.getHeaders(),
_160
},
_160
});
_160
_160
if (uploadResponse.status === 201) {
_160
console.log(`Successfully uploaded ${fileName} to Webflow.`);
_160
} else {
_160
console.error(
_160
`Failed to upload ${fileName}. Response status: ${uploadResponse.status}`
_160
);
_160
}
_160
} catch (error) {
_160
console.error(`Error uploading asset from ${assetUrl}:`, error);
_160
}
_160
}
_160
_160
// Main function to execute the folder creation and asset upload
_160
(async () => {
_160
const siteId = process.env.SITE_ID;
_160
_160
for (const { folderName, assets } of assetDetails) {
_160
const folderId = await createAssetFolder(siteId, folderName);
_160
if (folderId) {
_160
for (const assetUrl of assets) {
_160
await uploadAsset(siteId, folderId, assetUrl);
_160
}
_160
}
_160
}
_160
})();

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

assets.js
.env

_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
_160
// Initialize Webflow client
_160
const webflow = new WebflowClient({
_160
accessToken: process.env.WEBFLOW_API_TOKEN,
_160
});
_160
_160
// Organize folders and assets
_160
const assetDetails = [
_160
{
_160
folderName: "Universal Assets",
_160
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_160
},
_160
{
_160
folderName: "English Assets",
_160
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
],
_160
},
_160
{
_160
folderName: "French Assets",
_160
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
],
_160
},
_160
];
_160
_160
// Function to create an asset folder
_160
async function createAssetFolder(siteId, folderName) {
_160
try {
_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
);
_160
if (existingFolder) {
_160
console.log(
_160
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_160
);
_160
return existingFolder.id;
_160
}
_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
});
_160
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_160
return response.id; // Return folder ID for further use
_160
} catch (error) {
_160
console.error(
_160
`Error creating or retrieving asset folder '${folderName}':`,
_160
error
_160
);
_160
}
_160
}
_160
_160
// Function to hash file data from URL
_160
async function getFileHashFromUrl(assetUrl) {
_160
_160
// Create a promise to handle asynchronous hashing of the file data
_160
return new Promise(async (resolve, reject) => {
_160
try {
_160
_160
// Fetch the file as a stream
_160
const response = await axios.get(assetUrl, { responseType: "stream" });
_160
_160
// Initialize SHA-256 hash
_160
const hash = crypto.createHash("sha256");
_160
_160
// Update the hash with each chunk of data received from the stream
_160
response.data.on("data", (data) => hash.update(data));
_160
_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
} catch (error) {
_160
reject(error);
_160
}
_160
});
_160
}
_160
_160
// Function to upload an asset to Webflow via S3
_160
async function uploadAsset(siteId, folderId, assetUrl) {
_160
try {
_160
// Generate the file hash for validation
_160
const fileHash = await getFileHashFromUrl(assetUrl);
_160
const fileName = assetUrl.split("/").pop();
_160
_160
// Step 1: Initialize the upload
_160
const uploadInit = await webflow.assets.create(siteId, {
_160
parentFolder: folderId,
_160
fileName: fileName + `.jpeg`,
_160
fileHash: fileHash,
_160
});
_160
const { uploadUrl, uploadDetails } = uploadInit;
_160
_160
// Create form data for S3 upload
_160
const form = new FormData();
_160
_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
_160
// Append the file to be uploaded
_160
const response = await axios.get(assetUrl, { responseType: "stream" });
_160
form.append("file", response.data, {
_160
filename: fileName,
_160
contentType: uploadDetails.contentType,
_160
});
_160
console.log(response);
_160
_160
// Step 2: Upload to the provided S3 URL
_160
const uploadResponse = await axios.post(uploadUrl, form, {
_160
headers: {
_160
...form.getHeaders(),
_160
},
_160
});
_160
_160
if (uploadResponse.status === 201) {
_160
console.log(`Successfully uploaded ${fileName} to Webflow.`);
_160
} else {
_160
console.error(
_160
`Failed to upload ${fileName}. Response status: ${uploadResponse.status}`
_160
);
_160
}
_160
} catch (error) {
_160
console.error(`Error uploading asset from ${assetUrl}:`, error);
_160
}
_160
}
_160
_160
// Main function to execute the folder creation and asset upload
_160
(async () => {
_160
const siteId = process.env.SITE_ID;
_160
_160
for (const { folderName, assets } of assetDetails) {
_160
const folderId = await createAssetFolder(siteId, folderName);
_160
if (folderId) {
_160
for (const assetUrl of assets) {
_160
await uploadAsset(siteId, folderId, assetUrl);
_160
}
_160
}
_160
}
_160
})();

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:

$ node assets.js

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.

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:

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.

$ touch assets.js

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.

6. Initialize the Webflow client

Initialize the Webflow client using the API token from your .env file.

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.

Create asset folders

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()

This function, createAssetFolder(), takes the site ID and folder name as arguments.

2. Check for existing asset folders

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

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

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

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

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

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

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

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

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:

  1. 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.
  2. Upload the File to S3: Use the provided URL and Amazon headers to upload the actual file to Amazon S3.

1. Create uploadAsset() function

Define an asynchronous function called uploadAsset() that takes the following arguments: siteId, folderId, and assetUrl.

2. Get file hash and file name

Use the getFileHashFromUrl() function to generate the file hash. Extract the file name from the URL.

3. Upload Metadata to Webflow

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

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

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

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:

$ node assets.js

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.

assets.js
.env
ExpandClose

_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
_155
const webflow = new WebflowClient({
_155
accessToken: process.env.WEBFLOW_API_TOKEN,
_155
});
_155
_155
// Organize folders and assets
_155
const assetDetails = [
_155
{
_155
folderName: "Universal Assets",
_155
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
_155
},
_155
{
_155
folderName: "English Assets",
_155
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
],
_155
},
_155
{
_155
folderName: "French Assets",
_155
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
],
_155
},
_155
];
_155
_155
// Function to hash file data from URL
_155
async function getFileHashFromUrl(assetUrl) {
_155
_155
// Create a promise to handle asynchronous hashing of the file data
_155
return new Promise(async (resolve, reject) => {
_155
try {
_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
} catch (error) {
_155
reject(error);
_155
}
_155
});
_155
}
_155
_155
// Function to create an asset folder
_155
async function createAssetFolder(siteId, folderName) {
_155
try {
_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
);
_155
if (existingFolder) {
_155
console.log(
_155
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
_155
);
_155
return existingFolder.id;
_155
}
_155
_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
});
_155
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
_155
return response.id; // Return folder ID for further use
_155
} catch (error) {
_155
console.error(
_155
`Error creating or retrieving asset folder '${folderName}':`,
_155
error
_155
);
_155
}
_155
}
_155
_155
// Function to upload an asset to Webflow via S3
_155
async function uploadAsset(siteId, folderId, assetUrl) {
_155
try {
_155
// Generate the file hash for validation
_155
const fileHash = await getFileHashFromUrl(assetUrl);
_155
const fileName = assetUrl.split("/").pop();
_155
_155
// Step 1: Initialize the upload
_155
const uploadInit = await webflow.assets.create(siteId, {
_155
parentFolder: folderId,
_155
fileName: fileName + `.jpeg`,
_155
fileHash: fileHash,
_155
});
_155
const { uploadUrl, uploadDetails } = uploadInit;
_155
_155
// Create form data for S3 upload
_155
const form = new FormData();
_155
_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
_155
// Append the file to be uploaded
_155
const response = await axios.get(assetUrl, { responseType: "stream" });
_155
form.append("file", response.data, {
_155
filename: fileName,
_155
contentType: uploadDetails.contentType,
_155
});
_155
console.log(response);
_155
_155
// Step 2: Upload to the provided S3 URL
_155
const uploadResponse = await axios.post(uploadUrl, form, {
_155
headers: {
_155
...form.getHeaders(),
_155
},
_155
});
_155
_155
if (uploadResponse.status === 201) {
_155
console.log(`Successfully uploaded ${fileName} to Webflow.`);
_155
} else {
_155
console.error(
_155
`Failed to upload ${fileName}. Response status: ${uploadResponse.status}`
_155
);
_155
}
_155
} catch (error) {
_155
console.error(`Error uploading asset from ${assetUrl}:`, error);
_155
}
_155
}
_155
_155
// Main function to execute the upload
_155
(async () => {
_155
const siteId = process.env.SITE_ID; // Replace with your actual site ID
_155
_155
for (const { folderName, assets } of assetDetails) {
_155
const folderId = await createAssetFolder(siteId, folderName);
_155
if (folderId) {
_155
for (const assetUrl of assets) {
_155
await uploadAsset(siteId, folderId, assetUrl);
_155
}
_155
}
_155
}
_155
})();