High-resolution image hosting · zoomable viewersDeep zoom image hosting

Your whole collection. Every pixel.

Upload huge images and PDFs. Get a zoomable viewer for any website.

  • No AI
  • Permanent link
  • Private until you say so

I’m putting online…

Building an app? Use the API →
Layout
Sort
One embed · 6 images · 3.4 gigapixels
Embroidered opening scene of the Bayeux Tapestry: King Edward the Confessor speaking with Harold
Bayeux Tapestry, scene 1
Late-medieval panel painting of the Crucifixion, with a donor kneeling at the foot of the cross
Crucifixion with a Donor, ca. 1480–85
Bosch’s triptych, crowded with tiny figures, giant fruit and fantastical creatures
Hieronymus Bosch, The Garden of Earthly Delights
Bird’s-eye engraving of Paris in which every street, garden and building is drawn
Turgot map of Paris, 1739
Bruegel’s Tower of Babel, a vast unfinished spiral of arches rising above a harbor town
Pieter Bruegel the Elder, The Tower of Babel, 1563
Holbein’s double portrait of two envoys beside shelves of instruments, with a distorted skull across the floor
Hans Holbein the Younger, The Ambassadors, 1533
One embed · 6 images · 3.4 gigapixels · Bayeux Tapestry, scene 1
1× · tiles load as you zoom

  • Bayeux Tapestry, scene 1: Embroidered opening scene of the Bayeux Tapestry: King Edward the Confessor speaking with Harold
  • Crucifixion with a Donor, ca. 1480–85: Late-medieval panel painting of the Crucifixion, with a donor kneeling at the foot of the cross
  • Hieronymus Bosch, The Garden of Earthly Delights: Bosch’s triptych, crowded with tiny figures, giant fruit and fantastical creatures
  • Turgot map of Paris, 1739: Bird’s-eye engraving of Paris in which every street, garden and building is drawn
  • Pieter Bruegel the Elder, The Tower of Babel, 1563: Bruegel’s Tower of Babel, a vast unfinished spiral of arches rising above a harbor town
  • Hans Holbein the Younger, The Ambassadors, 1533: Holbein’s double portrait of two envoys beside shelves of instruments, with a distorted skull across the floor

Bayeux Tapestry, scene 1
5,895 × 3,790 px · 22 MP

Catalogs, manuscripts and maps, public or private.

Every object and painting gets a permanent link. Keep acquisitions and embargoed scans behind a password until they’re ready.

Talk to me about your collection

Collections of all your images

One zoomable composite, from overview to object.

Reference IDs for catalog numbers

Pro

Map every image to its record.

Password-protected images

Paid

Keep unpublished acquisitions private.

Never downsize

Zoom in on a downsized image, then on the original.

A website usually shrinks a big image to a couple of thousand pixels. That looks fine until someone zooms in. Here’s the Turgot map of Paris twice: downsized to 2,000 pixels wide, and the 35,690-pixel original. Zoom into any street, then drag the divider.

Why it matters

35,690 px → 2,000 px wide: 99.7 % of the pixels thrown away. With them go the details maps and manuscripts live in: street names, dimensions, marginalia.

What ZoomHub does instead

All 1.0 billion pixels kept, and about 20 tiles streamed to fill a full-HD screen. It stays sharp at any zoom, on any device.

How deep zoom works

Gigapixel images that load as fast as a photo.

Your image is cut into a pyramid of small tiles. The viewer only streams the tiles on your screen, so a 2-gigapixel scan opens instantly on any device.

The idea behind deep zoom · TED2007

“The only thing that ought to limit the performance of a system like this one is the number of pixels on your screen at any given moment.”

Blaise Agüera y Arcas, demonstrating Seadragon at TED2007. Watch the talk on TED.com

The pixels on your screen, right now

12

tiles fill a 1,920 × 1,080 screen, of 10,512 in this image

Orange: what the screen needs, layer by layer. At 1×: all 12 tiles at 1/32 size.

The image: a made-up scan, 50,000 × 40,000 px (2 gigapixels). Zoom in, and a finer layer takes over, but only the part on screen.

1×

I was on Microsoft’s Seadragon team in 2009–10. ZoomHub carries on where zoom.it left off: images put online there since 2009 still load, and 440+ websites embed its viewers.

440+

websites embed ZoomHub viewers.

No AI

Nothing upscaled, sharpened or invented. Every square above is one pixel of the Turgot map of Paris, 1739, enlarged 5×, exactly as it was uploaded.

Viewers & embeds

Zoomable image viewers for museums, maps, documents and photography.

Artists & photographers

Paid

A series laid out side by side in one film strip, without the ZoomHub badge.

Documents · every page in one view

Maps · optional minimap · up to 5 GB on paid plans

Compare · two images, zoomed in sync

Hubble: NASA, ESA/Hubble and the Hubble Heritage Team · Webb: NASA, ESA, CSA, STScI; J. DePasquale, A. Koekemoer, A. Pagan (STScI) · CC BY 4.0

Embed editor · no coding required

Layout
Navigation
Slideshow
Accent coloroutlines and buttons

Live preview · change a viewer’s layout or look later, and embeds already on your site update.

Try it with your own image

Developers

Building an app or a pipeline?

The REST API converts images from a URL or an upload and returns a share link and the embed HTML.

See the API

Privacy

Password-protected images, private until you say so.

Unpublished acquisitions, scans under embargo, work shared with a client before release. Keep any image private until it’s ready.

Password-protected images

Paid

Set a password on any image. Visitors enter it once and stay unlocked for hours: on the image’s page, in its viewers, and in collections protected with the same password.

The tiles are protected too

Not just the page. The image tiles won’t load without the password.

Guessing won’t get anyone in

After 5 wrong passwords in a minute, the next guesses aren’t even checked.

Publish when you’re ready

When the embargo lifts, make the image public. It keeps its link and its embed code, so nothing on your site needs updating.

Pricing

Image hosting plans, priced by the size of your collection.

Free

$0 / month

10 images · badge shown

  • Personal use
  • Links, embeds and comparisons
  • Galleries and PDFs
  • Files up to 200 MB
  • Collections of up to 10 images
Start free

Starter

$29 / month

50 images · no badge

  • Commercial use
  • Password-protected images
  • Slideshows, no ZoomHub logo
  • Files up to 5 GB
  • Collections of up to 50 images
Choose Starter

Pro

Fits 100 images

$79 / month

200 images · no badge

  • Everything in Starter
  • Reference IDs
  • Room for a whole collection
  • Collections of up to 200 images
Choose Pro

Business

$199 / month

1,000 images · no badge

  • Everything in Pro
  • Room for an archive
  • API keys
  • Collections of up to 1,000 images
  • High-volume API access? Ask me
Choose Business

Pro · $79 a month

Paying yearly gets you 2 months free. Free is for personal use; commercial use needs a paid plan. Each page of a PDF counts as an image.

Enterprise

1,000+ images

A larger collection, or needs the plans don’t cover. Tell me what you’re putting online and I’ll put together a price.

Collections of 10,000 images and more, like this Flickr showcase.

Contact me

FAQ

Questions about putting large images online.

How do I put a very large image on my website?

Upload the file or paste a link, and confirm the email I send you. You get a permanent link and one line of HTML to paste into your page: no coding, no plugin, no build step.

What’s the largest file I can upload?

Up to 5 GB per file on paid plans (200 MB on Free): JPEG, PNG, TIFF, WebP, GIF or PDF.

Will ZoomHub change or compress my image?

No AI, no upscaling, and your full-size image isn’t resampled. PNG, TIFF and other lossless files stay pixel-exact, transparency kept; photos get high-quality JPEG tiles.

Can I put a multi-page PDF online?

Yes. A PDF becomes one zoomable document, with every page laid out together or in a single strip. All its pages are processed at once. Each page of a PDF counts as an image.

Can I keep images private?

Yes, on a paid plan. Set a password on any image. Visitors enter it once for its page, its viewers and collections protected with the same password, and the tiles are protected too.

Can I automate uploads?

Yes. The REST API converts images from a URL or an upload, reports progress, and returns the share URL and embed HTML. With an API key, which comes with the Business plan, it starts right away and can also set titles, descriptions and reference IDs.

Is ZoomHub free?

Free for personal use, up to 10 images. Commercial use starts at $29 a month and removes the “Powered by ZoomHub” badge.

Will my links keep working?

Yes. ZoomHub carries on where zoom.it left off: in the last 90 days, more than 1,500 images first put online between 2009 and 2014 were viewed in embeds on over 400 websites.

Museums, archives & libraries

Putting a collection online? Talk to me.

I’m Daniel, the engineer behind ZoomHub. I’ve been thinking about zoomable interfaces since 2007, wrote the series “Inside Deep Zoom” (parts I, II and III) in 2008, and in 2009–10 was on Microsoft’s Seadragon team at Live Labs, first as an intern, then full-time, working on zoom.it. Tell me what you’re digitizing and what has to stay private, and I’ll tell you how it would work.

I read and answer every message myself.

Opens your email app. Or write to daniel@zoomhub.net.

Developers

Deep zoom from your own code: a REST API for images and collections.

Convert images from a URL, upload files, poll progress, and get back a share URL and embed HTML. Plain JSON, no SDK required.

cURL

# 1 · Convert an image from a URL
curl --location --get https://api.zoomhub.net/v1/content \
  --data-urlencode "url=https://example.org/scan.tif" \
  --data-urlencode "email=you@example.org"

# 2 · Follow the link I email you, then poll until "ready"
curl https://api.zoomhub.net/v1/content/k7Hq

# Or upload a file with an API key (Business plan)
curl --header "Authorization: Bearer $ZOOMHUB_API_KEY" \
  "https://api.zoomhub.net/v1/content/upload?contentType=image%2Fjpeg&filename=scan.jpg" > form.json
fields=(); while IFS= read -r field; do fields+=(--form-string "$field"); done \
  < <(jq --raw-output 'del(.url, ."Content-Type") | to_entries[] | "\(.key)=\(.value)"' form.json)
curl "${fields[@]}" --form-string "Content-Type=image/jpeg" --form "file=@scan.jpg" \
  "$(jq --raw-output .url form.json)"
curl --location --header "Authorization: Bearer $ZOOMHUB_API_KEY" \
  "https://api.zoomhub.net/v1/content?url=$(jq --raw-output '.url + "/" + .key | @uri' form.json)"

JavaScript

const api = "https://api.zoomhub.net"

// 1 · Convert an image from a URL
const query = new URLSearchParams({ url: "https://example.org/scan.tif", email: "you@example.org" })
let content = await (await fetch(`${api}/v1/content?${query}`)).json()

// 2 · Follow the link I email you, then poll until it’s ready
while (!content.ready && !content.failed) {
  await new Promise((resolve) => setTimeout(resolve, 5000))
  content = await (await fetch(`${api}/v1/content/${content.id}`)).json()
}

// 3 · Share it, or paste content.embedHtml into your page
console.log(content.shareUrl)

// Or upload a file with an API key (Business plan)
const headers = { Authorization: `Bearer ${apiKey}` }
const params = new URLSearchParams({ contentType: file.type, filename: file.name })
const { url, ...fields } = await (await fetch(`${api}/v1/content/upload?${params}`, { headers })).json()
const body = new FormData()
for (const [name, value] of Object.entries(fields)) body.append(name, value)
body.set("Content-Type", file.type)
body.append("file", file) // the file goes last
await fetch(url, { method: "POST", body })
const uploaded = new URLSearchParams({ url: `${url}/${fields.key}` })
content = await (await fetch(`${api}/v1/content?${uploaded}`, { headers })).json()

Python

import time
import requests

api = "https://api.zoomhub.net"

# 1 · Convert an image from a URL
content = requests.get(f"{api}/v1/content",
    params={"url": "https://example.org/scan.tif", "email": "you@example.org"}).json()

# 2 · Follow the link I email you, then poll until it’s ready
while not (content["ready"] or content["failed"]):
    time.sleep(5)
    content = requests.get(f"{api}/v1/content/{content['id']}").json()

# 3 · Share it, or paste content["embedHtml"] into your page
print(content["shareUrl"])

# Or upload a file with an API key (Business plan)
headers = {"Authorization": f"Bearer {api_key}"}
form = requests.get(f"{api}/v1/content/upload", headers=headers,
    params={"contentType": "image/jpeg", "filename": "scan.jpg"}).json()
url = form.pop("url")
form["Content-Type"] = "image/jpeg"
with open("scan.jpg", "rb") as file:
    requests.post(url, data=form, files={"file": file}).raise_for_status()
content = requests.get(f"{api}/v1/content", headers=headers,
    params={"url": f"{url}/{form['key']}"}).json()
Response · 200 OKapplication/json
{
  "id": "k7Hq",
  "url": "https://example.org/scan.tif",
  "verified": true,
  "ready": true,
  "failed": false,
  "progress": 1,
  "shareUrl": "https://zoomhub.net/a/k7Hq",
  "embedHtml": "<script src=\"https://zoomhub.net/a/k7Hq.js?width=auto&height=400px\"></script>",
  "dzi": {
    "url": "https://cache.zoomhub.net/content/k7Hq.dzi",
    "width": 41184,
    "height": 24576,
    "tileSize": 510,
    "tileOverlap": 1,
    "tileFormat": "webp"
  },
  "title": null,
  "caption": null,
  "description": null,
  "referenceId": null
}

Convert from a URL

One request queues the conversion; poll until ready.

Upload files

Get a presigned upload form, then send files of up to 5 GB straight to storage.

Collections & viewers

The v2 API creates collections and viewers, with structured JSON errors.