Contenido del repositorio de origen con títulos, ejemplos, código, tablas, enlaces e imágenes preservados.
Netlify Blobs
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 missingDeploy-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.
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.
getStoredefaults tous-east-2regardless of where your functions run — no error or warning is raised. To use another region you must passregionon everygetStorecall for that store (reads, writes, deletes); a call that omits it hitsus-east-2and 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
onlyIfMatchretries. 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):
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 existAPI 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
modifiedboolean 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.)
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.
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:
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:
const store = getStore({ name: "animals", consistency: "strong" }); // store level
const dog = await store.get("dog", { consistency: "strong" }); // operation levelThe Netlify CLI always uses strong consistency.
Regions
Valid regions (a smaller set than the function regions): us-east-1, us-east-2, eu-central-1, ap-southeast-1, ap-southeast-2.
- Deploy-specific stores (
getDeployStore) default to your functions region;regionis auto-set in Functions/Edge Functions/Build Plugins. Override explicitly:
const uploads = getDeployStore({ name: "file-uploads", region: "ap-southeast-2" });- Site-wide stores (
getStore) default tous-east-2and do not follow your functions region.regionis 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.
Custom fetch
If you can't use Node.js 18, supply your own fetch:
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 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:
netlify recipes blobs-migrate YOUR_STORE_NAMEMigrated 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.
- Blobs is not a database. For dynamic, per-user, or transactional data,
use Netlify DB — Blobs is for objects, files, and cache-like state.
- When a store operation fails, surface the error and read the function
logs — do not invent REST endpoints or side-channel APIs to retry.
netlify blobs:list/get/set/deleteexist for inspection; the CLI
reference is their source of truth — link, don't restate.
- 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.
- 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.
- 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.
- Build plugins: state BOTH halves — they can read from any of the site's
stores, but write only to deploy-specific stores (getDeployStore).

