---
title: Netlify Blobs
description: Netlify Blobs via @netlify/blobs. Auto-detects siteID and token on Netlify runtimes; falls back to env vars elsewhere.
---

## Installation

`@netlify/blobs` is an optional peer dependency of `files-sdk` - install alongside the SDK so the adapter's imports resolve at runtime.

```package-install
files-sdk @netlify/blobs
```

## Usage

```ts lineNumbers
import { Files } from "files-sdk";
import { netlifyBlobs } from "files-sdk/netlify-blobs";

// On Netlify Functions / Edge / build runtimes, siteID + token are
// auto-detected from NETLIFY_BLOBS_CONTEXT - pass them explicitly only
// when running outside Netlify (e.g. local scripts, your own server).
const files = new Files({
  adapter: netlifyBlobs({
    name: "uploads",
    // siteID: process.env.NETLIFY_SITE_ID,
    // token: process.env.NETLIFY_API_TOKEN,
    // deployScoped: false,           // true uses getDeployStore()
    // consistency: "eventual",       // or "strong"
    // region: "eu-central-1",        // site stores default to the API's region
  }),
});
```

Site-wide stores don't pick up your site's Functions region on their own, so set `region` to keep a store's data next to the functions that read it. Deploy-scoped stores already use the deploy's region. Changing `region` later doesn't move data a store already holds.

A missing site ID, token, or deploy ID (outside a Netlify runtime, with no env vars set) or an unknown `region` throws `Invalid`: the store can never work as configured, so it isn't retried.

Netlify Blobs has no native size, content-type, or last-modified fields, so the adapter packs them - plus `cacheControl` and user `metadata` - into Netlify's metadata map at upload time. `head()` and `download()` read them back, so the unified `StoredFile` shape works the same as on the cloud adapters.

## Options

<AutoTypeTable
  path="../../packages/files-sdk/src/netlify-blobs/index.ts"
  name="NetlifyBlobsAdapterOptions"
/>

## Compatibility

| Method | Status | Notes |
| --- | :-: | --- |
| `upload` | ⚠️ | Stream bodies are buffered up-front - Netlify's `set()` has no streaming form, so streaming uploads can't avoid materializing the body in memory. Resumable uploads (`control`) are not supported — Netlify Blobs has no chunked/resumable upload primitive. |
| `download` | ⚠️ | Netlify's `get()` has no range primitive, so `range` throws. |
| `delete` | ✅ |  |
| `list` | ⚠️ | Netlify's list response only carries key + etag - size, content type, and last-modified need a follow-up `head()` per item, so list entries always return `size: 0` and `contentType: 'application/octet-stream'`. Netlify's pagination cursor is internal to its SDK, so the adapter returns its own `cursor` (the last key of the page) whenever `limit` leaves entries behind, and each call re-walks Netlify's pages from the start up to that key. `limit` bounds server-side I/O for the first page; later pages cost a walk from the start. With `delimiter`, a folder counts against `limit` like a file. |
| `search` | ⚠️ | Built on `listAll` — inherits this adapter's `list` behavior above. Client-side key match (glob, regex, substring, exact). |
| `head` | ⚠️ | Netlify Blobs has no native size, content-type, or last-modified - the adapter packs them into Netlify's metadata at upload time and reads them back via `getMetadata`. Blobs written outside the SDK come back with `size: 0` and `contentType: 'application/octet-stream'` because the embedded fields are absent. |
| `exists` | ✅ |  |
| `copy` | ⚠️ | Read-then-write - Netlify Blobs has no server-side copy primitive, so the source is fetched and re-uploaded. Not server-side atomic; concurrent writes to the source between the get and put are not detected. |
| `url` | ❌ | No URL primitive - Netlify Blobs has no public URL or signing endpoint; reads always go through the SDK with the token. Use `download()` instead, or proxy the body through your application. |
| `signedUploadUrl` | ❌ | No presigned upload primitive - Netlify Blobs writes go through the SDK with the token. Upload server-side via the SDK or proxy uploads through your application. |

## Keys

`@netlify/blobs` puts each key into the request URL without encoding it, and URL parsing would rewrite some keys before they reach Netlify: a `#` or `?` would cut the key short (`Invoice #42.pdf` would be stored as `Invoice `), `\` would become `/`, tabs and newlines would be dropped, trailing spaces and control characters would be trimmed, and `.` / `..` segments would be resolved (`../x` even escapes the store). Netlify doesn't document whether it decodes `%` escapes, so a `%` can't be sent safely either. The adapter rejects keys like these with an `Invalid` error before sending any request. Spaces elsewhere in a key, non-ASCII characters, and `/` separators work as usual.

Netlify's SDK takes no `AbortSignal`, so a `signal` or `timeout` rejects the call but can't cancel a request already in flight.
