netlify-access-control
Picks the right Netlify protection layer for a deployed site and disambiguates the three unrelated things people call "auth". Use when a developer wants to…
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
$ npx -y skills add netlify/context-and-tools --skill netlify-blobs --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/netlify-blobsContext 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
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.
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.
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.
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`.
const uploads = getStore("json-uploads");
await uploads.setJSON(key, data, { metadata: { country: context.geo.country.name } });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" })`.
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.
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/const { stores } = await listStores(); // does NOT include deploy-specific storesawait store.delete(key); // resolves undefined
const { deletedBlobs } = await store.deleteAll(); // deletes the whole store; 0 if it didn't existBuild 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);
};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);
}const { data, etag } = await uploads.getWithMetadata("my-key", { etag: caPublic 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.
Repo: netlify/context-and-tools
Picks the right Netlify protection layer for a deployed site and disambiguates the three unrelated things people call "auth". Use when a developer wants to…
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…
Use OpenAI, Anthropic, Google Gemini, or OpenRouter models from Netlify Functions or Edge Functions without managing provider API keys or accounts — the…
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…
Configure Netlify projects via netlify.toml and the _headers/_redirects files — covering build settings and deploy contexts alongside environment…
Zero-config Postgres for Netlify apps via @netlify/database — querying data from Functions/Edge Functions, writing schema migrations, setting up Drizzle ORM,…