Log in

netlify-blobs

All-time installs
2,590

Store and retrieve unstructured data like file uploads, images, documents, JSON, and cache-like state with Netlify Blobs. It is a zero-config key/value store accessible from Functions, Edge Functions, and Build Plugins. Reach for this when you handle a file or image upload, persist Background Function output like sitemaps or processed media, cache API responses, store per-deploy assets, or need a simple key/value store from a serverless function. Covers creating site and deploy stores, reading and writing values and JSON, listing keys with metadata, atomic conditional writes, consistency modes, region selection, and file-based uploads. Not for per-user, transactional, or relational data — use Netlify DB for that.

Other options

Summary

Store and retrieve unstructured data like file uploads, images, documents, JSON, and cache-like state with Netlify Blobs. It is a zero-config key/value store accessible from Functions, Edge Functions, and Build Plugins. Reach for this when you handle a file or image upload, persist Background Function output like sitemaps or processed media, cache API responses, store per-deploy assets, or need a simple key/value store from a serverless function. Covers creating site and deploy stores, reading and writing values and JSON, listing keys with metadata, atomic conditional writes, consistency modes, region selection, and file-based uploads. Not for per-user, transactional, or relational data — use Netlify DB for that.

Raw SKILL.md

13.5K bytes
---
name: netlify-blobs
description: Store and retrieve unstructured data like file uploads, images, documents, JSON, and cache-like state with Netlify Blobs. It is a zero-config key/value store accessible from Functions, Edge Functions, and Build Plugins. Reach for this when you handle a file or image upload, persist Background Function output like sitemaps or processed media, cache API responses, store per-deploy assets, or need a simple key/value store from a serverless function. Covers creating site and deploy stores, reading and writing values and JSON, listing keys with metadata, atomic conditional writes, consistency modes, region selection, and file-based uploads. Not for per-user, transactional, or relational data — use Netlify DB for that.
---

# Netlify Blobs

Modern syntax — import from `@netlify/blobs`, open a store, operate on it:

```ts
import { getStore } from "@netlify/blobs";
const store = getStore("file-uploads");        // site-wide
await store.set(key, value, { metadata: { … } });
const entry = await store.get(key);            // null if missing
```

Deploy-specific isolation:
```ts
import { getDeployStore } from "@netlify/blobs";
const store = getDeployStore("file-uploads");
```

Requires Fetch API (Node.js 18+). **Functions written in Go cannot access Blobs.** Not for per-user/transactional/relational data — use Netlify DB.

## Footguns (read first)

- **Site-scoped stores (`getStore`) are shared across ALL deploy contexts.** Code on a deploy preview reads, overwrites, and deletes production data. Never run destructive tests or seed throwaway data from previews — use `getDeployStore()` or a context-specific store name for isolation.
- **Site-wide stores do NOT follow your functions region.** `getStore` defaults to `us-east-2` regardless of where your functions run — no error or warning is raised. To use another region you must pass `region` on **every** `getStore` call for that store (reads, writes, deletes); a call that omits it hits `us-east-2` and won't see data held elsewhere. Changing a store's region does not migrate data.
- **Last write wins.** No concurrency control. Do not build counters, balances, or read-modify-write logic on a blob key — even with `onlyIfMatch` retries. That's transactional data; use Netlify DB.
- **No built-in access control.** The serving function is the gate. Default to private: gate reads behind an authenticated function rather than exposing blobs publicly. Treat user input as unsafe — don't serve arbitrary caller-supplied keys; scope keys with something callers can't tamper with.
- **Eventual consistency by default** — updates/deletions take up to 60s to propagate. Pass `consistency: "strong"` if a read must see the latest write immediately (slower reads).
- **When an operation fails, surface the error and read the function logs.** Do not invent REST endpoints or side-channel APIs to retry.

## Store selection

- `getStore(name)` — site-wide; persists across deploys, readable from all contexts.
- `getDeployStore(name)` — scoped to one deploy; use for isolation and for any write from a **Build Plugin** or file-based upload.
- **Build Plugins:** can *read* from any of the site's stores, but can *write* only to deploy-specific stores (`getDeployStore`).

Both accept a positional form `getStore(name, { region, siteID, token })` / `getDeployStore(name, { deployID, region, siteID, token })` or an object form `getStore({ name, consistency, region, siteID, token, fetch })`. `siteID`, `deployID`, and `token` are set automatically inside Functions/Edge Functions/Build Plugins; supply them explicitly only to override (e.g. `siteID` of another site you own). `region` is auto-set **only** for `getDeployStore` (defaults to your functions region); for `getStore` it is **not** auto-set and defaults to `us-east-2`. Site ID = API `site_id` = `NETLIFY_SITE_ID` = the UI's **Project ID**.

## Common tasks

Persist an upload (Function):
```ts
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";
import { v4 as uuid } from "uuid";

export default async (req: Request, context: Context) => {
  const form = await req.formData();
  const file = form.get("file") as File;
  const uploads = getStore("file-uploads");
  await uploads.set(uuid(), file, {
    metadata: { country: context.geo.country.name }
  });
  return new Response("Submission saved");
};
```

Persist JSON — use `setJSON`:
```ts
const uploads = getStore("json-uploads");
await uploads.setJSON(key, data, { metadata: { … } });
```

Read a blob (null if missing):
```ts
const entry = await uploads.get(key);
if (entry === null) return new Response("Not found", { status: 404 });
return new Response(entry);
```

Read with metadata:
```ts
const { data, metadata } = await uploads.getWithMetadata(key);
```

Delete / delete a whole store:
```ts
await uploads.delete(key);
const { deletedBlobs } = await uploads.deleteAll(); // 0 if store didn't exist
```

## API surface

Store-opening: `getStore`, `getDeployStore`, `listStores` (imported from `@netlify/blobs`).
Store instance methods: `get`, `getWithMetadata`, `getMetadata`, `set`, `setJSON`, `list`, `delete`, `deleteAll`.

**`set(key, value, { metadata, onlyIfMatch, onlyIfNew })`** — `value` is `ArrayBuffer | Blob | string`. Overwrites by default. Resolves `{ modified, etag }`.
**`setJSON(key, value, { metadata, onlyIfMatch, onlyIfNew })`** — same, `value` any JSON-serializable.
**`get(key, { consistency, type })`** — `type` one of `text` (default) / `json` / `arrayBuffer` / `blob` / `stream`. Resolves the value, or `null` if missing.
**`getWithMetadata(key, { consistency, etag, type })`** — resolves `{ data, etag, metadata }`, or `null` if missing. If `etag` matches the passed value, `data` is `null` (cache still fresh).
**`getMetadata(key, { consistency, etag, type })`** — resolves `{ metadata, etag }`, or `null` if missing. Check existence without downloading the blob.
**`list({ directories, paginate, prefix })`** — resolves `{ blobs: [{ etag, key }], directories: string[] }`.
**`listStores({ paginate })`** — resolves `{ stores: string[] }`. Does **not** include deploy-specific stores.
**`delete(key)`** — resolves `undefined`.
**`deleteAll()`** — resolves `{ deletedBlobs }`; deleting a store is deleting all its blobs.

### Atomic conditional writes
- `onlyIfNew: true` — write only if the key does not exist.
- `onlyIfMatch: etag` — write only if current ETag matches (optimistic concurrency).
- Inspect the returned `modified` boolean to detect success/failure.
```ts
const { modified } = await emails.set("[email protected]", "Jane Doe", { onlyIfNew: true });
if (!modified) return new Response("Email already exists", { status: 400 });
```
(These are for single-key create-if-absent / compare-and-set, not for building transactional counters.)

### Listing hierarchically
Group keys with `/`. `list({ directories: true })` returns top-level `directories` plus root blobs. Drill in with `prefix` — **the prefix must include a trailing slash** (`"cats/"`), or keys like `catsuit` also match.
```ts
const { blobs, directories } = await animals.list({ directories: true });
const cats = await animals.list({ directories: true, prefix: "cats/" });
```

### Pagination
Server pages up to **1,000** entries (`list`) / **1,000** stores (`listStores`). Handled automatically by default; pass `paginate: true` for an `AsyncIterator`:
```ts
for await (const entry of store.list({ paginate: true })) {
  console.log(entry.blobs);
}
```

### Conditional requests / local caching
Pass a cached `etag` to `getWithMetadata`/`getMetadata`; if it matches, `data` is `null` (your copy is fresh). Compare the whole value including surrounding quotes and any weakness prefix.

## Configuration

### Consistency
Default is **eventual** (single-region, edge-cached; propagation within 60s). Opt into **strong** per store or per read:
```ts
const store = getStore({ name: "animals", consistency: "strong" }); // store level
const dog = await store.get("dog", { consistency: "strong" });      // operation level
```
The Netlify CLI always uses strong consistency.

### Regions
Valid regions (a **smaller** set than the [function regions](https://docs.netlify.com/build/functions/configuration#region)): `us-east-1`, `us-east-2`, `eu-central-1`, `ap-southeast-1`, `ap-southeast-2`.

- **Deploy-specific stores** (`getDeployStore`) default to your functions region; `region` is auto-set in Functions/Edge Functions/Build Plugins. Override explicitly:
  ```ts
  const uploads = getDeployStore({ name: "file-uploads", region: "ap-southeast-2" });
  ```
- **Site-wide stores** (`getStore`) default to `us-east-2` and do **not** follow your functions region. `region` is never auto-set here.
  ```ts
  const profiles = getStore({ name: "user-profiles", region: "eu-central-1" });
  ```

**Gotcha — pass `region` on every call.** A site-wide store only reaches non-default-region data if **every** `getStore` call (reads, writes, deletes) passes the same `region`. Omit it and the call silently uses `us-east-2` — no error is raised. **Changing a store's region does not migrate data**: the store appears empty in the new region while the original data stays in the old one. To move data, copy each entry to a store opened in the new region, then delete from the old.

### Custom `fetch`
If you can't use Node.js 18, supply your own `fetch`:
```ts
const uploads = getStore({ fetch, name: "file-uploads" });
```

## File-based uploads (deploy-specific stores)

For framework/tool authors integrating without a build plugin. Place blob files under `.netlify/blobs/deploy` in the site's base directory; Netlify uploads them (preserving directory structure) after the build, before the deploy.

**Netlify deletes `.netlify/blobs/deploy` before each build** — files committed to the repo are NOT uploaded. You must create blob files *during* the build (build command or plugin).

Attach metadata with a sibling JSON file prefixing the blob filename with `$` and ending `.json`:
```
.netlify/blobs/deploy/
├─ dogs/
│  ├─ good-boy.jpg
│  └─ $good-boy.jpg.json
├─ cat.jpg
└─ mouse.jpg      (no metadata)
```
Metadata files must be valid JSON or the deploy fails. Requires continuous deployment or CLI deploys.

## Deploy-specific store lifecycle
- Kept in sync on rollback; cleaned up with automatic deploy deletion.
- **Downloading a deploy does NOT download deploy-specific blobs.**
- **Locking a published deploy does NOT prevent writing to its deploy-specific stores.**

## Expiration (no built-in TTL)
Roll your own: `set` with a timestamp in metadata → `getWithMetadata` to check → `delete` if expired.

## CLI
`netlify blobs:list/get/set/delete` exist for inspection — see the [CLI blobs command reference](https://cli.netlify.com/commands/blobs/) for details. The CLI always uses strong consistency and requires a site-wide store.

## Local development
Netlify Dev uses a sandboxed local store: no file-based uploads, and you cannot read production data locally.

## Constraints
- Store names: no `/` or `:`, max **64 bytes**.
- Keys: non-empty, cannot start with `/`, any Unicode, max **600 bytes**.
- Object max **5 GB**; metadata max **2 KB**. (Byte limits, not char counts — some UTF-8 chars are multi-byte.)
- Blobs encrypted at rest and in transit; accessible only through your own site.
- **Not** part of Netlify's HIPAA-compliant hosting offering.
- Review any third-party build plugin's code before trusting it with blob access.

## Migration (`@netlify/blobs` 6.5.0 → 7.0.0)
Site-wide stores written with v6.5.0 or earlier become inaccessible after upgrading (namespacing change). Migrate per store with the latest [Netlify CLI](https://docs.netlify.com/api-and-cli-guides/cli-guides/get-started-with-cli):
```sh
netlify recipes blobs-migrate YOUR_STORE_NAME
```
Migrated stores are accessible with v7.0.0+.

<!-- system: agent-context/blobs/system.md — human-owned, merged by ctx-gen; edit system.md, not this section -->
# Netlify house rules (blobs)

These are org conventions, not docs facts — merged into the rendered skill by
ctx-gen and never generated. Owned by the skills maintainer.

1. Blobs is not a database. For dynamic, per-user, or transactional data,
   use Netlify DB — Blobs is for objects, files, and cache-like state.
2. When a store operation fails, surface the error and read the function
   logs — do not invent REST endpoints or side-channel APIs to retry.
3. `netlify blobs:list/get/set/delete` exist for inspection; the CLI
   reference is their source of truth — link, don't restate.
4. Blobs have no built-in access control — the serving function is the gate.
   When in doubt, default to private: gate reads behind an authenticated
   function rather than exposing blobs publicly.
5. Site-scoped stores are shared across ALL deploy contexts — code on a
   deploy preview reads, overwrites, and deletes production data. Never run
   destructive tests or seed throwaway data from previews; use
   `getDeployStore()` or a context-specific store name for isolation.
6. Don't build counters, balances, or read-modify-write logic on a blob key —
   even with `onlyIfMatch` retries. That's transactional data; use Netlify DB.
7. Build plugins: state BOTH halves — they can read from any of the site's
   stores, but write only to deploy-specific stores (`getDeployStore`).

Security audits

SnykPASS
SocketPASS
Gen Agent Trust HubPASS