| name | wikimedia-commons-thumbnails |
| description | Generate, construct, and retrieve raster thumbnail previews for any Wikimedia Commons file — thumb URL scheme, iiurlwidth/iiurlheight Action API pattern, thumbmime format conversion matrix (raster downscale, SVG→PNG, PDF→JPEG, video keyframes), responsive/retina URLs, REST API thumbnails, SPARQL thumbnailUrl, and caching behavior |
| depends_on | ["wikimedia-api-access","wikimedia-commons"] |
| license | MIT |
| compatibility | opencode |
| skill_discovery_hints | [{"keywords":["thumbnail","thumb","preview","rasterize","render","image scaling","responsive image","Retina"]},{"keywords":["thumburl","thumbmime","iiurlwidth","iiurlheight","responsiveUrls","thumbwidth","thumbheight"]},{"keywords":["thumb.php","thumbhandler","thumbnailUrl","contentUrl","rendering pipeline","image thumbnail API"]},{"keywords":["SVG rasterize","PDF page render","DjVu thumbnail","keyframe","video thumbnail","page rendering"]},{"keywords":["resize image","downscale","preview size","Varnish cache","thumb cache"]}] |
| last_verified | "2026-06-16T00:00:00.000Z" |
⚠️ User-Agent required: All curl and code examples in this skill access Wikimedia APIs. Requests without a descriptive User-Agent header will be blocked with HTTP 403 or 429. See the wikimedia-api-access skill for the correct format and rate-limiting patterns.
⚡ This skill is about the rendering pipeline only. For file-type-specific concerns (SVG editing, PDF Wikisource integration, audio/video transcoding), see the dedicated skills cross-referenced below.
💡 Quickest thumbnail from just a filename: Skip hash-path computation and
extra API calls — use Special:FilePath instead. See Section 0.
What Is the Commons Thumbnail System?
Every file on Wikimedia Commons gets a raster thumbnail preview — a downscaled, reformatted image that can be served quickly to browsers, embedded in articles, and consumed by the API. The thumbnail system:
- Downscales raster images (JPEG, PNG, GIF, WebP, TIFF, AVIF) to any supported width
- Rasterizes vector and document formats (SVG, PDF, DjVu) into PNG or JPEG at any requested width
- Extracts keyframes from video files as JPEG thumbnails
- Serves generic placeholders for audio (speaker icon) and other non-visual files
- Generates responsive/retina versions (2×, 1.5×) automatically for sufficiently large files
The system spans four access methods: the Action API, direct URL construction, the REST API, and the legacy thumbhandler.
Quick Reference
| Goal | Method | Example |
|---|
| Get thumbnail URL inline with file metadata | Action API iiurlwidth | &iiurlwidth=800 on prop=imageinfo |
| Construct a thumb URL from scratch | URL scheme | .../thumb/{hash}/{filename}/{width}px-{filename}.{ext} |
| Get thumbnail via REST API | GET /page/image/{title}/{width}px | https://commons.wikimedia.org/api/rest_v1/page/image/Example.jpg/800px |
| Get thumbnail URL from SPARQL | schema:thumbnailUrl | ?file schema:thumbnailUrl ?thumb |
| Responsive/retina versions | responsiveUrls field | "2": ".../1600px-..." |
File-Type Behavior at a Glance
| File Type | Original MIME | thumbmime | Format Change | Resolution Limit |
|---|
| JPEG, PNG, GIF, WebP, TIFF, AVIF | image/jpeg, etc. | Same as original | ❌ No | Native raster dimensions |
| SVG | image/svg+xml | image/png | ✅ SVG → PNG | Unlimited (vector) |
| PDF | application/pdf | image/jpeg | ✅ PDF → JPEG | Unlimited (per page) |
| DjVu | image/vnd.djvu | image/jpeg | ✅ DjVu → JPEG | Unlimited (per page) |
| Video (WebM, OGV) | video/webm, etc. | image/jpeg | ✅ Keyframe → JPEG | Keyframe resolution |
| Audio (OGG, FLAC, etc.) | audio/ogg, etc. | — | N/A — no real thumb | Generic icon only |
🔗 File-type-specific skills:
0. The Easiest Way: Special:FilePath
If you have a filename (e.g., File:Sunset_over_the_ocean.jpg) and need a
thumbnail without computing the hash path or making an API call, use the
Special:FilePath endpoint:
https://commons.wikimedia.org/wiki/Special:FilePath/File:Sunset_over_the_ocean.jpg?width=320
This endpoint:
- Accepts the full title (with
File: prefix), URL-encoded
- Redirects transparently to the actual thumbnail (browser handles the redirect)
- Sets appropriate CORS headers for
<img> tags
- Works with any file type (JPEG, PNG, SVG, PDF, video keyframes)
- Requires no hash-path computation, no MD5 hashing, no extra API calls
From JavaScript (browser):
const filename = 'File:Example.jpg';
const thumbUrl = `https://commons.wikimedia.org/wiki/Special:FilePath/${encodeURIComponent(filename)}?width=320`;
document.getElementById('preview').src = thumbUrl;
From Python:
import urllib.parse
filename = 'File:Example.jpg'
url = f'https://commons.wikimedia.org/wiki/Special:FilePath/{urllib.parse.quote(filename)}?width=320'
When to Use vs. the Action API
| Scenario | Recommended | Why |
|---|
You have a filename and need an <img> src | ✅ Special:FilePath | Zero extra API calls, simplest code |
| You already have file metadata from an API call | ✅ Action API iiurlwidth | Thumbnail URL is already in the response |
| You need exact dimensions or responsive 2× URLs | ✅ Action API iiurlwidth | Returns responsiveUrls and exact thumbwidth/thumbheight |
| You're building a URL without browser redirects (server-side) | ⚠️ Either approach | Special:FilePath requires following a redirect; direct URL is one-hop |
| You don't want to URL-encode special characters | ✅ Action API | Returns pre-built, correctly-encoded URLs |
1. The Thumb URL Scheme
Every thumb URL follows a predictable pattern:
https://upload.wikimedia.org/wikipedia/commons/thumb/{hash_path}/{filename}/{width}px-{dashified_filename}.{ext}
Where:
| Part | Source | Example |
|---|
{hash_path} | First two chars of the file's MD5 hash, split into two subdirectories | a/ab (from MD5 hash starting with ab...) |
{filename} | The bare file name, URL-encoded | Example.jpg |
{width} | Requested width in pixels | 800 |
{dashified_filename} | Filename with spaces replaced by underscores, URL-encoded | Example.jpg |
{ext} | The thumbnail extension (may differ from original) | .jpg, .png |
Finding the Hash Path
The hash path is not computed manually — it's obtained from the file's url field in the API response:
import re
resp = requests.get("https://commons.wikimedia.org/w/api.php", params={
"action": "query",
"prop": "imageinfo",
"iiprop": "url",
"titles": "File:Example.jpg",
"format": "json",
}, timeout=30, headers={"User-Agent": "MyBot/1.0 (user@example.com)"})
url = resp.json()["query"]["pages"]["pageid"]["imageinfo"][0]["url"]
match = re.search(r"/commons/([a-f0-9]/[a-f0-9]+)/", url)
hash_path = match.group(1)
💡 You normally don't construct thumb URLs manually — the Action API's iiurlwidth parameter (see Section 2) returns the complete thumburl for you. Manual construction is only needed when you have a file URL but want to build the thumb URL without an extra API call.
Real URL Examples by File Type
| File Type | Thumb URL (800px) |
|---|
| JPEG | .../thumb/a/ab/Example.jpg/960px-Example.jpg |
| SVG | .../thumb/8/80/Wikipedia-logo-v2.svg/960px-Wikipedia-logo-v2.svg.png |
| PDF (page 1) | .../thumb/2/27/PDF_metadata.pdf/page1-960px-PDF_metadata.pdf.jpg |
| WebM (keyframe) | .../thumb/a/a2/Elephants_Dream_%282006%29.webm/960px--Elephants_Dream_%282006%29.webm.jpg |
Note the differences:
- SVG: extension changes to
.png
- PDF:
page1- prefix before the width
- Video:
-- (double dash) instead of -{dashified_filename}- before the filename
- JPEG: extension stays
.jpg
2. Requesting Thumbnails via the Action API
The most common way to get thumbnail URLs is to request them inline with file metadata using the iiurlwidth and iiurlheight parameters on prop=imageinfo.
Basic Usage
curl -s "https://commons.wikimedia.org/w/api.php?action=query&prop=imageinfo&iiprop=url|mime|thumbmime&iiurlwidth=800&titles=File:Example.jpg&format=json" \
-H "User-Agent: MyBot/1.0 (user@example.com)" | python3 -m json.tool
The response includes these thumbnail-specific fields within each imageinfo entry:
{
"thumburl": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/Example.jpg/960px-Example.jpg",
"thumbwidth": 800,
"thumbheight": 600,
"thumbmime": "image/jpeg",
"responsiveUrls": {
"2": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/Example.jpg/1920px-Example.jpg"
}
}
| Field | Always Present? | Meaning |
|---|
thumburl | ✅ | Full URL to the generated thumbnail |
thumbwidth | ✅ | Actual width of the returned thumbnail (may differ slightly from requested width due to aspect ratio) |
thumbheight | ✅ | Actual height of the returned thumbnail |
thumbmime | ✅ | MIME type of the thumbnail (may differ from the file's mime) |
responsiveUrls | ⚠️ | Object with multiplier keys ("2", "1.5") mapping to higher-resolution versions. Only present for files large enough to need responsive scaling. |
Requesting Exact Dimensions
You can request a specific width or height:
import requests
SESSION = requests.Session()
SESSION.headers.update({"User-Agent": "MyBot/1.0 (user@example.com)"})
resp = SESSION.get("https://commons.wikimedia.org/w/api.php", params={
"action": "query",
"prop": "imageinfo",
"iiprop": "url|mime|thumbmime",
"iiurlwidth": 400,
"titles": "File:Example.jpg",
"format": "json",
})
data = resp.json()
info = next(iter(data["query"]["pages"].values()))["imageinfo"][0]
print(f"Thumbnail: {info['thumburl']}")
print(f"Dimensions: {info['thumbwidth']}×{info['thumbheight']}")
print(f"Format: {info['thumbmime']}")
If you specify both iiurlwidth and iiurlheight, the server will fit the thumbnail within those bounds while preserving aspect ratio (it may be smaller in one dimension).
Batch Thumbnails — Multiple Files at Once
The same iiurlwidth applies to all titles in one query:
resp = SESSION.get("https://commons.wikimedia.org/w/api.php", params={
"action": "query",
"prop": "imageinfo",
"iiprop": "url|thumbmime",
"iiurlwidth": 200,
"titles": "File:Example.jpg|File:Example2.png|File:Example3.svg",
"format": "json",
})
Requesting Different Thumbnail Sizes for Different Files
The iiurlwidth is a single value for the entire query. To get different widths for different files, make separate queries or use iiurlheight for one dimension and let the other vary.
3. Responsive / Retina URLs
When a file is large enough that serving a single thumbnail would look pixelated on high-DPI (Retina) displays, Commons returns a responsiveUrls object:
"responsiveUrls": {
"2": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/Example.jpg/1920px-Example.jpg",
"1.5": "https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/Example.jpg/1440px-Example.jpg"
}
| Key | Meaning | When Present |
|---|
"2" | 2× Retina (double the requested width) | Always, if file is large enough |
"1.5" | 1.5× Retina | Sometimes, for mid-range files |
Use in HTML:
<img src="https://.../960px-Example.jpg"
srcset="https://.../1440px-Example.jpg 1.5x,
https://.../1920px-Example.jpg 2x"
width="800" height="600"
alt="Example">
Python — constructing responsive srcset from API response:
info = resp.json()["query"]["pages"]["pageid"]["imageinfo"][0]
srcset_parts = [f"{info['thumburl']} 1x"]
for multiplier, url in info.get("responsiveUrls", {}).items():
srcset_parts.append(f"{url} {multiplier}x")
srcset = ", ".join(srcset_parts)
⚠️ Small files may not get responsive URLs. If the file's raster dimensions are close to or smaller than the requested thumbnail width, Commons may skip generating responsive versions.
4. The thumbmime Field — Format Conversion Matrix
The thumbmime field tells you the MIME type of the generated thumbnail, which may differ from the file's original mime. This is the key to understanding what Commons does to render each file type.
How to Read the Matrix
curl -s "https://commons.wikimedia.org/w/api.php?action=query&prop=imageinfo&iiprop=mime|thumbmime&iiurlwidth=400&titles=File:Example.jpg|File:Logo.svg|File:Doc.pdf|File:Video.webm&format=json" \
-H "User-Agent: MyBot/1.0 (user@example.com)" | python3 -c "
import json, sys
data = json.load(sys.stdin)
for pid, page in data['query']['pages'].items():
ii = page.get('imageinfo', [{}])[0]
print(f\"{page['title']}: original={ii.get('mime')} → thumb={ii.get('thumbmime')}\")
"
Full Matrix
Original mime | File Type | thumbmime | Ext | What Happens |
|---|
image/jpeg | JPEG | image/jpeg | .jpg | Downscaled JPEG |
image/png | PNG | image/png | .png | Downscaled PNG |
image/gif | GIF | image/gif | .gif | Static frame (animated GIFs get a single frame) |
image/webp | WebP | image/webp | .webp | Downscaled WebP |
image/tiff | TIFF | image/jpeg | .jpg | TIFF → JPEG conversion + downscale |
image/avif | AVIF | image/avif | .avif | Downscaled AVIF |
image/svg+xml | SVG | image/png | .png | Rasterized to PNG at requested width (vector→raster) |
application/pdf | PDF | image/jpeg | .jpg | Page rendered as JPEG |
image/vnd.djvu | DjVu | image/jpeg | .jpg | Page rendered as JPEG |
video/webm | WebM | image/jpeg | .jpg | Keyframe extracted as JPEG |
video/ogg | OGV (Theora) | image/jpeg | .jpg | Keyframe extracted as JPEG |
audio/ogg | OGG Audio | — | — | No real thumbnail; generic icon |
| All audio types | Audio | — | — | No real thumbnail; generic icon |
⚠️ Key takeaway: Never assume the thumbnail extension matches the file extension. Always check thumbmime and derive the extension from it, or just use thumburl directly.
Deriving Extension from thumbmime
MIME_TO_EXT = {
"image/jpeg": ".jpg",
"image/png": ".png",
"image/gif": ".gif",
"image/webp": ".webp",
"image/avif": ".avif",
}
thumb_ext = MIME_TO_EXT.get(info.get("thumbmime", ""), ".jpg")
But again — you rarely need this. Just use thumburl directly.
5. Thumbnail Size Constraints
Maximum Width
| Context | Max Width | Notes |
|---|
| Default API / URL request | 2048 px | The standard maximum for unauthenticated thumbnails |
| Logged-in users (via API) | 4096 px | Larger thumbs available to authenticated requests |
| SVG (any request) | 4096 px | SVGs can be rendered larger because they scale cleanly |
| PDF/DjVu (any request) | 4096 px | Same reasoning — page rendering is resolution-independent |
What happens when you exceed the limit?
The server returns the largest allowed thumbnail (e.g., 2048px instead of 3000px). It does NOT return an error — it silently caps the width. Always check thumbwidth in the response to confirm the actual size.
The Retina Multiplier and Max Width
If you request iiurlwidth=800, Commons generates:
800px thumbnail → thumburl
1600px (2×) thumbnail → responsiveUrls["2"]
But if 1600 > max_width, the 2× version is capped at max_width and the server returns responsiveUrls at the capped size.
Aspect Ratio Preservation
The thumbnail always preserves the original aspect ratio. If you request iiurlwidth=800, the thumbheight will be computed automatically:
thumbheight = requested_width * (original_height / original_width)
For SVGs, the aspect ratio comes from the viewBox attribute. For PDFs, from the page dimensions.
6. REST API Thumbnail Endpoint
The Wikimedia REST API also provides a thumbnail endpoint:
GET https://commons.wikimedia.org/api/rest_v1/page/image/{title}/{width}px
Usage
curl -s "https://commons.wikimedia.org/api/rest_v1/page/image/Example.jpg/800px" \
-H "User-Agent: MyBot/1.0 (user@example.com)" \
-o thumbnail.jpg
Limitations
| Aspect | REST API | Direct URL / Action API |
|---|
| File types | Images only (JPEG, PNG, GIF, SVG) | All file types with visual output |
| PDF/DjVu | ❌ Not supported (404) | ✅ Supported via iiurlwidth + URL construction |
| Video | ❌ Not supported | ✅ Supported via iiurlwidth |
| Metadata in response | Binary only — no metadata returned | Returns thumbwidth, thumbheight, thumbmime, responsiveUrls as JSON |
| Headers | Content-Type, Content-Length | N/A (Action API returns JSON) |
💡 When to use the REST API: When you only need to download a thumbnail for a known image file and don't need metadata about it. For anything else (PDFs, video, responsive URLs), use the Action API with iiurlwidth.
7. Thumbhandler Legacy API
Commons has a legacy thumbhandler at:
https://commons.wikimedia.org/w/thumb.php?f={filename}&w={width}
Parameters
| Parameter | Required | Description |
|---|
f | ✅ | Filename (e.g., Example.jpg) |
w | ✅ | Width in pixels |
p | ❌ | Page number for PDF/DjVu (e.g., 1) |
q | ❌ | Quality (1-100, default ~85) |
Example
curl -s "https://commons.wikimedia.org/w/thumb.php?f=Example.jpg&w=400" \
-H "User-Agent: MyBot/1.0 (user@example.com)" \
-o thumbnail.jpg
⚠️ Status
The thumbhandler is legacy/deprecated. It still works but:
- Slower than direct URL construction (it's a PHP handler, not a CDN-optimized path)
- No
responsiveUrls support
- No structured metadata in the response
- Prefer the Action API
iiurlwidth method or direct URL construction whenever possible
8. SPARQL Thumbnail Patterns
When querying Commons via SPARQL, you can retrieve thumbnail URLs directly using the schema:thumbnailUrl predicate. This is especially useful for generating visual result grids.
Basic SPARQL — Get Thumbnails for Depicts
PREFIX schema: <http://schema.org/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
PREFIX wd: <http://www.wikidata.org/entity/>
SELECT ?file ?thumb ?caption WHERE {
?file wdt:P180 wd:Q42 ; # depicts Douglas Adams
schema:thumbnailUrl ?thumb ; # thumbnail URL
rdfs:label ?caption . # file caption
FILTER(LANG(?caption) = "en")
}
LIMIT 50
⚠️ QLever requires explicit PREFIX declarations. See the wikimedia-commons-sparql skill for the full prefix block and endpoint details.
ImageGrid View
The #defaultView:ImageGrid directive renders results as a visual grid. It needs schema:url (the file page URL, not the thumbnail URL):
#defaultView:ImageGrid
PREFIX schema: <http://schema.org/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
PREFIX wd: <http://www.wikidata.org/entity/>
SELECT ?file ?image WHERE {
?file wdt:P180 wd:Q42 ;
schema:url ?image . # file page URL — ImageGrid renders this as thumbnail
}
Content URL vs Thumbnail URL
| Predicate | Returns | Use Case |
|---|
schema:url | File page URL (Special:FilePath/...) | ImageGrid view, linking |
schema:contentUrl | Direct download URL for the full file | Raw file access |
schema:thumbnailUrl | Thumbnail URL (typically ~800px) | Embedding in results, previews |
SELECT ?full ?thumb ?file WHERE {
?file schema:contentUrl ?full ;
schema:thumbnailUrl ?thumb .
}
LIMIT 10
Filtering by Image Size (Useful for Thumbnails)
# Files wider than 4000px — useful to know which files have large responsive thumbnails
PREFIX schema: <http://schema.org/>
SELECT ?file ?w ?h ?thumb WHERE {
?file schema:width ?w ;
schema:height ?h ;
schema:thumbnailUrl ?thumb .
FILTER(?w > 4000)
}
LIMIT 100
9. Caching Behavior
Commons uses Varnish as its HTTP cache, deployed globally across multiple data centers. Thumbnails are cached aggressively.
Cache Keys
Thumbnails are cached by their full URL. The same URL always returns the same thumbnail unless the file is updated.
Cache Invalidation
| Event | Effect |
|---|
| New file upload | Thumbnails generated on first request (cold cache) |
| File updated (new version) | All thumbnails for that file are purged from cache |
| Thumbnail requested at new width | Generated and cached; previously cached widths are unaffected |
Cold vs Warm Cache
- Cold cache (first request after upload or update): The server renders the thumbnail on-demand. For SVGs, PDFs, and videos, this can take several seconds for the first request.
- Warm cache (subsequent requests): The thumbnail is served from edge cache in milliseconds.
Cache Durations
| Content | TTL (typical) |
|---|
| Popular thumbnails (frequently requested) | Indefinite (pinned in cache) |
| Unpopular thumbnails | Until evicted by LRU |
| After file update | Immediately purged |
💡 Practical implication: When you upload a new version of a file (especially an SVG), the first few thumbnail requests may be slow as the cache warms. This is normal. The thumbnail URL stays the same — the old cache entry is purged and the new one is generated on first access.
Putting It All Together: A Complete Example
"""
Fetch thumbnails for multiple Commons files with different types.
Demonstrates iiurlwidth, thumbmime handling, and responsive URLs.
"""
import requests
API = "https://commons.wikimedia.org/w/api.php"
UA = "ThumbnailDemo/1.0 (https://example.com; user@example.com)"
SESSION = requests.Session()
SESSION.headers.update({"User-Agent": UA})
def get_thumb_info(title, width=400):
"""Get thumbnail info for a file at the given width."""
resp = SESSION.get(API, params={
"action": "query",
"prop": "imageinfo",
"iiprop": "url|mime|thumbmime|size",
"iiurlwidth": width,
"titles": title,
"format": "json",
})
resp.raise_for_status()
pages = resp.json()["query"]["pages"]
page = next(iter(pages.values()))
info = page.get("imageinfo", [{}])[0]
return page["title"], info.get("mime"), info.get("thumbmime"), info.get("thumburl"), info.get("thumbwidth"), info.get("thumbheight"), info.get("responsiveUrls", {})
files = [
"File:Example.jpg",
"File:Wikipedia-logo-v2.svg",
"File:PDF metadata.pdf",
"File:Elephants Dream (2006).webm",
]
for title in files:
name, mime, tmime, url, tw, th, resp_urls = get_thumb_info(title, 400)
print(f"\n📄 {name}")
print(f" Original: {mime}")
print(f" Thumb: {tmime}")
print(f" URL: {url}")
print(f" Size: {tw}×{th}")
if resp_urls:
for mult, u in resp_urls.items():
print(f" {mult}×: {u}")
10. CORS: When Direct Thumbnail URLs Are Blocked
When embedding Commons thumbnails in browser-based applications (JavaScript, CSS background-image, <img> elements), you may encounter CORS (Cross-Origin Resource Sharing) errors. This happens when your application runs on one origin (e.g., localhost:8765 or your-app.toolforge.org) and tries to fetch resources directly from upload.wikimedia.org.
The Problem
Access to image at 'https://upload.wikimedia.org/wikipedia/commons/thumb/...'
from origin 'http://localhost:8765' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Chrome DevTools shows: net::ERR_BLOCKED_BY_ORB (Opaque Response Blocking).
Why This Happens
Wikimedia's image servers (upload.wikimedia.org) do not send CORS headers by default. This is intentional — it prevents third-party sites from:
- Reading image pixel data via JavaScript Canvas APIs
- Using the images in WebGL contexts that require
crossorigin attributes
- Exfiltrating user-specific information through image requests
For most use cases, this is fine — images are displayed, not processed. But browser security policies now block certain operations on cross-origin images without explicit CORS headers.
Solutions
| Approach | When to Use | Complexity |
|---|
| Use the file page URL | Displaying images in <img> tags | Low — works for simple display |
| Proxy through your server | Canvas/WebGL/background-image in CSS | Medium — requires server-side code |
| Use the REST API thumbnail endpoint | Modern browsers, single-origin apps | Low — returns CORS headers |
1. Use the File Page URL (Simple Display Only)
For basic image display (no Canvas, no WebGL), use the file page URL instead of the direct upload URL:
# Direct upload URL (no CORS headers)
https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/Example.jpg/800px-Example.jpg
# File page URL (CORS-friendly, redirects to thumbnail)
https://commons.wikimedia.org/wiki/Special:FilePath/Example.jpg?width=800
The Special:FilePath/ endpoint sets appropriate CORS headers and redirects to the actual image. This works for <img> tags but may still fail for Canvas/WebGL operations.
2. Proxy Through Your Server (Canvas/WebGL/background-image)
For applications that need full access to image data (Canvas, WebGL textures, CSS background-image), proxy the image through your own server:
const thumbUrl = '/images/abc123.jpg';
const resp = await fetch(thumbnailUrl);
const buffer = await resp.arrayBuffer();
const hash = createHash('sha256').update(buffer).digest('hex');
const filepath = `cache/${hash}.jpg`;
await writeFile(filepath, buffer);
return { url: `/images/${hash}.jpg`, original: thumbnailUrl };
Key insight: The browser requests from your-origin → your server fetches from Commons → your server caches and serves from your-origin. The browser sees the response as same-origin, so no CORS restrictions apply.
Example from the Wikimedia Photosphere Tours project:
scene.panorama = "/images/abc123.jpg";
scene._thumb = "https://upload.wikimedia.org/.../200px-...";
scene._original = "https://upload.wikimedia.org/.../Example.jpg";
3. Use REST API Thumbnail Endpoint
The REST API's thumbnail endpoint (/api/rest_v1/page/image/{title}/{width}px) may include CORS headers in some configurations. Check the response headers for your specific use case.
Decision Tree
Do you need to read pixel data (Canvas, WebGL)?
├── YES -> Proxy through your server (Solution 2)
└── NO
└── Is it just for display (<img>, CSS background)?
├── YES -> Try Special:FilePath URL (Solution 1)
└── Still blocked?
└── Proxy through your server (Solution 2)
Common Pitfall: Ignoring Proxy Paths
When your server returns both url (proxied path) and thumb/original (direct Commons URLs), always use url for browser rendering. A common mistake is to use thumb directly:
const thumbUrl = scene._thumb;
element.style.backgroundImage = `url(${thumbUrl})`;
const thumbUrl = scene.panorama;
element.style.backgroundImage = `url(${thumbUrl})`;
This pattern applies to thumbnails, panoramas, audio/video files, and any other media served from upload.wikimedia.org.
Related Skills