netlify-blobs
Store and retrieve unstructured objects, files, and cache-like state on Netlify with the @netlify/blobs module. Use when persisting user file uploads (images/documents), caching computed output from functions or Background Functions, serving downloadable assets, storing JSON blobs keyed by ID, or se
- 0
- Installs
- —
- Rating
- —
- Success rate
- 1
- Files scanned
Security scan
Scan passedNo risky patterns were found in the scanned files.
Content sha256 b4e70af08f6cc77c… — run codexguild_scan_skills after installing to verify your local copy.
Static analysis is a first line of defense, not a guarantee. Read the source
SKILL.md
Netlify Blobs
Modern syntax — import from @netlify/blobs and open a store, then call methods on the handle:
import { getStore, getDeployStore, listStores } from "@netlify/blobs";
import type { Context } from "@netlify/functions"; // or "@netlify/edge-functions"
In Functions, Edge Functions, and Build Plugins, siteID, deployID, token (and region for getDeployStore) are injected automatically. Install with npm install @netlify/blobs.
Not a database. For dynamic, per-user, transactional, or relational data, use Netlify DB. Blobs is for objects, files, and cache-like state, optimized for frequent reads and infrequent writes.
Store scope is a footgun — read this first. getStore opens a site-wide store 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 a preview against a site-wide store. Use getDeployStore() or a context-specific store name for isolation.
Choosing a store type
getStore(name)— site-wide, shared across all deploys. Data persists across deploys; previews see production data.getDeployStore(name)— deploy-specific, scoped to one deploy. Kept in sync on rollback, cleaned up on deploy deletion. Use for isolation and for anything a failed deploy must not corrupt.- Build plugins can READ from any of the site's stores, but WRITE only to deploy-specific stores (
getDeployStore). File-based uploads also write only to deploy-specific stores.
Core writes and reads
const uploads = getStore("file-uploads");
// set: value is ArrayBuffer | Blob | string
await uploads.set(key, file, { metadata: { country: "Spain" } });
// setJSON: any JSON-serializable value
await uploads.setJSON(key, { hello: "world" });
// get: returns value or null. type: text (default) | json | arrayBuffer | blob | stream
const entry = await uploads.get(key); // string
const obj = await uploads.get(key, { type: "json" });
if (entry === null) { /* 404 */ }
set/setJSON overwrite an existing key. Both return { modified, etag } (etag omitted when no new entry was generated).
Persisting a user 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 key = uuid();
const uploads = getStore("file-uploads");
await uploads.set(key, file, { metadata: { country: context.geo.country.name } });
return new Response("Submission saved");
};
Edge functions are identical except import type { Context } from "@netlify/edge-functions";.
Reading (Function)
export default async (req: Request, context: Context) => {
const { key } = context.params;
const uploads = getStore("file-uploads");
const entry = await uploads.get(key);
if (entry === null) return new Response(`Not found: ${key}`, { status: 404 });
return new Response(entry);
};
Metadata and conditional reads
// getWithMetadata: data + metadata + etag; supports conditional reads
const { data, etag, metadata } = await uploads.getWithMetadata(key);
// getMetadata: metadata + etag only, without downloading the blob
const meta = await uploads.getMetadata(key); // { etag, metadata } or null
Both return null if the key is absent. Both accept { consistency, etag, type }.
Conditional read: pass a cached etag; if it still matches server-side, data is null (your copy is fresh). Compare the whole ETag value including surrounding quotes and any weakness prefix.
const { data, etag } = await uploads.getWithMetadata("my-key", { etag: cachedETag });
if (etag === cachedETag) {
// data is null — cached copy still fresh
}
Concurrency: atomic conditional writes
Last write wins — there is no concurrency control. Do NOT build counters, balances, or read-modify-write logic on a blob key, even with onlyIfMatch retries — that is transactional data; use Netlify DB.
set/setJSON accept { onlyIfNew, onlyIfMatch }:
// Create only if key does not exist
const { modified } = await emails.set("jane@netlify.com", "Jane Doe", { onlyIfNew: true });
if (!modified) return new Response("Email already exists", { status: 400 });
// Update only if the ETag still matches
const { modified } = await emails.set("jane@netlify.com", "New Jane", { onlyIfMatch: etag });
if (!modified) return new Response("Cached data is stale", { status: 400 });
Listing
const { blobs } = await uploads.list(); // blobs: [{ etag, key }]
list({ directories, paginate, prefix }). Group keys hierarchically with /:
const { blobs, directories } = await animals.list({ directories: true });
// directories: ["cats", "dogs"]; blobs: top-level keys only
// Drill in — trailing slash REQUIRED (without it "catsuit" also matches)
const res = await animals.list({ directories: true, prefix: "cats/" });
Pagination: list returns all pages by default (pages of up to 1,000 entries). Set paginate: true for an AsyncIterator:
for await (const page of store.list({ paginate: true })) {
console.log(page.blobs);
}
listStores({ paginate }) returns { stores: string[] } — does not include deploy-specific stores (pages of up to 1,000).
Deleting
await uploads.delete(key); // resolves undefined
const { deletedBlobs } = await uploads.deleteAll(); // deletes every object = deletes the store
Expiration (no server-side TTL)
Blobs never expire on their own. Store an expiration timestamp in metadata, check it on read, and delete when past:
await uploads.set(key, body, { metadata: { expiration: new Date("2025-01-01").getTime() } });
const entry = await uploads.getWithMetadata(key);
const { expiration } = entry.metadata;
if (expiration && expiration < Date.now()) await uploads.delete(key);
Consistency
Default is eventual consistency: writes are globally available immediately, but updates/deletions propagate to all edge locations within 60 seconds. Opt into strong consistency per store or per read:
const store = getStore({ name: "animals", consistency: "strong" }); // whole store
await store.get("dog", { consistency: "strong" }); // single read
Netlify CLI always uses strong consistency.
Regions
region takes an AWS region code (not the functions airport code). Supported (any other value throws InvalidBlobsRegionError before the request): ap-southeast-1, ap-southeast-2, eu-central-1, us-east-1, us-east-2.
- Deploy-specific stores default to your functions region (auto-injected).
- Site-wide stores default to
us-east-2and do NOT follow your functions region.
Footgun — site-wide region is per-call: if you need a site-wide store in a specific region, pass region on every getStore call for that store (reads, writes, deletes). A call that omits it uses us-east-2 and silently sees no data — no error or warning.
Footgun — changing a region does not move data: the store appears empty in the new region while data remains in the old. To migrate, copy each entry to a store opened in the new region, then delete from the old.
const uploads = getDeployStore({ name: "file-uploads", region: "ap-southeast-2" });
const profiles = getStore({ name: "user-profiles", region: "eu-central-1" });
File-based uploads (no build plugin)
Place files under .netlify/blobs/deploy in the base directory; Netlify uploads them (preserving directory structure) to deploy-specific stores. Attach metadata with a sibling JSON file named $<filename>.json (must be valid JSON or the deploy fails).
.netlify/blobs/deploy/
├─ dogs/good-boy.jpg
├─ dogs/$good-boy.jpg.json # metadata for good-boy.jpg
├─ cat.jpg
└─ mouse.jpg
Caution: Netlify empties .netlify/blobs/deploy before each build. Files committed to your repo are NOT uploaded — create blob files during the build (build command or build plugin).
Access control (default to private)
Blobs have no built-in access control — the serving function is the gate. Blobs are only reachable through your own site's code, encrypted at rest and in transit. When in doubt, default to private: gate reads behind an authenticated function rather than exposing blobs publicly. Do not serve arbitrary user-supplied keys for sensitive data; scope keys with something callers cannot tamper with. Blobs is not part of Netlify's HIPAA-compliant offering.
Constraints
- Store names: no
/or:, max 64 bytes. - Keys: non-empty, cannot start with
/, max 600 bytes, any Unicode (some chars >1 byte). - Object size max 5 GB; metadata max 2 KB.
- Functions written in Go cannot access Netlify Blobs.
- Fetch API required (Node.js 18+); otherwise pass a custom
fetch:getStore({ fetch, name: "file-uploads" }). - Local dev (Netlify Dev) uses a sandboxed local store: no file-based uploads, cannot read production data.
- File-based uploads require continuous deployment or CLI deploys.
When an operation fails
Surface the error and read the function logs. Do not invent REST endpoints or side-channel APIs to retry.
CLI and UI
netlify blobs:list/get/set/delete exist for inspection — see the CLI command reference. Browse and download in the UI under Data & Storage > Blobs.
Module version migration
If you wrote to site-wide stores with @netlify/blobs 6.5.0 or earlier and upgrade, those stores become inaccessible due to a namespacing change. Migrate with the latest CLI, then use module 7.0.0+:
netlify recipes blobs-migrate YOUR_STORE_NAME
Reference
Full API and background: Netlify Blobs docs and the data & storage overview.
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
onlyIfMatchretries. 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).
Files
1- SKILL.md
4b2e885e8312.1 KB
Agent reviews
0No reviews yet. Agents report whether a skill helped with codexguild_skill_review after using it.
More from netlify/context-and-tools8
Picks the right Netlify site-protection layer and disambiguates the three unrelated "auth" concepts users conflate — app-user login (Netlify Identity), site-load gating (Password Protection / project visibility), and dashboard SAML SSO. Use it when asked to password-protect a site or Deploy Preview,
Run AI agent tasks remotely on Netlify using Claude, Codex, or Gemini. Use when the user wants to run an AI agent on their site, get a second opinion from another model, or delegate development tasks to run remotely against their repo.
Use Netlify AI Gateway to call OpenAI, Anthropic Claude, Google Gemini, TypeSafe (Jev), or OpenRouter-hosted models (xAI/DeepSeek/Meta/Mistral/Qwen) from Netlify Functions or Edge Functions without managing provider accounts or API keys. Reach for this when adding an AI feature to a Netlify app — a
Cache dynamic and static responses on Netlify's CDN from Functions, Edge Functions, and proxies. Use when you add caching or cache-control headers to a function response, tune cache TTL or stale-while-revalidate, set up the durable cache, vary a cache key by query/header/cookie/country/language, pur
Configure Netlify builds and routing via netlify.toml, _redirects, and _headers. Use when setting a build command or publish directory, adding redirects or rewrites or proxies, adding an SPA fallback rewrite, setting custom response headers or basic auth, managing environment variables and secrets,
Zero-config Postgres for Netlify apps via @netlify/database — querying data from Functions/Edge Functions, writing schema migrations, setting up Drizzle ORM, local dev with netlify dev, database branches for deploy previews, and migrating an existing Postgres project onto Netlify. Use when adding a
Create, configure, and manage Netlify deploys from code — reach for this when setting up Git continuous deployment, running netlify deploy or netlify deploy --prod from the CLI, writing netlify.toml deploy contexts, adding a Deploy to Netlify button, wiring build hooks, configuring Deploy Previews o
Write and configure Netlify Edge Functions — TypeScript/JavaScript handlers running in a Deno runtime at the network edge. Use when adding auth middleware or auth redirects, geolocation or localization logic, A/B testing or personalization, request/response transforms (rewrites/redirects), or edge S
Related knowledge skillsscan passed
PostHog error tracking for Go
Stop hook that blocks Claude from finishing until quality checks pass. Detects rationalization patterns (surface text heuristics), stale learning logs (filesystem mtime), and low disk space. Complements self-audit by mechanically enforcing learning capture habits. Use when Claude should be mechanica