Guide for using Netlify Blobs for file and asset storage — images, documents, uploads, exports, cached binary artifacts. Covers getStore(), CRUD operations, metadata, listing, deploy-scoped vs site-scoped stores, and local development. Do NOT use Blobs as a dynamic data store — use Netlify Database for that.
Modern syntax — import from @netlify/blobs, open a store, operate on it:
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:
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.
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.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.onlyIfMatch retries. That's transactional data; use Netlify DB.consistency: "strong" if a read must see the latest write immediately (slower reads).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.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.
Persist an upload (Function):
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:
const uploads = getStore("json-uploads");
await uploads.setJSON(key, data, { metadata: { … } });
Read a blob (null if missing):
const entry = await uploads.get(key);
if (entry === null) return new Response("Not found", { status: 404 });
return new Response(entry);
Read with metadata:
const { data, metadata } = await uploads.getWithMetadata(key);
Delete / delete a whole store:
await uploads.delete(key);
const { deletedBlobs } = await uploads.deleteAll(); // 0 if store didn't exist
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.
onlyIfNew: true — write only if the key does not exist.onlyIfMatch: etag — write only if current ETag matches (optimistic concurrency).modified boolean to detect success/failure.const { modified } = await emails.set("jane@netlify.com", "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.)
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.
const { blobs, directories } = await animals.list({ directories: true });
const cats = await animals.list({ directories: true, prefix: "cats/" });
Server pages up to 1,000 entries (list) / 1,000 stores (listStores). Handled automatically by default; pass paginate: true for an AsyncIterator:
for await (const entry of store.list({ paginate: true })) {
console.log(entry.blobs);
}
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.
Default is eventual (single-region, edge-cached; propagation within 60s). Opt into strong per store or per read:
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.
Valid regions (a smaller set than the function regions): us-east-1, us-east-2, eu-central-1, ap-southeast-1, ap-southeast-2.
getDeployStore) default to your functions region; region is auto-set in Functions/Edge Functions/Build Plugins. Override explicitly:
const uploads = getDeployStore({ name: "file-uploads", region: "ap-southeast-2" });
getStore) default to us-east-2 and do not follow your functions region. region is never auto-set here.
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.
fetchIf you can't use Node.js 18, supply your own fetch:
const uploads = getStore({ fetch, name: "file-uploads" });
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.
Roll your own: set with a timestamp in metadata → getWithMetadata to check → delete if expired.
netlify blobs:list/get/set/delete exist for inspection — see the CLI blobs command reference for details. The CLI always uses strong consistency and requires a site-wide store.
Netlify Dev uses a sandboxed local store: no file-based uploads, and you cannot read production data locally.
/ or :, max 64 bytes./, any Unicode, max 600 bytes.@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:
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 -->These are org conventions, not docs facts — merged into the rendered skill by ctx-gen and never generated. Owned by the skills maintainer.
netlify blobs:list/get/set/delete exist for inspection; the CLI
reference is their source of truth — link, don't restate.getDeployStore() or a context-specific store name for isolation.onlyIfMatch retries. That's transactional data; use Netlify DB.getDeployStore).skillbazaar install netlify-blobs --agent claudeSign in (free) to install skills with the CLI.
Author
@netlify
on GitHub
Published by