# Run the asset pre-upload pass

https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/asset-pre-upload

Scan your saved API data and Webflow HTML export for every CDN image URL, upload them to Sanity, and save the mapping file that your content scripts will reference.

---

## Navigation

**Course:** [Migrating from Webflow to Sanity](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity) · [View as markdown](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity.md)

**Previous:** [Fetch your collection items from the API](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/fetch-collection-items-api) · **Next:** [Resolve reference fields from API data](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/resolve-reference-fields)

---

## Course Contents

1. [Introduction](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/introduction-to-webflow-migration)
2. [Audit your Webflow content](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/audit-webflow-content)
3. [Recognize Webflow's content modeling patterns](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/recognize-content-modeling-patterns)
4. [Export from Webflow and connect the Data API](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/export-webflow-connect-api)
5. [Initialize Sanity and install the migration toolkit](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/initialize-sanity-migration-toolkit)
6. [Map collections to Sanity document types](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/map-collections-to-schemas)
7. [Build a page builder with a sections array](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/page-builder-sections-array)
8. [Model custom-template pages as singletons](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/custom-template-singletons)
9. [Model navigation in Sanity](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/model-navigation)
10. [Create a siteSettings singleton](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/site-settings-singleton)
11. [Handle internal links in Portable Text](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/internal-links-portable-text)
12. [Fetch your collection items from the API](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/fetch-collection-items-api)
13. **Run the asset pre-upload pass** *(current)*
14. [Resolve reference fields from API data](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/resolve-reference-fields)
15. [Convert rich text to Portable Text](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/convert-rich-text-portable-text)
16. [Extract static page content using the page DOM API](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/extract-static-page-content)
17. [Import content with the two-pass pattern](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/two-pass-import)
18. [Validate your import in Studio](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/validate-import-studio)
19. [Set up forms, redirects, and sitemap](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/forms-redirects-sitemap)
20. [Set up draft mode and visual preview](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/draft-mode-visual-preview)
21. [Validate your import without a frontend](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/validate-migration)
22. [Launch checklist and DNS transfer](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/launch-checklist-dns)
23. [AI prompt library and resources](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/appendix-prompt-library-resources)

---

Assets must exist in Sanity before documents that reference them are created. Run this pass first, before any content migration.



The Webflow Assets API only returns files from the Site Assets panel. It misses images in CMS fields and rich text. Scan your saved API data and HTML export for all image URLs instead.



### Asset sources to scan



Images appear in three different places depending on how your Webflow site was built:



1. **Webflow CDN URLs** — most images in CMS field data and rich text. Webflow uses multiple CDN domains depending on account age; scan all three:

- `uploads-ssl.webflow.com` — most common

- `assets-global.website-files.com` — older accounts

- `cdn.prod.website-files.com` — newer accounts

2. **Local export paths** — images referenced as relative paths in the HTML export (e.g. `images/product-hero.png`). These exist at `webflow/export/images/` and need to be uploaded from disk rather than fetched from a URL.


If `asset-map.json` ends up with fewer entries than you expect, or if you see `No asset mapping for figure image` warnings during import, run this diagnostic: grep your HTML export for all `src=` and `href=` attributes and compare the domains against what the scanner matched.



### Write the scanner and uploader



```javascript:scripts/pre-upload-assets.js
// scripts/pre-upload-assets.js
import { client } from '../lib/sanity-client.js'
import { glob } from 'fs/promises'
import fs from 'fs'
import path from 'path'

// All known Webflow CDN domains — check your HTML export if you're missing assets
const CDN_PATTERNS = [
  /https:\/\/uploads-ssl\.webflow\.com\/[^\s"'<>\\]+/g,
  /https:\/\/assets-global\.website-files\.com\/[^\s"'<>\\]+/g,
  /https:\/\/cdn\.prod\.website-files\.com\/[^\s"'<>\\]+/g,
]

// Local export image paths: src="images/foo.png" → upload from webflow/export/images/foo.png
const LOCAL_IMG_PATTERN = /(?:src|href)=["'](images\/[^"']+)["']/g

const WEBFLOW_CDN_PATTERN = CDN_PATTERNS[0] // kept for backwards compat in rich-text.js

function cleanUrl(url) {
  // Strip any trailing punctuation that leaked in from HTML attribute context
  return url.replace(/[\\,;]+$/, '')
}

async function run() {
  const seen = new Set()
  const assetMap = {}

  // Scan all NDJSON item files for CDN URLs in fieldData
  const ndjsonFiles = await Array.fromAsync(glob('webflow/api/items/*.ndjson'))
  for (const filePath of ndjsonFiles) {
    const content = fs.readFileSync(filePath, 'utf8')
    for (const pattern of CDN_PATTERNS) {
      for (const match of content.matchAll(new RegExp(pattern.source, 'g'))) seen.add(cleanUrl(match[0]))
    }
  }

  // Scan HTML export files for CDN URLs and local relative paths
  const htmlFiles = await Array.fromAsync(glob('webflow/export/**/*.html'))
  const localPaths = new Set() // relative paths like "images/foo.png"
  for (const filePath of htmlFiles) {
    const content = fs.readFileSync(filePath, 'utf8')
    for (const pattern of CDN_PATTERNS) {
      for (const match of content.matchAll(new RegExp(pattern.source, 'g'))) seen.add(cleanUrl(match[0]))
    }
    // Collect local export image paths
    for (const match of content.matchAll(LOCAL_IMG_PATTERN)) localPaths.add(match[1])
  }

  console.log(`Found ${seen.size} CDN URLs + ${localPaths.size} local paths. Uploading...`)

  // Upload CDN assets
  for (const url of seen) {
    try {
      const response = await fetch(url)
      if (!response.ok) throw new Error(`HTTP ${response.status} fetching ${url}`)

      const contentType = response.headers.get('content-type') ?? 'application/octet-stream'
      const filename = decodeURIComponent(url.split('/').pop()?.split('?')[0] ?? 'asset')
      // SVGs and other non-raster files must be uploaded as 'file', not 'image'
      const assetType = /^image\/(jpeg|png|gif|webp|avif)/.test(contentType) ? 'image' : 'file'

      const buffer = Buffer.from(await response.arrayBuffer())
      const asset = await client.assets.upload(assetType, buffer, {
        filename,
        contentType,
        source: { name: 'webflow-migration', id: url }
      })
      assetMap[url] = asset._id
      console.log(`✓ ${filename}`)
    } catch (err) {
      console.warn(`✗ ${url}: ${err.message}`)
    }
  }

  // Upload local export images (e.g. images/product-hero.png)
  for (const relPath of localPaths) {
    const diskPath = path.join('webflow/export', relPath)
    if (!fs.existsSync(diskPath)) { console.warn(`✗ Local file not found: ${diskPath}`); continue }
    try {
      const buffer = fs.readFileSync(diskPath)
      const filename = path.basename(relPath)
      const asset = await client.assets.upload('image', buffer, {
        filename,
        source: { name: 'webflow-migration', id: relPath }
      })
      // Map both the relative path and a root-relative URL form so rich-text.js can look it up
      assetMap[relPath] = asset._id
      assetMap[`/${relPath}`] = asset._id
      console.log(`✓ ${filename} (local)`)
    } catch (err) {
      console.warn(`✗ ${relPath}: ${err.message}`)
    }
  }

  fs.writeFileSync('asset-map.json', JSON.stringify(assetMap, null, 2))
  console.log(`Done. asset-map.json written with ${Object.keys(assetMap).length} entries.`)
  if (Object.keys(assetMap).length === 0) {
    console.warn('⚠ asset-map.json is empty — check CDN domains in CDN_PATTERNS against your HTML export.')
  }
}

run().catch(err => { console.error(err.message); process.exit(1) })
```

Run it from the project root:



```bash
node --env-file=.env scripts/pre-upload-assets.js
```

The script logs each URL as it uploads (✓ for success, ✗ for failure) and writes asset-map.json when complete. It's safe to re-run. If interrupted, assets already uploaded are skipped on the next run because Sanity deduplicates by source ID.



Next, you’ll build the utility modules that your extraction scripts depend on.



---

## Related Resources

- [Full course as markdown](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity.md)
- [All courses and lessons](https://www.sanity.io/learn/sitemap.md)
- [Complete content for LLMs](https://www.sanity.io/learn/llms-full.txt)

<AgentInstructions>

## Submitting Feedback

If you encounter incorrect, outdated, or confusing documentation on this page, submit feedback:

POST https://www.sanity.io/learn/resource/feedback

```json
{
  "path": "/learn/course/migrating-content-from-webflow-to-sanity/asset-pre-upload.md",
  "feedback": "Description of the issue"
}
```

Only submit feedback when you have something specific and actionable to report.

</AgentInstructions>
