Skip to content

Display Images

Fetch authenticated images and display them in the browser

storage images display authenticated img tag blob URL React guide

When files are protected by permissions, you can’t just point an <img> tag at the storage URL — the browser won’t send the authentication header. This guide shows how to fetch an image with an authenticated session and display it.

A plain <img src> tag makes an unauthenticated GET request. If your file requires authentication, the request will be rejected:

<!-- This won't work for authenticated files -->
<img src="https://local.storage.local.nhost.run/v1/files/FILE_ID" />

There are three ways to solve this:

  1. Blob URL (recommended) — fetch the file with the SDK, create a local object URL. CDN-friendly: the CDN caches the file and re-validates with the client’s auth headers.
  2. Pre-signed URL — generate a temporary public URL. Not CDN-friendly: each URL is unique, bypassing the cache.
  3. Public bucket — if the file doesn’t need protection, make it public.
Section titled “Option 1: Blob URL (Recommended for Private Files)”

Fetch the image using the SDK (which includes the auth token), then create a blob URL for the <img> tag.

const { body } = await nhost.storage.getFile(fileId, {
w: 100,
h: 100,
f: 'webp',
})
const url = URL.createObjectURL(body)

Thumbnails can be loaded for each image file and stored in a state map:

const loadThumbnail = useCallback(
async (fileId: string): Promise<void> => {
try {
const { body } = await nhost.storage.getFile(fileId, {
w: 100,
h: 100,
f: 'webp',
})
const url = URL.createObjectURL(body)
setThumbnails((prev) => ({ ...prev, [fileId]: url }))
} catch {
// Silently skip thumbnails that fail to load
}
},
[nhost.storage],
)

Then rendered inline:

{file.mimeType?.startsWith('image/') && file.id && thumbnails[file.id] && (
<img
src={thumbnails[file.id]}
alt={file.name || ''}
style={{ width: 32, height: 32, objectFit: 'cover', borderRadius: 4 }}
/>
)}

This is the recommended approach for private files. The request goes through the CDN, which caches the file content. On subsequent requests the CDN re-validates using a conditional request that includes the client’s Authorization header, so the backend only confirms the user still has access without re-serving the file. This gives you both privacy and CDN performance.

Generate a temporary URL that doesn’t require authentication. The image loads like a normal <img> tag.

const { body } = await nhost.storage.getFilePresignedURL(fileId)
const url = body.url
// Use as an <img> src — no auth headers needed
const img = document.createElement('img')
img.src = url

You can also store the pre-signed URL in state and display it in a text field for sharing:

const handleGetPresignedUrl = async (fileId: string): Promise<void> => {
try {
const { body } = await nhost.storage.getFilePresignedURL(fileId)
setPresignedUrl(body.url)
} catch (err) {
const error = err as FetchError<ErrorResponse>
setStatusMessage({
message: `Failed to get pre-signed URL: ${error.message}`,
isError: true,
})
}
}

Pre-signed URLs are simpler but expire after the bucket’s download_expiration (default: 30 seconds). Each pre-signed URL is unique, which means the CDN treats every generated URL as a different resource — effectively bypassing the cache entirely. Only use pre-signed URLs when you need to share files with systems that cannot send authentication headers. See Pre-signed URLs for details.

If the file doesn’t need protection, place it in a bucket with public role select permissions. Then the URL works directly:

<img
src="https://local.storage.local.nhost.run/v1/files/FILE_ID?w=400&f=webp"
alt="Public image"
/>

This is the simplest approach and works best with CDN caching. See Permissions for how to configure public access.

ApproachAuth requiredCDN-friendlyExpirationBest for
Blob URL (recommended)YesYes (re-validates with auth headers)NonePrivate files in an authenticated UI
Pre-signed URLOnly to generateNo (unique URLs bypass cache)ConfigurableSharing with external systems
Public bucketNoYesNoneContent accessible to everyone
  • Use blob URLs for private files displayed in an authenticated UI (avatars, user uploads) — this is the preferred approach as it combines privacy with CDN caching
  • Use pre-signed URLs only when sharing files with systems that cannot send authentication headers (email links, third-party integrations)
  • Use public buckets for content that anyone should be able to see (marketing images, public assets)