---
name: netlify-blobs
description: Store and retrieve files and other unstructured data using Netlify Blobs object storage. Use for uploaded files, images, video, pre-rendered content, cached responses, or large JSON documents that do not need relational querying. For any database-like use cases (records, collections, queryable application state), use Netlify Database instead.
---

# Netlify Blobs

Netlify Blobs is a zero-configuration object store optimized for frequent reads and infrequent writes. It provides
persistent storage scoped to your site with automatic provisioning and access control. The API will be the same across
all compute types (serverless functions, edge functions, build plugins, etc.).

Requires Fetch API support (Node.js 18+ recommended).

## When to use Netlify Blobs

Use Netlify Blobs for **unstructured object data** — things that are naturally a file or a self-contained blob:

- Uploaded files (user uploads, documents, attachments)
- Images, video, audio, and other binary assets
- Pre-rendered pages, HTML snippets, or build artifacts tied to a deploy
- Cached responses from upstream APIs or expensive computations
- Large JSON documents that are read and written as a whole (e.g. a prerender manifest, a cached API payload)

## When NOT to use Netlify Blobs

For ANY database-like use cases, use **Netlify Database** (see the `netlify-database` skill) — not Blobs.

That includes:

- Records, rows, or collections of entities (users, posts, orders, sessions, comments, …)
- Data you need to query, filter, sort, join, or count
- Data with relationships between different kinds of entities (users who own posts, posts with comments, …)
- Application state that evolves over time and needs to be read and updated field-by-field
- Anything a developer would naturally model as a table with columns

Blobs is an **object store**, not a database. Treating it as one — for example, storing one blob per user keyed by
user ID — works for trivial cases but breaks down as soon as you need to query or relate data. Netlify Database is
the correct primitive for those use cases, is equally zero-configuration, and handles relational data, SQL queries,
transactions, and migrations out of the box.

## Quick Start

Install the package and use `getStore` to start storing data:

```bash
npm install @netlify/blobs
```

```typescript
import { getStore } from '@netlify/blobs'

const store = getStore('my-store')

// Write
await store.set('greeting', 'Hello, world!')
await store.setJSON('config', { theme: 'dark', lang: 'en' })

// Read
const greeting = await store.get('greeting', { type: 'text' })
const config = await store.get('config', { type: 'json' })

// Delete
await store.delete('greeting')
```

## API Reference

### Global Functions

#### `getStore(name: string | StoreOptions): Store`

Returns a store instance for a site-level namespace. Data persists across deploys.

```typescript
import { getStore } from '@netlify/blobs'

// Simple usage
const store = getStore('my-store')

// With options
const store = getStore({
  name: 'my-store',
  consistency: 'strong',
})
```

**`StoreOptions`:**

| Property | Type | Description |
|----------|------|-------------|
| `name` | `string` | Store namespace (required) |
| `consistency` | `'strong' \| 'eventual'` | Consistency model (default: `'eventual'`) |

ONLY add the options argument if the user needs strong consistency.

#### `getDeployStore(options?: DeployStoreOptions): Store`

Returns a store scoped to a specific deploy. Data is isolated per deploy and immutable after deploy finishes.

```typescript
import { getDeployStore } from '@netlify/blobs'

const store = getDeployStore()
```

Use deploy stores for data that should be tied to a specific deploy snapshot (e.g., pre-rendered page data, build
artifacts). ONLY add the options argument if the user needs strong consistency.

#### `listStores(options?: ListStoresOptions): Promise<{ stores: string[] }>`

Lists all site-level store names. Supports pagination for sites with many stores.

```typescript
import { listStores } from '@netlify/blobs'

const { stores } = await listStores()
console.log(stores) // ['my-store', 'uploads', ...]

// Paginated iteration
for await (const page of listStores({ paginate: true })) {
  console.log(page.stores)
}
```

DO NOT pass options unless paginating.

### Store Methods

THESE ARE THE ONLY STORE METHODS. DO NOT MAKE UP NEW ONES.

#### `set(key: string, value: string | ArrayBuffer | Blob, options?: SetOptions): Promise<void>`

Stores a value. Supports strings, binary data (ArrayBuffer), and Blob objects.

```typescript
await store.set('my-key', 'my-value')
await store.set('binary-key', new Uint8Array([1, 2, 3]))
await store.set('with-meta', 'value', {
  metadata: { contentType: 'text/plain', author: 'system' },
})
```

**`SetOptions`:**

| Property | Type | Description |
|----------|------|-------------|
| `metadata` | `Record<string, string>` | Custom metadata (max 64 KB total) |

NEVER add metadata unless the user explicitly instructs you to.

#### `setJSON(key: string, value: any, options?: SetOptions): Promise<void>`

Stores a JSON-serializable value. Automatically serializes with `JSON.stringify`.

```typescript
await store.setJSON('prerender-manifest', { routes: ['/', '/about'], generatedAt: Date.now() })
await store.setJSON('cache/weather-api', { city: 'Lisbon', temperature: 22, fetchedAt: Date.now() })
```

NEVER add metadata unless the user explicitly instructs you to.

#### `get(key: string, options?: GetOptions): Promise<string | Blob | object | ArrayBuffer | null>`

Retrieves a value by key. Returns `null` if the key does not exist.

```typescript
// As text (default)
const text = await store.get('my-key', { type: 'text' })

// As JSON
const data = await store.get('config', { type: 'json' })

// As ArrayBuffer
const binary = await store.get('binary-key', { type: 'arrayBuffer' })

// As Blob
const blob = await store.get('image', { type: 'blob' })

// As ReadableStream
const stream = await store.get('large-file', { type: 'stream' })
```

**`GetOptions.type`:** `'text'` | `'json'` | `'arrayBuffer'` | `'blob'` | `'stream'`

ALWAYS use `store.get('key', { type: 'json' })` instead of `JSON.parse(await store.get('key'))`. NEVER add the options
argument unless you need a specific type.

#### `getWithMetadata(key: string, options?: GetOptions): Promise<{ data, metadata } | null>`

Retrieves a value along with its custom metadata.

```typescript
const result = await store.getWithMetadata('my-key', { type: 'text' })
if (result) {
  console.log(result.data)     // the value
  console.log(result.metadata) // { contentType: '...', ... }
}
```

NEVER add the second getOpts arg unless you need an explicit type or have an etag to check against. AVOID adding it
unless it's reliably available but IF an etag is provided, it will only return the blob if the etag is different than
what's stored.

#### `getMetadata(key: string): Promise<{ metadata: Record<string, string> } | null>`

Retrieves only the metadata for a key, without downloading the value. Useful for checking existence or reading metadata
on large blobs.

```typescript
const result = await store.getMetadata('my-key')
if (result) {
  console.log(result.metadata)
}
```

NEVER add the second getOpts arg unless you need an explicit type or have an etag to check against. AVOID adding it
unless it's reliably available but IF an etag is provided, it will only return the blob if the etag is different than
what's stored.

#### `list(options?: ListOptions): Promise<{ blobs: ListEntry[], directories: string[] }>`

Lists entries in the store. By default retrieves all pages. Use `paginate: true` for an `AsyncIterable`.

```typescript
// List all
const { blobs } = await store.list()

// With prefix filter
const { blobs } = await store.list({ prefix: 'uploads/' })

// With hierarchical browsing
const { blobs, directories } = await store.list({
  prefix: 'uploads/',
  directories: true,
})

// Paginated iteration
for await (const page of store.list({ paginate: true })) {
  for (const blob of page.blobs) {
    console.log(blob.key)
  }
}
```

**`ListOptions`:**

| Property | Type | Description |
|----------|------|-------------|
| `prefix` | `string` | Filter keys by prefix |
| `directories` | `boolean` | Group by `/` delimiter |
| `paginate` | `boolean` | Return `AsyncIterable` for page-by-page iteration |
| `cursor` | `string` | Pagination cursor from previous response |
| `limit` | `number` | Max entries per page |

**`ListEntry`:**

| Property | Type | Description |
|----------|------|-------------|
| `key` | `string` | Blob key |
| `etag` | `string` | Content hash |

#### `delete(key: string): Promise<void>`

Deletes a single blob. No error if the key does not exist.

```typescript
await store.delete('my-key')
```

## Storage Scopes

### Site-Level Stores (`getStore`)

Data persists across all deploys and is shared by all functions and builds for the site. Use for uploaded files,
cached responses, and other unstructured objects that should survive redeployments. (For structured application
state — records, collections, anything relational — use Netlify Database instead.)

```typescript
import { getStore } from '@netlify/blobs'

const store = getStore('user-uploads')
```

### Deploy-Specific Stores (`getDeployStore`)

Data is scoped to a single deploy. Use for build-generated data, pre-rendered content, or data that should be
immutable after deploy. Build plugins and file-based uploads must write to deploy-specific stores.

```typescript
import { getDeployStore } from '@netlify/blobs'

const store = getDeployStore()
```

**Production isolation pattern:** Use a global store in production and deploy store otherwise to prevent non-production
data from polluting global stores:

```typescript
import { getStore, getDeployStore } from '@netlify/blobs'

function getBlobStore(...storeOptions) {
  if (Netlify.context?.deploy.context === 'production') {
    return getStore(...storeOptions)
  }
  return getDeployStore(...storeOptions)
}

const store = getBlobStore('app-data')
```

## Consistency Models

| Model | Default | Propagation | Use Case |
|-------|---------|-------------|----------|
| `eventual` | Yes | Up to 60 seconds | High-read workloads, caching |
| `strong` | No | Immediate | Critical data, counters |

Last-write-wins: concurrent writes to the same key result in the final write persisting. Add object-locking mechanisms
if you need concurrency guarantees.

```typescript
// Strong consistency when you need immediate read-after-write
const store = getStore({ name: 'counters', consistency: 'strong' })
```

## File-Based Uploads

Deploy blobs by placing files in the `.netlify/blobs/deploy/` directory at build time. These are written to a
deploy-specific store automatically.

```
.netlify/blobs/deploy/
  my-key.txt              # Value stored with key "my-key.txt"
  $my-key.txt.json        # Optional metadata for "my-key.txt"
  nested/path/data.json   # Key is "nested/path/data.json"
```

**Metadata pattern:** Create a `$filename.json` sibling file:

```json
{
  "contentType": "text/plain",
  "author": "build-pipeline"
}
```

Read file-based blobs in functions using `getDeployStore`:

```typescript
import { getDeployStore } from '@netlify/blobs'

const store = getDeployStore()
const data = await store.get('my-key.txt', { type: 'text' })
```

## Limits

| Resource | Limit |
|----------|-------|
| Store name | 64 bytes |
| Key | 600 bytes |
| Object size | 5 GB |
| Metadata per object | 64 KB |
| Stores per site | Unlimited |
| Objects per store | Unlimited |

## Common Errors & Solutions

### "Store not found" or empty reads

**Cause:** Store name mismatch or reading from wrong scope.

**Fix:**

1. Verify the store name matches exactly (case-sensitive)
2. Use `listStores()` to confirm the store exists
3. Ensure you're using `getStore` for site-level data and `getDeployStore` for deploy-scoped data

### "The environment has not been configured to use Netlify Blobs"

**Cause:** Missing Blobs environment configuration in local development.

**Fix:**

1. Install the framework plugin (`@netlify/vite-plugin`, `@netlify/nuxt`, or `@netlify/vite-plugin-tanstack-start`)
2. Deploy to Netlify or use the Vite plugin for local env configuration
3. Ensure the site has at least one production deploy

This does NOT apply to legacy V1 functions which require manual siteID/token configuration.

### "Unauthorized" or permission errors

**Cause:** Function is not running in a Netlify environment.

**Fix:**

1. Deploy to Netlify
2. Ensure the site has at least one production deploy
3. Confirm the function is deployed to Netlify (not a third-party host)

### Stale reads after writing

**Cause:** Default eventual consistency model (up to 60s propagation).

**Fix:**

1. Use strong consistency if immediate reads are required:
   ```typescript
   const store = getStore({ name: 'my-store', consistency: 'strong' })
   ```
2. Note that strong consistency has higher latency

### Large object upload timeouts

**Cause:** Object exceeds function timeout or network limits.

**Fix:**

1. Verify object is under 5 GB
2. For large uploads, use file-based uploads via `.netlify/blobs/deploy/` at build time
3. For function uploads, ensure function timeout is sufficient (background functions have 15-min timeout)
