Skip to content

fix(tldraw): fix download original for cross-origin assets#8090

Merged
MitjaBezensek merged 3 commits intomainfrom
mitja/download-original
Feb 26, 2026
Merged

fix(tldraw): fix download original for cross-origin assets#8090
MitjaBezensek merged 3 commits intomainfrom
mitja/download-original

Conversation

@MitjaBezensek
Copy link
Copy Markdown
Contributor

@MitjaBezensek MitjaBezensek commented Feb 25, 2026

"Download original" was broken in production because assets are hosted on tldrawusercontent.com (cross-origin from tldraw.com). Browsers ignore <a download> for cross-origin URLs and navigate instead of downloading.

This replaces the <a> link approach with fetch + blob. Blob URLs are always same-origin, so <a download> works correctly. Falls back to window.open if the fetch fails (e.g. CORS blocked).

This would also work for SDK user that also host images on a separate domain, which is why I feel that doing the fix in the SDK code is the right way to go.

Change type

  • bugfix

Test plan

  1. Upload an image on tldraw.com
  2. Select it → click download icon in toolbar → browser downloads with correct filename
  3. Right-click → "Download original" → same behavior
  4. Test with video asset

Release notes

  • Fixed "Download original" not triggering a download for cross-origin assets

Note

Medium Risk
Changes the asset download flow to fetch and blob-create files, which can be affected by CORS/network failures and browser memory usage, but is scoped to a single UI action with a safe fallback.

Overview
Fixes Download original for image/video shapes when the asset URL is cross-origin by replacing the <a download> navigation approach with fetchblobFile and triggering download via the shared downloadFile helper.

Also makes multi-selection more robust by skipping shapes whose resolved URL is missing (instead of aborting the whole loop) and falling back to openWindow(url, '_blank') if the fetch/download path fails (e.g. CORS blocked).

Written by Cursor Bugbot for commit f6c3ced. This will update automatically on new commits. Configure here.

@MitjaBezensek MitjaBezensek added the dotcom-hotfix-please ⚙️ Triggers tldraw.com production deploy after merge label Feb 25, 2026
@huppy-bot huppy-bot bot added the bugfix Bug fix label Feb 25, 2026
@vercel
Copy link
Copy Markdown

vercel bot commented Feb 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
examples Ready Ready Preview Feb 26, 2026 11:16am
5 Skipped Deployments
Project Deployment Actions Updated (UTC)
analytics Ignored Ignored Preview Feb 26, 2026 11:16am
chat-template Ignored Ignored Preview Feb 26, 2026 11:16am
tldraw-docs Ignored Ignored Preview Feb 26, 2026 11:16am
tldraw-shader Ignored Ignored Preview Feb 26, 2026 11:16am
workflow-template Ignored Ignored Preview Feb 26, 2026 11:16am

Request Review

link.download = asset.props.name
} else {
link.download = 'download'
if (!url) continue
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Felt like continue is better here.

Copy link
Copy Markdown
Contributor

@kaneel kaneel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good fallback

}
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trackEvent fires when no download occurs

Low Severity

trackEvent('download-original', ...) runs after the loop even when no asset was downloaded or opened. With continue on null URL, all shapes can be skipped and the loop finishes without any user-facing action, yet the event is still recorded.

Fix in Cursor Fix in Web

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable autofix in the Cursor dashboard.

@MitjaBezensek MitjaBezensek added this pull request to the merge queue Feb 26, 2026
@MitjaBezensek MitjaBezensek removed this pull request from the merge queue due to a manual request Feb 26, 2026
@MitjaBezensek MitjaBezensek added this pull request to the merge queue Feb 26, 2026
Merged via the queue into main with commit 416d3b0 Feb 26, 2026
18 checks passed
@MitjaBezensek MitjaBezensek deleted the mitja/download-original branch February 26, 2026 11:23
huppy-bot bot pushed a commit that referenced this pull request Feb 26, 2026
"Download original" was broken in production because assets are hosted
on `tldrawusercontent.com` (cross-origin from `tldraw.com`). Browsers
ignore `<a download>` for cross-origin URLs and navigate instead of
downloading.

This replaces the `<a>` link approach with fetch + blob. Blob URLs are
always same-origin, so `<a download>` works correctly. Falls back to
`window.open` if the fetch fails (e.g. CORS blocked).

This would also work for SDK user that also host images on a separate
domain, which is why I feel that doing the fix in the SDK code is the
right way to go.

### Change type

- [x] `bugfix`

### Test plan

1. Upload an image on tldraw.com
2. Select it → click download icon in toolbar → browser downloads with
correct filename
3. Right-click → "Download original" → same behavior
4. Test with video asset

### Release notes

- Fixed "Download original" not triggering a download for cross-origin
assets

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes the asset download flow to fetch and blob-create files, which
can be affected by CORS/network failures and browser memory usage, but
is scoped to a single UI action with a safe fallback.
> 
> **Overview**
> Fixes **`Download original`** for image/video shapes when the asset
URL is cross-origin by replacing the `<a download>` navigation approach
with `fetch` → `blob` → `File` and triggering download via the shared
`downloadFile` helper.
> 
> Also makes multi-selection more robust by skipping shapes whose
resolved URL is missing (instead of aborting the whole loop) and falling
back to `openWindow(url, '_blank')` if the fetch/download path fails
(e.g. CORS blocked).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
f6c3ced. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
huppy-bot bot added a commit that referenced this pull request Feb 26, 2026
This is an automated hotfix for dotcom deployment.

Original PR: #8090
Original Author: @MitjaBezensek
steveruizok added a commit that referenced this pull request Feb 27, 2026
Add 12 new entries from PRs merged since v4.4.0:
- Featured: click-through on transparent image pixels (#7942)
- API: enum-to-const-object refactor (#8084)
- Improvements: SVG sanitizer (#7896), save-on-blur (#8037)
- Bug fixes: cross-origin download (#8090), zero-size draw (#8067),
  rich text toolbar cleanup (#8050), zoom threshold (#8040),
  selection foreground fallback (#8011), sticky note SVG shadow (#7934),
  arrow frame clamping (#7932), zero pressure draw (#5693)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Bug fix dotcom-hotfix-please ⚙️ Triggers tldraw.com production deploy after merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants