CMS and websites
Upload images to Webflow and use SocialCutter
Guide to uploading a master image to Webflow Assets with the v2 API, calling SocialCutter and using the correct outputs in a CMS Collection or on page images.
- Webflow
- API v2
- Assets
- CMS Collection
- SocialCutter
- feature image
- images
The problem: one master, many sizes
Webflow lets you upload an image and place it in a CMS Collection or in a page’s image field. What it does not do for you is produce that same image at the sizes each network expects: 1200x630 for a Facebook card, 1080x1920 for TikTok, 1200x675 for X. If you upload a single JPG and reuse it everywhere, you end up with forced crops or a cut-off subject.
This guide’s flow uploads one master to Webflow Assets, processes it with SocialCutter and uses each output where it belongs. One source, every size correct.
Requirements and site token
You need the Webflow Data API v2 (base https://api.webflow.com/v2) and a site token. Create it under Site settings → Apps & integrations → API access and enable the scopes we use:
| Scope | Purpose |
|---|---|
assets:read / assets:write | Create and read Assets |
cms:read / cms:write | Read and write collection items |
sites:read / sites:write | Resolve the site_id and publish the site |
The exact scope names are shown on the token creation screen and may vary between versions. The official reference is at https://developers.webflow.com/data/reference. API v2 replaces the old v1: if you find examples with /sites/{site_id}/assets without the /v2 prefix, they belong to the retired version.
Store the token and the site_id in variables:
export WEBFLOW_TOKEN="your_site_token"
export SITE_ID="your_site_id"
export API_URL="https://api.socialcutter.theboomer.dev"
export API_KEY="sc_your_key"
1. Upload the master to Assets
Uploading in the v2 API takes two steps, exactly as the official Upload Asset reference describes: first you create the asset record and the API returns an upload URL with the form details; then you send the file as multipart to that URL.
POST https://api.webflow.com/v2/sites/{site_id}/assets · scope assets:write
| Field | Required | What it is |
|---|---|---|
fileName | Yes | File name including the extension; under 100 characters |
fileHash | Yes | MD5 hash of the file contents |
parentFolder | No | ID of the Asset folder the file lands in |
Create the asset
curl -s -X POST "https://api.webflow.com/v2/sites/$SITE_ID/assets" \
-H "Authorization: Bearer $WEBFLOW_TOKEN" \
-H "accept-version: 2.0.0" \
-H "Content-Type: application/json" \
-d '{
"fileName": "master.jpg",
"fileHash": "md5_hash_of_the_file",
"parentFolder": "asset_folder_id"
}' > asset.json
jq '{id, contentType, uploadUrl, assetUrl, hostedUrl, parentFolder}' asset.json
The 200 response carries, among others, these fields:
| Field | What it is |
|---|---|
id | Asset identifier; you use it later to read the asset or change its alt text |
uploadUrl | Temporary presigned Amazon S3 URL the binary is sent to |
uploadDetails | Metadata for uploading the asset binary: the form fields to send with the file |
assetUrl | S3 link to the asset |
hostedUrl | Link to the asset, the one you reference |
parentFolder | Parent folder for the asset |
contentType, originalFileName, createdOn, lastUpdated | Type, original file name and dates |
The documentation is explicit: you must use uploadUrl and uploadDetails in the POST request to S3 to complete the upload. That URL is issued by Webflow; SocialCutter does not host your file.
The fileHash is the MD5 hash of the file contents: generate it with md5sum master.jpg (on macOS, md5 -q master.jpg). Webflow uses it to avoid duplicates: if the hash matches a file that already exists, it does not store it again. If it does not match, the upload fails with 400. parentFolder is the ID of the Assets folder and is optional.
Create the target folder (optional)
If you do not want assets to land at the site root, create the folder first:
curl -s -X POST "https://api.webflow.com/v2/sites/$SITE_ID/asset_folders" \
-H "Authorization: Bearer $WEBFLOW_TOKEN" \
-H "accept-version: 2.0.0" \
-H "Content-Type: application/json" \
-d '{ "displayName": "SocialCutter" }' > folder.json
jq '{id, displayName, parentFolder}' folder.json
The endpoint is POST /v2/sites/{site_id}/asset_folders (scope assets:write) and it accepts displayName (required) and parentFolder (optional, to nest folders). Keep the folder id and pass it as parentFolder when creating the asset.
Send the file
The uploadDetails fields must be sent exactly as given, together with the file, to the uploadUrl:
UPLOAD_URL=$(jq -r '.uploadUrl' asset.json)
jq -r '.uploadDetails | to_entries[] | "\(.key)=\(.value)"' asset.json > fields.txt
curl -s -X POST "$UPLOAD_URL" \
$(while IFS= read -r line; do printf -- "-F %s " "$line"; done < fields.txt) \
-F "file=@./master.jpg" > upload.json
Do not invent the field names: they come from uploadDetails and change with the asset type. Send exactly what the API returns.
Size limit: Webflow images must not exceed 4 MB (documents are capped at 10 MB), per the Working with Assets guide. SocialCutter accepts masters up to 5 MB, so a large master may not go straight into Assets: generate it with SocialCutter first, whose outputs are much lighter, or shrink it.
Verify the asset landed correctly
GET https://api.webflow.com/v2/assets/{asset_id} (scope assets:read) returns the detail of the uploaded asset:
curl -s "https://api.webflow.com/v2/assets/$ASSET_ID" \
-H "Authorization: Bearer $WEBFLOW_TOKEN" \
-H "accept-version: 2.0.0" \
| jq '{id, hostedUrl, contentType, size, originalFileName, altText}'
The fields that matter for verification: hostedUrl (the real link, the one you pass to SocialCutter), contentType (file format), size (size in bytes), originalFileName, altText and variants (the responsive variants Webflow creates to serve your site responsively). If hostedUrl does not load when opened, the upload to uploadUrl did not complete: repeat the POST with the uploadDetails fields and the file.
The same detail can be listed per folder. Note that folderId only appears in list responses, not when querying a single asset. The list endpoint takes folderId (a 24-character hex ObjectId) and pagination with limit (max 100) and offset:
curl -s "https://api.webflow.com/v2/sites/$SITE_ID/assets?folderId=$FOLDER_ID&limit=100" \
-H "Authorization: Bearer $WEBFLOW_TOKEN" \
-H "accept-version: 2.0.0" \
| jq '.assets[] | {id, hostedUrl, size, folderId, altText}'
Alt text and the display name are changed with PATCH https://api.webflow.com/v2/assets/{asset_id} (scope assets:write), sending altText and/or displayName.
2. Call SocialCutter with the asset URL
Once the master is in Assets, its hostedUrl is the source for SocialCutter:
curl -s -X POST "$API_URL/api/v1/images/process" \
-H "X-API-Key: *** \
-H "Content-Type: application/json" \
-d "{
\"source\": { \"type\": \"url\", \"value\": \"$(jq -r '.hostedUrl' asset.json)\" },
\"destinations\": [
{ \"platform\": \"facebook\", \"format\": \"link\" },
{ \"platform\": \"instagram\", \"format\": \"post\" },
{ \"platform\": \"twitter\", \"format\": \"summary_large_image\" }
]
}" > sc.json
jq '.image_id, (.outputs[] | {url, platform, format, width, height})' sc.json
Each element of outputs carries the output URL, its platform, its format and its dimensions. The cover crop (the default) is centered.
3. Publish the site
New Assets and created items do not show on the published site until you publish it:
curl -s -X POST "https://api.webflow.com/v2/sites/$SITE_ID/publish" \
-H "Authorization: Bearer $WEBFLOW_TOKEN" \
-H "accept-version: 2.0.0" \
-H "Content-Type: application/json" \
-d '{ "publishToWebflowSubdomain": true, "customDomains": ["your-domain.com"] }'
Adjust customDomains to the project’s real domains.
4. Use the outputs in the CMS Collection
If the CMS Collection has an image field, there are two routes: upload each output as an Asset (repeating step 1) and reference its id, or pass the URL directly if your field accepts it. Check the collection schema before building the item:
# List collections
curl -s "https://api.webflow.com/v2/sites/$SITE_ID/collections" \
-H "Authorization: Bearer $WEBFLOW_TOKEN" \
-H "accept-version: 2.0.0" | jq '.collections[] | {id, slug}'
# Schema of one collection (fields and types)
curl -s "https://api.webflow.com/v2/collections/$COLLECTION_ID" \
-H "Authorization: Bearer $WEBFLOW_TOKEN" \
-H "accept-version: 2.0.0" | jq '.fields[] | {slug, type}'
Create the live item with the image field value taken from outputs[0].url:
curl -s -X POST "https://api.webflow.com/v2/collections/$COLLECTION_ID/items/live" \
-H "Authorization: Bearer $WEBFLOW_TOKEN" \
-H "accept-version: 2.0.0" \
-H "Content-Type: application/json" \
-d "{
\"fieldData\": {
\"name\": \"Sample post\",
\"slug\": \"sample-post\",
\"image\": \"$(jq -r '.outputs[0].url' sc.json)\"
}
}"
The real name of the image field is the slug the schema returns; it is not necessarily called image.
5. Use the outputs as page images
For a standalone image on a page, upload the output to Assets and use its URL in the HTML. In practice the cleanest pattern is: upload the master, process with SocialCutter and upload each output once, keeping its hostedUrl to reference from the CMS or from pages.
That order is the point: the file you upload to Assets is already produced by SocialCutter at the destination’s proportion (1080x1080 for instagram post, 1200x627 for linkedin post), as a centred crop. Webflow only hosts it and creates its responsive variants, so the CDN serves files that are already at the correct size instead of re-cropped versions of the original master.
Cost
- 1 use per destination (platform and format combination) per request.
- Repeated destinations in the same request are not charged twice.
- Failed processing runs are refunded.
Common errors
| Situation | Likely cause |
|---|---|
| 401 from Webflow | Token missing, malformed or lacking the required scope (assets:write to create the asset) |
| 400 creating the asset | fileHash does not match the file’s MD5, or fileName is over 100 characters |
Asset stays empty or hostedUrl does not load | The POST to uploadUrl was not completed with the uploadDetails fields |
| Image not showing | Site not published or item still a draft |
| 401 from SocialCutter | sc_ key malformed or revoked |
| 413 from SocialCutter | Master file exceeds 5 MB |
| Master will not upload to Webflow | It exceeds Webflow’s 4 MB per-image limit |
| 429 from SocialCutter | Wallet quota exhausted |
| Empty image field | The field slug is not what you assumed: check the schema |
| An asset is duplicated or missing | Webflow uses the fileHash to avoid storing files with the same MD5 twice |
Next steps
- WordPress guide: Publish the correct sizes in WordPress
- Shopify guide: Product and blog images in Shopify
- Automation: Orchestrate the flow with n8n
- API from the terminal: Process images with curl
- Official Webflow reference: https://developers.webflow.com/data/reference
Frequently asked questions
What permissions does a Webflow site token need?
A site token created under Site settings → Apps & integrations → API access. For this flow enable the assets scopes (read and write), the cms scopes (read and write) and the sites scopes (read and write, so the site can be published). Check the exact scope names on the token screen, as they change between versions.
What does the create-asset endpoint return?
The 200 response carries id, uploadUrl (a presigned Amazon S3 URL for the binary), uploadDetails (the upload form fields), assetUrl (S3 link to the asset), hostedUrl (link to the asset), parentFolder and fields such as contentType, originalFileName and createdOn.
What is parentFolder for when creating the asset?
It is optional and it is the ID of the Assets folder the file lands in. Folders are created with POST /v2/sites/{site_id}/asset_folders, which takes displayName and an optional parentFolder and returns its id.
How do I check that the asset was uploaded correctly?
With GET /v2/assets/{asset_id}, which returns hostedUrl, contentType, size in bytes, originalFileName and altText. If hostedUrl does not load, the POST to uploadUrl was not completed with the uploadDetails fields.
Is there a size limit for Webflow asset uploads?
Yes: Webflow images must not exceed 4 MB and documents are capped at 10 MB. The SocialCutter API accepts masters up to 5 MB, so a large master may not go straight into Assets: run it through SocialCutter first, whose outputs are much smaller, or shrink it.
Can I use the Webflow asset URL as the source in SocialCutter?
Yes. After the master is uploaded, the API returns its hostedUrl; pass that URL as the source in POST /api/v1/images/process and SocialCutter will download the file from there.
Do I have to publish the site for CMS Collection images to show?
Items created through the live endpoint and a site published with POST /sites/{site_id}/publish become visible. If you only create draft items, they stay hidden until published.
How much does processing cost?
1 use per destination, meaning per platform and format combination you request. Repeated destinations in the same request are not charged twice and failures are refunded.