Skip to content

Display Images

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)