Skip to content
Deployment
Skill

/netlify-blobs

Store and retrieve unstructured objects, file uploads, and cache-like state on Netlify using the @netlify/blobs key/value API from Functions, Edge Functions, and Build Plugins. Use when a task involves saving user file or image uploads, persisting form or contact-form

From plugin
netlify-skills
3715 skills1 MCP
Install
$ npx -y skills add netlify/context-and-tools --skill netlify-blobs --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/netlify-blobs

Context preview

The summary Claude sees to decide when to auto-load this skill.

Store and retrieve unstructured objects, file uploads, and cache-like state on Netlify using the @netlify/blobs key/value API from Functions, Edge Functions, and Build Plugins. Use when a task involves saving user file or image uploads, persisting form or contact-form

SKILL.md

netlify-blobs.SKILL.md
name: netlify-blobs
description: Store and retrieve unstructured objects, file uploads, and cache-like state on Netlify using the @netlify/blobs key/value API from Functions, Edge Functions, and Build Plugins. Use when a task involves saving user file or image uploads, persisting form or contact-form submissions, storing generated output from Background Functions (sitemaps/processed media/bulk-email results), building read-only asset stores, adding client-side blob expiration, or wiring file-based blob uploads at deploy time. Not for per-user, transactional, or relational data (counters/balances/sessions) — reach for Netlify DB there instead.

Netlify Blobs

Modern import — reach for this:

import { getStore, getDeployStore, listStores } from "@netlify/blobs";

Install: `npm install @netlify/blobs`. Fetch API is required (built into Node 18+); otherwise pass a custom `fetch`.

Two ways to open a store — use the **options-object form** when you need `consistency` or a custom `fetch` (the string form cannot pass them):

const store = getStore("file-uploads");                          // string form
const store = getStore({ name: "animals", consistency: "strong" }); // options form

`siteID`, `token`, `deployID`, and `region` are set automatically inside Functions, Edge Functions, and Build Plugins — do not pass them manually there.

Choosing the store type — READ THIS FIRST

  • **`getStore(name)`** — site-scoped. Persists across deploys and is **shared across ALL deploy contexts**. Code on a Deploy Preview reads, overwrites, and deletes production data. **Never seed throwaway data or run destructive tests from a preview.**
  • **`getDeployStore(name)`** — scoped to one deploy; isolated from production. Use this for throwaway/per-deploy data, or use a context-specific store name for isolation.

Blobs have **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. Never accept an arbitrary caller-supplied key against a store holding sensitive data.

Common tasks

Persist a user upload with metadata (`set`)

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 Function form is identical but imports `Context` from `@netlify/edge-functions`.

Persist JSON (`setJSON`)

const uploads = getStore("json-uploads");
await uploads.setJSON(key, data, { metadata: { country: context.geo.country.name } });

Read a blob (`get`) — always null-check

const uploads = getStore("file-uploads");
const entry = await uploads.get(key);          // string by default
if (entry === null) {
  return new Response(`Could not find ${key}`, { status: 404 });
}
return new Response(entry);

Pass `type` for other formats: `get(key, { type: "json" | "arrayBuffer" | "blob" | "stream" | "text" })`.

Atomic conditional write

Write only if the key is new:

const { modified } = await store.set("jane@netlify.com", "Jane Doe", { onlyIfNew: true });
if (!modified) return new Response("Email already exists", { status: 400 });

Write only if the entry matches a known ETag (compare-and-swap):

const { modified } = await store.set(key, "New Jane", { onlyIfMatch: etag });
if (!modified) return new Response("Cached data is stale", { status: 400 });

**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.

List blobs

const { blobs } = await store.list();          // auto-paginates all pages
// blobs: [ { etag: "\"etag1\"", key: "..." }, ... ]

Manual pagination (returns an `AsyncIterator`):

for await (const entry of store.list({ paginate: true })) {
  console.log(entry.blobs);
}

Hierarchical listing — group keys with `/`, set `directories: true` to list one level, and use a **trailing slash** on `prefix` to drill in (without it, `cats` would also match `catsuit`):

const { blobs, directories } = await store.list({ directories: true });      // top level
const catList = await store.list({ directories: true, prefix: "cats/" });    // inside cats/

List stores

const { stores } = await listStores();   // does NOT include deploy-specific stores

Delete

await store.delete(key);                       // resolves undefined
const { deletedBlobs } = await store.deleteAll(); // deletes the whole store; 0 if it didn't exist

Build plugin — write to a deploy-specific store

Build plugins can **READ from any of the site's stores, but can WRITE only to deploy-specific stores** (`getDeployStore`).

import { readFile } from "node:fs/promises";
import { getDeployStore } from "@netlify/blobs";
import { v4 as uuid } from "uuid";

export const onPostBuild = async () => {
  const file = await readFile("some-file.txt", "utf8");
  const uploads = getDeployStore("file-uploads");
  await uploads.set(uuid(), file);
};

Client-side expiration (no server-side TTL)

Blobs have no TTL. Store a timestamp in metadata, check it on read, and `delete` when expired:

await uploads.set(key, await req.text(), {
  metadata: { expiration: new Date("2024-01-01").getTime() }
});
const entry = await uploads.getWithMetadata(key);
const { expiration } = entry.metadata;
if (expiration && expiration < Date.now()) {
  await uploads.delete(key);
}

Conditional read with ETag (`getWithMetadata`)

const { data, etag } = await uploads.getWithMetadata("my-key", { etag: ca
Read more
Ships withnetlify-skills

Public Netlify skills for AI coding agents. Each skill is a focused, factual reference for a Netlify platform primitive — designed to help agents build correctly on Netlify without needing to search docs.

Get the whole plugin

Other skills on netlify-skills.