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
1
require("dotenv").config();
2
const { WebflowClient } = require("webflow-api");
3
const crypto = require("crypto");
4
const axios = require("axios");
5
const FormData = require("form-data");
6

7
const webflow = new WebflowClient({
8
accessToken: process.env.WEBFLOW_API_TOKEN,
9
});
10

11
// Organize folders and assets
12
const assetDetails = [
13
{
14
folderName: "Universal Assets",
15
assets: ["https://images.unsplash.com/photo-1451187580459-43490279c0fa"],
16
},
17
{
18
folderName: "English Assets",
19
assets: [
20
"https://images.unsplash.com/photo-1543109740-4bdb38fda756",
21
"https://images.unsplash.com/photo-1665410620550-c54105af7d0c",
22
"https://images.unsplash.com/photo-1526129318478-62ed807ebdf9",
23
],
24
},
25
{
26
folderName: "French Assets",
27
assets: [
28
"https://images.unsplash.com/photo-1454386608169-1c3b4edc1df8",
29
"https://images.unsplash.com/photo-1500039436846-25ae2f11882e",
30
"https://images.unsplash.com/photo-1528717663417-3742fee05a29",
31
],
32
},
33
];
34

35
// Function to hash file data from URL
36
async function getFileHashFromUrl(assetUrl) {
37

38
// Create a promise to handle asynchronous hashing of the file data
39
return new Promise(async (resolve, reject) => {
40
try {
41
const response = await axios.get(assetUrl, { responseType: "stream" });
42
// Create a SHA-256 hash instance to generate the file hash
43
const hash = crypto.createHash("sha256");
44
// Update the hash with each chunk of data received from the stream
45
response.data.on("data", (data) => hash.update(data));
46
// Finalize the hash calculation and resolve the promise with the hash value
47
response.data.on("end", () => resolve(hash.digest("hex")));
48
response.data.on("error", reject);
49
} catch (error) {
50
reject(error);
51
}
52
});
53
}
54

55
// Function to create an asset folder
56
async function createAssetFolder(siteId, folderName) {
57
try {
58
// Check if the folder already exists
59
const existingFolders = await webflow.assets.listFolders(siteId);
60
const existingFolder = existingFolders.assetFolders.find(
61
(folder) => folder.displayName === folderName
62
);
63
if (existingFolder) {
64
console.log(
65
`Folder '${folderName}' already exists with ID: ${existingFolder.id}`
66
);
67
return existingFolder.id;
68
}
69

70
// Create the folder if it does not exist
71
console.log(`Creating folder: ${folderName}`);
72
const response = await webflow.assets.createFolder(siteId, {
73
displayName: folderName,
74
});
75
console.log(`Folder '${folderName}' created with ID: ${response.id}`);
76
return response.id; // Return folder ID for further use
77
} catch (error) {
78
console.error(
79
`Error creating or retrieving asset folder '${folderName}':`,
80
error
81
);
82
}
83
}
84

85
// Function to upload an asset to Webflow via S3
86
async function uploadAsset(siteId, folderId, assetUrl) {
87
try {
88
// Generate the file hash for validation
89
const fileHash = await getFileHashFromUrl(assetUrl);
90
const fileName = assetUrl.split("/").pop();
91

92
// Step 1: Initialize the upload
93
const uploadInit = await webflow.assets.create(siteId, {
94
parentFolder: folderId,
95
fileName: fileName + `.jpeg`,
96
fileHash: fileHash,
97
});
98
const { uploadUrl, uploadDetails } = uploadInit;
99

100
// Create form data for S3 upload
101
const form = new FormData();
102

103
// Append all required fields to the form
104
form.append("acl", uploadDetails.acl);
105
form.append("bucket", uploadDetails.bucket);
106
form.append("X-Amz-Algorithm", uploadDetails.xAmzAlgorithm);
107
form.append("X-Amz-Credential", uploadDetails.xAmzCredential);
108
form.append("X-Amz-Date", uploadDetails.xAmzDate);
109
form.append("key", uploadDetails.key);
110
form.append("Policy", uploadDetails.policy);
111
form.append("X-Amz-Signature", uploadDetails.xAmzSignature);
112
form.append("success_action_status", uploadDetails.successActionStatus);
113
form.append("Content-Type", uploadDetails.contentType);
114
form.append("Cache-Control", uploadDetails.cacheControl);
115

116
// Append the file to be uploaded
117
const response = await axios.get(assetUrl, { responseType: "stream" });
118
form.append("file", response.data, {
119
filename: fileName,
120
contentType: uploadDetails.contentType,
121
});
122
console.log(response);
123

124
// Step 2: Upload to the provided S3 URL
125
const uploadResponse = await axios.post(uploadUrl, form, {
126
headers: {
127
...form.getHeaders(),
128
},
129
});
130

131
if (uploadResponse.status === 201) {
132
console.log(`Successfully uploaded ${fileName} to Webflow.`);
133
} else {
134
console.error(
135
`Failed to upload ${fileName}. Response status: ${uploadResponse.status}`
136
);
137
}
138
} catch (error) {
139
console.error(`Error uploading asset from ${assetUrl}:`, error);
140
}
141
}
142

143
// Main function to execute the upload
144
(async () => {
145
const siteId = process.env.SITE_ID; // Replace with your actual site ID
146

147
for (const { folderName, assets } of assetDetails) {
148
const folderId = await createAssetFolder(siteId, folderName);
149
if (folderId) {
150
for (const assetUrl of assets) {
151
await uploadAsset(siteId, folderId, assetUrl);
152
}
153
}
154
}
155
})();