Skip to main content
Nhost Storage lets your users upload and download files. Nhost Storage is partially integrated with the GraphQL API, where file metadata and permissions are managed. Files are stored in S3 and served via a CDN.

Files

Files can be of any type, such as images, documents, videos, etc. File metadata is stored in your database in the files table in the storage schema. This means that file metadata is available in your GraphQL API, which makes it easy to:
  • Read file metadata via GraphQL.
  • Manage file permissions (in Hasura).
  • Create GraphQL relationships between files and your database tables.
Don’t modify the database schema, nor GraphQL root fields in any of the tables in the storage schema.
You’re allowed to add and modify the following:
  • GraphQL Relationships
  • Permissions

Upload File

When a file is uploaded, the file metadata is inserted into the storage.files table and a file id is returned. The file’s id is how you reference and access the file. You can upload files to a specific bucket and include custom metadata:
  • JavaScript
  • HTTP
// Basic file upload
const uploadResp = await nhost.storage.uploadFiles({
  'file[]': [new File(['test content'], 'test-file.txt', { type: 'text/plain' })]
})

// Upload to specific bucket with metadata
const uploadWithMetadata = await nhost.storage.uploadFiles({
  'bucket-id': 'user-uploads',
  'file[]': [new File(['test content'], 'test-file.txt', { type: 'text/plain' })],
  'metadata[]': [{
    name: 'custom-filename.txt',
    metadata: {
      alt: 'Custom description',
      category: 'document'
    }
  }]
})

Download File

There are two ways to download a file:
  • Public URL
  • Pre-signed URL

Public URL

Public URLs are available for both unauthenticated and authenticated users. Permissions are checked for every file request. Files are served via CDN for optimal performance.
  • JavaScript
  • HTTP
// Download file content
const downloadResp = await nhost.storage.getFile(fileId)
const fileBlob = downloadResp.body
const fileContent = await fileBlob.text()

// Get file metadata headers only (without downloading content)
const metadataResp = await nhost.storage.getFileMetadataHeaders(fileId)
Public URLs support:
  • Conditional requests using If-Modified-Since, If-None-Match headers for caching
  • Range requests for partial file downloads using the Range header
  • Image transformations via query parameters (see Image Transformation section)

Pre-signed URL

Pre-signed URLs work differently from public URLs and are ideal for temporary, secure access. How they work:
  • Permission check happens only when requesting the pre-signed URL
  • Once generated, anyone with the URL can download the file (no additional auth required)
  • URLs expire after a configurable time period
Expiration settings:
  • Default bucket: 30 seconds expiration
  • Configurable per bucket via the download_expiration field (in seconds)
  • JavaScript
  • HTTP
// Get pre-signed URL
const resp = await nhost.storage.getFilePresignedURL(fileId)
console.log('URL:', resp.body.url)
console.log('Expires in:', resp.body.expiration, 'seconds')

// Use the URL directly (no auth headers needed)
const fileResponse = await fetch(resp.body.url)
const fileBlob = await fileResponse.blob()

Delete File

Delete a file and the file metadata in the database. This permanently removes both the file content from storage and its associated metadata.
  • JavaScript
  • HTTP
await nhost.storage.deleteFile(fileId)
File deletion is permanent and cannot be undone. Always delete files via the Nhost Storage API to ensure both the file content and metadata are properly removed.

Replace File

Replace an existing file with new content while preserving the file ID. This is useful when you need to update a file without changing references to it.
  • JavaScript
  • HTTP
const newFile = new File(['updated content'], 'updated-file.txt', { type: 'text/plain' })

const replaceResp = await nhost.storage.replaceFile(fileId, {
  file: newFile,
  metadata: {
    name: 'new-filename.txt',
    metadata: {
      version: '2.0',
      updated: new Date().toISOString()
    }
  }
})
The replace operation follows these steps:
  1. Sets isUploaded flag to false during the update process
  2. Replaces the file content in storage
  3. Updates file metadata (size, mime-type, etc.)
  4. Sets isUploaded flag back to true

Buckets

Buckets are containers used to organize files and group permissions. They provide a way to apply different configurations and access controls to different types of files. Buckets are stored in the storage.buckets table in your database and are accessible via the buckets field in your GraphQL API.

Bucket Configuration

Each bucket can be configured with the following properties:

File Restrictions

  • Minimum size - Minimum file size in bytes
  • Maximum size - Maximum file size in bytes to prevent large uploads

Access Control

  • Cache control - HTTP cache headers for files in this bucket
  • Allow pre-signed URLs - Whether files can be accessed via pre-signed URLs
  • Download expiration - Expiration time in seconds for pre-signed URLs

Default Bucket

  • Every Nhost project includes a default bucket
  • Used automatically when no bucket is specified during upload
  • Cannot be deleted (system requirement)
  • Has standard configuration suitable for most use cases

Working with Buckets

Upload to Specific Bucket

// Upload to a custom bucket
const uploadResp = await nhost.storage.uploadFiles({
  'bucket-id': 'user-profiles',
  'file[]': [profileImage]
})

GraphQL Bucket Management

You can query and manage buckets through your GraphQL API:
query GetBuckets {
  buckets {
    id
    minUploadFileSize
    maxUploadFileSize
    presignedUrlsEnabled
    downloadExpiration
    cacheControl
  }
}

Common Bucket Patterns

User Uploads: Separate public and private user content
- user-public (images, documents accessible to others)
- user-private (personal files, drafts)
Content Types: Organize by file type or purpose
- images (profile pictures, thumbnails)
- documents (PDFs, spreadsheets)
- media (videos, audio files)
Environment-based: Different buckets for different environments
- dev-uploads
- staging-uploads
- prod-uploads

Permissions

Permissions to upload, download, and delete files are managed through Hasura’s permission system on the storage.files table.

Upload

To upload a file, a user must have the insert permission to the storage.files table. The id column must be granted. The following columns can be used for insert permissions:
  • id
  • bucket_id
  • name
  • size
  • mime_type

Download

To download a file, a user must have the select permission to the storage.files table. All columns must be granted.

Delete

To delete a file, a user must have the delete permission to the storage.files table.
Just deleting the file metadata in the storage.files table does not delete the actual file. Always delete files via Nhost Storage. This way, both the file metadata and the actual file are deleted.

Image Transformation

Images can be transformed on-the-fly by adding query parameters to file URLs. This feature works with both public URLs and pre-signed URLs, allowing you to resize, optimize, and convert images without pre-processing.

Available Transformations

Resize Parameters

  • w - Maximum width to resize image to while maintaining aspect ratio
  • h - Maximum height to resize image to while maintaining aspect ratio

Quality and Format

  • q - Image quality (1-100). Applies to JPEG, WebP, and PNG files
  • f - Output format. Options: auto, same, jpeg, webp, png, avif
    • auto - Uses content negotiation based on Accept header
    • same - Keeps original format (default)

Effects

  • b - Blur the image using sigma value (0 or higher)

Examples

  • JavaScript
  • HTTP
const transformedFile = await nhost.storage.getFile(fileId, {
  w: 400,
  h: 300,
  q: 90,
  f: 'webp'
})

Use Cases

Responsive Images: Generate different sizes for various screen resolutions
const thumbnailUrl = `${baseUrl}?w=150&h=150&q=80`
const mobileUrl = `${baseUrl}?w=400&q=85&f=webp`
const desktopUrl = `${baseUrl}?w=1200&q=90&f=webp`
Performance Optimization: Reduce file sizes and improve loading times
const optimizedUrl = `${baseUrl}?q=75&f=webp`
Image Effects: Add visual effects like blur for backgrounds
const blurredBg = `${baseUrl}?w=1920&h=1080&b=10&q=60`
Image transformations are cached at the CDN level for optimal performance. The first request might be slower as the transformed image is generated and cached.
I