{"id":27246,"plugin_id":"plugin_asdk_app_691f1f8f72408191afdbbdf8242bdf86","kind":"skill","collection_source":"plugin_package","comparison_source":null,"observed_at":"2026-10-07T00:02:43.416Z","digest":"9ca09ca7af1d5fc9d8df2bc6203ab5cc568ea6a512a7a2a6d68ce9f3392744bc","against":24990,"payload":{"description":"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 seeding deploy-specific data. Reach for this for key/value or object storage from Functions, Edge Functions, or Build Plugins — not for per-user, transactional, or relational data (use Netlify DB for that). Triggers include \"save an uploaded file\", \"cache API results\", \"store generated site map\", \"key/value store for a function\", or \"file uploads without a database\".","included_files":[],"name":"netlify-blobs","skill_md_contents":"---\nname: netlify-blobs\ndescription: 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 seeding deploy-specific data. Reach for this for key/value or object storage from Functions, Edge Functions, or Build Plugins — not for per-user, transactional, or relational data (use Netlify DB for that). Triggers include \"save an uploaded file\", \"cache API results\", \"store generated site map\", \"key/value store for a function\", or \"file uploads without a database\".\n---\n\n# Netlify Blobs\n\nModern syntax — import from `@netlify/blobs` and open a store, then call methods on the handle:\n\n```ts\nimport { getStore, getDeployStore, listStores } from \"@netlify/blobs\";\nimport type { Context } from \"@netlify/functions\"; // or \"@netlify/edge-functions\"\n```\n\nIn Functions, Edge Functions, and Build Plugins, `siteID`, `deployID`, `token` (and `region` for `getDeployStore`) are injected automatically. Install with `npm install @netlify/blobs`.\n\n**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.\n\n**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.\n\n## Choosing a store type\n\n- `getStore(name)` — site-wide, shared across all deploys. Data persists across deploys; previews see production data.\n- `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.\n- **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.\n\n## Core writes and reads\n\n```ts\nconst uploads = getStore(\"file-uploads\");\n\n// set: value is ArrayBuffer | Blob | string\nawait uploads.set(key, file, { metadata: { country: \"Spain\" } });\n\n// setJSON: any JSON-serializable value\nawait uploads.setJSON(key, { hello: \"world\" });\n\n// get: returns value or null. type: text (default) | json | arrayBuffer | blob | stream\nconst entry = await uploads.get(key);            // string\nconst obj = await uploads.get(key, { type: \"json\" });\nif (entry === null) { /* 404 */ }\n```\n\n`set`/`setJSON` overwrite an existing key. Both return `{ modified, etag }` (`etag` omitted when no new entry was generated).\n\n### Persisting a user upload (Function)\n\n```ts\nimport { getStore } from \"@netlify/blobs\";\nimport type { Context } from \"@netlify/functions\";\nimport { v4 as uuid } from \"uuid\";\n\nexport default async (req: Request, context: Context) => {\n  const form = await req.formData();\n  const file = form.get(\"file\") as File;\n  const key = uuid();\n  const uploads = getStore(\"file-uploads\");\n  await uploads.set(key, file, { metadata: { country: context.geo.country.name } });\n  return new Response(\"Submission saved\");\n};\n```\n\nEdge functions are identical except `import type { Context } from \"@netlify/edge-functions\";`.\n\n### Reading (Function)\n\n```ts\nexport default async (req: Request, context: Context) => {\n  const { key } = context.params;\n  const uploads = getStore(\"file-uploads\");\n  const entry = await uploads.get(key);\n  if (entry === null) return new Response(`Not found: ${key}`, { status: 404 });\n  return new Response(entry);\n};\n```\n\n## Metadata and conditional reads\n\n```ts\n// getWithMetadata: data + metadata + etag; supports conditional reads\nconst { data, etag, metadata } = await uploads.getWithMetadata(key);\n\n// getMetadata: metadata + etag only, without downloading the blob\nconst meta = await uploads.getMetadata(key); // { etag, metadata } or null\n```\n\nBoth return `null` if the key is absent. Both accept `{ consistency, etag, type }`.\n\n**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.\n\n```ts\nconst { data, etag } = await uploads.getWithMetadata(\"my-key\", { etag: cachedETag });\nif (etag === cachedETag) {\n  // data is null — cached copy still fresh\n}\n```\n\n## Concurrency: atomic conditional writes\n\n**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.\n\n`set`/`setJSON` accept `{ onlyIfNew, onlyIfMatch }`:\n\n```ts\n// Create only if key does not exist\nconst { modified } = await emails.set(\"jane@netlify.com\", \"Jane Doe\", { onlyIfNew: true });\nif (!modified) return new Response(\"Email already exists\", { status: 400 });\n\n// Update only if the ETag still matches\nconst { modified } = await emails.set(\"jane@netlify.com\", \"New Jane\", { onlyIfMatch: etag });\nif (!modified) return new Response(\"Cached data is stale\", { status: 400 });\n```\n\n## Listing\n\n```ts\nconst { blobs } = await uploads.list(); // blobs: [{ etag, key }]\n```\n\n`list({ directories, paginate, prefix })`. Group keys hierarchically with `/`:\n\n```ts\nconst { blobs, directories } = await animals.list({ directories: true });\n// directories: [\"cats\", \"dogs\"]; blobs: top-level keys only\n\n// Drill in — trailing slash REQUIRED (without it \"catsuit\" also matches)\nconst res = await animals.list({ directories: true, prefix: \"cats/\" });\n```\n\nPagination: `list` returns all pages by default (pages of up to 1,000 entries). Set `paginate: true` for an `AsyncIterator`:\n\n```ts\nfor await (const page of store.list({ paginate: true })) {\n  console.log(page.blobs);\n}\n```\n\n`listStores({ paginate })` returns `{ stores: string[] }` — **does not include deploy-specific stores** (pages of up to 1,000).\n\n## Deleting\n\n```ts\nawait uploads.delete(key);                        // resolves undefined\nconst { deletedBlobs } = await uploads.deleteAll(); // deletes every object = deletes the store\n```\n\n## Expiration (no server-side TTL)\n\nBlobs never expire on their own. Store an expiration timestamp in metadata, check it on read, and `delete` when past:\n\n```ts\nawait uploads.set(key, body, { metadata: { expiration: new Date(\"2025-01-01\").getTime() } });\nconst entry = await uploads.getWithMetadata(key);\nconst { expiration } = entry.metadata;\nif (expiration && expiration < Date.now()) await uploads.delete(key);\n```\n\n## Consistency\n\nDefault 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:\n\n```ts\nconst store = getStore({ name: \"animals\", consistency: \"strong\" }); // whole store\nawait store.get(\"dog\", { consistency: \"strong\" });                  // single read\n```\n\nNetlify CLI always uses strong consistency.\n\n## Regions\n\n`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`.\n\n- **Deploy-specific stores** default to your functions region (auto-injected).\n- **Site-wide stores** default to `us-east-2` and do NOT follow your functions region.\n\n**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.\n\n**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.\n\n```ts\nconst uploads = getDeployStore({ name: \"file-uploads\", region: \"ap-southeast-2\" });\nconst profiles = getStore({ name: \"user-profiles\", region: \"eu-central-1\" });\n```\n\n## File-based uploads (no build plugin)\n\nPlace 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).\n\n```\n.netlify/blobs/deploy/\n├─ dogs/good-boy.jpg\n├─ dogs/$good-boy.jpg.json   # metadata for good-boy.jpg\n├─ cat.jpg\n└─ mouse.jpg\n```\n\n**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).\n\n## Access control (default to private)\n\nBlobs 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.\n\n## Constraints\n\n- Store names: no `/` or `:`, max 64 bytes.\n- Keys: non-empty, cannot start with `/`, max 600 bytes, any Unicode (some chars >1 byte).\n- Object size max 5 GB; metadata max 2 KB.\n- Functions written in **Go cannot access Netlify Blobs**.\n- Fetch API required (Node.js 18+); otherwise pass a custom `fetch`: `getStore({ fetch, name: \"file-uploads\" })`.\n- Local dev (Netlify Dev) uses a sandboxed local store: no file-based uploads, cannot read production data.\n- File-based uploads require continuous deployment or CLI deploys.\n\n## When an operation fails\n\nSurface the error and read the function logs. Do not invent REST endpoints or side-channel APIs to retry.\n\n## CLI and UI\n\n`netlify blobs:list/get/set/delete` exist for inspection — see the [CLI command reference](https://cli.netlify.com/commands/blobs/). Browse and download in the UI under **Data & Storage > Blobs**.\n\n## Module version migration\n\nIf 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+:\n\n```sh\nnetlify recipes blobs-migrate YOUR_STORE_NAME\n```\n\n## Reference\n\nFull API and background: [Netlify Blobs docs](https://docs.netlify.com/build/data-and-storage/netlify-blobs/) and the [data & storage overview](https://docs.netlify.com/build/data-and-storage/overview/).\n\n<!-- system: agent-context/blobs/system.md — human-owned, merged by ctx-gen; edit system.md, not this section -->\n# Netlify house rules (blobs)\n\nThese are org conventions, not docs facts — merged into the rendered skill by\nctx-gen and never generated. Owned by the skills maintainer.\n\n1. Blobs is not a database. For dynamic, per-user, or transactional data,\n   use Netlify DB — Blobs is for objects, files, and cache-like state.\n2. When a store operation fails, surface the error and read the function\n   logs — do not invent REST endpoints or side-channel APIs to retry.\n3. `netlify blobs:list/get/set/delete` exist for inspection; the CLI\n   reference is their source of truth — link, don't restate.\n4. Blobs have no built-in access control — the serving function is the gate.\n   When in doubt, default to private: gate reads behind an authenticated\n   function rather than exposing blobs publicly.\n5. Site-scoped stores are shared across ALL deploy contexts — code on a\n   deploy preview reads, overwrites, and deletes production data. Never run\n   destructive tests or seed throwaway data from previews; use\n   `getDeployStore()` or a context-specific store name for isolation.\n6. Don't build counters, balances, or read-modify-write logic on a blob key —\n   even with `onlyIfMatch` retries. That's transactional data; use Netlify DB.\n7. Build plugins: state BOTH halves — they can read from any of the site's\n   stores, but write only to deploy-specific stores (`getDeployStore`).\n"},"changes":[{"path":"/description","type":"changed","before":"Guide for using Netlify Blobs object storage. Use when storing files, images, documents, or simple key-value data without a full database. Covers getStore(), CRUD operations, metadata, listing, deploy-scoped vs site-scoped stores, and local development.","after":"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 seeding deploy-specific data. Reach for this for key/value or object storage from Functions, Edge Functions, or Build Plugins — not for per-user, transactional, or relational data (use Netlify DB for that). Triggers include \"save an uploaded file\", \"cache API results\", \"store generated site map\", \"key/value store for a function\", or \"file uploads without a database\"."},{"path":"/included_files","type":"changed","before":[{"relative_path":"LICENSE.txt","size_in_bytes":10776},{"relative_path":"agents/openai.yaml","size_in_bytes":312},{"relative_path":"assets/netlify-small.svg","size_in_bytes":1291},{"relative_path":"assets/netlify.png","size_in_bytes":2686}],"after":[]},{"path":"/skill_md_contents","type":"changed","before":"---\nname: netlify-blobs\ndescription: Guide for using Netlify Blobs object storage. Use when storing files, images, documents, or simple key-value data without a full database. Covers getStore(), CRUD operations, metadata, listing, deploy-scoped vs site-scoped stores, and local development.\n---\n\n# Netlify Blobs\n\nNetlify Blobs is zero-config object storage available from any Netlify compute (functions, edge functions, framework server routes). No provisioning required.\n\n```bash\nnpm install @netlify/blobs\n```\n\n## Getting a Store\n\n```typescript\nimport { getStore } from \"@netlify/blobs\";\n\nconst store = getStore({ name: \"my-store\" });\n\n// Use \"strong\" consistency when you need immediate reads after writes\nconst store = getStore({ name: \"my-store\", consistency: \"strong\" });\n```\n\n## CRUD Operations\n\nThese are the **only** store methods. Do not invent others.\n\n### Create / Update\n\n```typescript\n// String or binary data\nawait store.set(\"key\", \"value\");\nawait store.set(\"key\", fileBuffer);\n\n// With metadata\nawait store.set(\"key\", data, {\n  metadata: { contentType: \"image/png\", uploadedAt: new Date().toISOString() },\n});\n\n// JSON data\nawait store.setJSON(\"key\", { name: \"Example\", count: 42 });\n```\n\n### Read\n\n```typescript\n// Text (default)\nconst text = await store.get(\"key\");                    // string | null\n\n// Typed retrieval\nconst json = await store.get(\"key\", { type: \"json\" });  // object | null\nconst stream = await store.get(\"key\", { type: \"stream\" });\nconst blob = await store.get(\"key\", { type: \"blob\" });\nconst buffer = await store.get(\"key\", { type: \"arrayBuffer\" });\n\n// With metadata\nconst result = await store.getWithMetadata(\"key\");\n// { data: any, etag: string, metadata: object } | null\n\n// Metadata only (no data download)\nconst meta = await store.getMetadata(\"key\");\n// { etag: string, metadata: object } | null\n```\n\n### Delete\n\n```typescript\nawait store.delete(\"key\");\n```\n\n### List\n\n```typescript\nconst { blobs } = await store.list();\n// blobs: [{ etag: string, key: string }, ...]\n\n// Filter by prefix\nconst { blobs } = await store.list({ prefix: \"uploads/\" });\n```\n\n## Store Types\n\n- **Site-scoped** (`getStore()`): Persist across all deploys. Use for most cases.\n- **Deploy-scoped** (`getDeployStore()`): Tied to a specific deploy lifecycle.\n\n## Limits\n\n| Limit | Value |\n|---|---|\n| Max object size | 5 GB |\n| Store name max length | 64 bytes |\n| Key max length | 600 bytes |\n\n## Local Development\n\nLocal dev uses a sandboxed store (separate from production). For Vite-based projects, install `@netlify/vite-plugin` to enable local Blobs access. Otherwise, use `netlify dev`.\n\n**Common error**: \"The environment has not been configured to use Netlify Blobs\" — install `@netlify/vite-plugin` or run via `netlify dev`.\n","after":"---\nname: netlify-blobs\ndescription: 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 seeding deploy-specific data. Reach for this for key/value or object storage from Functions, Edge Functions, or Build Plugins — not for per-user, transactional, or relational data (use Netlify DB for that). Triggers include \"save an uploaded file\", \"cache API results\", \"store generated site map\", \"key/value store for a function\", or \"file uploads without a database\".\n---\n\n# Netlify Blobs\n\nModern syntax — import from `@netlify/blobs` and open a store, then call methods on the handle:\n\n```ts\nimport { getStore, getDeployStore, listStores } from \"@netlify/blobs\";\nimport type { Context } from \"@netlify/functions\"; // or \"@netlify/edge-functions\"\n```\n\nIn Functions, Edge Functions, and Build Plugins, `siteID`, `deployID`, `token` (and `region` for `getDeployStore`) are injected automatically. Install with `npm install @netlify/blobs`.\n\n**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.\n\n**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.\n\n## Choosing a store type\n\n- `getStore(name)` — site-wide, shared across all deploys. Data persists across deploys; previews see production data.\n- `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.\n- **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.\n\n## Core writes and reads\n\n```ts\nconst uploads = getStore(\"file-uploads\");\n\n// set: value is ArrayBuffer | Blob | string\nawait uploads.set(key, file, { metadata: { country: \"Spain\" } });\n\n// setJSON: any JSON-serializable value\nawait uploads.setJSON(key, { hello: \"world\" });\n\n// get: returns value or null. type: text (default) | json | arrayBuffer | blob | stream\nconst entry = await uploads.get(key);            // string\nconst obj = await uploads.get(key, { type: \"json\" });\nif (entry === null) { /* 404 */ }\n```\n\n`set`/`setJSON` overwrite an existing key. Both return `{ modified, etag }` (`etag` omitted when no new entry was generated).\n\n### Persisting a user upload (Function)\n\n```ts\nimport { getStore } from \"@netlify/blobs\";\nimport type { Context } from \"@netlify/functions\";\nimport { v4 as uuid } from \"uuid\";\n\nexport default async (req: Request, context: Context) => {\n  const form = await req.formData();\n  const file = form.get(\"file\") as File;\n  const key = uuid();\n  const uploads = getStore(\"file-uploads\");\n  await uploads.set(key, file, { metadata: { country: context.geo.country.name } });\n  return new Response(\"Submission saved\");\n};\n```\n\nEdge functions are identical except `import type { Context } from \"@netlify/edge-functions\";`.\n\n### Reading (Function)\n\n```ts\nexport default async (req: Request, context: Context) => {\n  const { key } = context.params;\n  const uploads = getStore(\"file-uploads\");\n  const entry = await uploads.get(key);\n  if (entry === null) return new Response(`Not found: ${key}`, { status: 404 });\n  return new Response(entry);\n};\n```\n\n## Metadata and conditional reads\n\n```ts\n// getWithMetadata: data + metadata + etag; supports conditional reads\nconst { data, etag, metadata } = await uploads.getWithMetadata(key);\n\n// getMetadata: metadata + etag only, without downloading the blob\nconst meta = await uploads.getMetadata(key); // { etag, metadata } or null\n```\n\nBoth return `null` if the key is absent. Both accept `{ consistency, etag, type }`.\n\n**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.\n\n```ts\nconst { data, etag } = await uploads.getWithMetadata(\"my-key\", { etag: cachedETag });\nif (etag === cachedETag) {\n  // data is null — cached copy still fresh\n}\n```\n\n## Concurrency: atomic conditional writes\n\n**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.\n\n`set`/`setJSON` accept `{ onlyIfNew, onlyIfMatch }`:\n\n```ts\n// Create only if key does not exist\nconst { modified } = await emails.set(\"jane@netlify.com\", \"Jane Doe\", { onlyIfNew: true });\nif (!modified) return new Response(\"Email already exists\", { status: 400 });\n\n// Update only if the ETag still matches\nconst { modified } = await emails.set(\"jane@netlify.com\", \"New Jane\", { onlyIfMatch: etag });\nif (!modified) return new Response(\"Cached data is stale\", { status: 400 });\n```\n\n## Listing\n\n```ts\nconst { blobs } = await uploads.list(); // blobs: [{ etag, key }]\n```\n\n`list({ directories, paginate, prefix })`. Group keys hierarchically with `/`:\n\n```ts\nconst { blobs, directories } = await animals.list({ directories: true });\n// directories: [\"cats\", \"dogs\"]; blobs: top-level keys only\n\n// Drill in — trailing slash REQUIRED (without it \"catsuit\" also matches)\nconst res = await animals.list({ directories: true, prefix: \"cats/\" });\n```\n\nPagination: `list` returns all pages by default (pages of up to 1,000 entries). Set `paginate: true` for an `AsyncIterator`:\n\n```ts\nfor await (const page of store.list({ paginate: true })) {\n  console.log(page.blobs);\n}\n```\n\n`listStores({ paginate })` returns `{ stores: string[] }` — **does not include deploy-specific stores** (pages of up to 1,000).\n\n## Deleting\n\n```ts\nawait uploads.delete(key);                        // resolves undefined\nconst { deletedBlobs } = await uploads.deleteAll(); // deletes every object = deletes the store\n```\n\n## Expiration (no server-side TTL)\n\nBlobs never expire on their own. Store an expiration timestamp in metadata, check it on read, and `delete` when past:\n\n```ts\nawait uploads.set(key, body, { metadata: { expiration: new Date(\"2025-01-01\").getTime() } });\nconst entry = await uploads.getWithMetadata(key);\nconst { expiration } = entry.metadata;\nif (expiration && expiration < Date.now()) await uploads.delete(key);\n```\n\n## Consistency\n\nDefault 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:\n\n```ts\nconst store = getStore({ name: \"animals\", consistency: \"strong\" }); // whole store\nawait store.get(\"dog\", { consistency: \"strong\" });                  // single read\n```\n\nNetlify CLI always uses strong consistency.\n\n## Regions\n\n`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`.\n\n- **Deploy-specific stores** default to your functions region (auto-injected).\n- **Site-wide stores** default to `us-east-2` and do NOT follow your functions region.\n\n**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.\n\n**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.\n\n```ts\nconst uploads = getDeployStore({ name: \"file-uploads\", region: \"ap-southeast-2\" });\nconst profiles = getStore({ name: \"user-profiles\", region: \"eu-central-1\" });\n```\n\n## File-based uploads (no build plugin)\n\nPlace 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).\n\n```\n.netlify/blobs/deploy/\n├─ dogs/good-boy.jpg\n├─ dogs/$good-boy.jpg.json   # metadata for good-boy.jpg\n├─ cat.jpg\n└─ mouse.jpg\n```\n\n**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).\n\n## Access control (default to private)\n\nBlobs 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.\n\n## Constraints\n\n- Store names: no `/` or `:`, max 64 bytes.\n- Keys: non-empty, cannot start with `/`, max 600 bytes, any Unicode (some chars >1 byte).\n- Object size max 5 GB; metadata max 2 KB.\n- Functions written in **Go cannot access Netlify Blobs**.\n- Fetch API required (Node.js 18+); otherwise pass a custom `fetch`: `getStore({ fetch, name: \"file-uploads\" })`.\n- Local dev (Netlify Dev) uses a sandboxed local store: no file-based uploads, cannot read production data.\n- File-based uploads require continuous deployment or CLI deploys.\n\n## When an operation fails\n\nSurface the error and read the function logs. Do not invent REST endpoints or side-channel APIs to retry.\n\n## CLI and UI\n\n`netlify blobs:list/get/set/delete` exist for inspection — see the [CLI command reference](https://cli.netlify.com/commands/blobs/). Browse and download in the UI under **Data & Storage > Blobs**.\n\n## Module version migration\n\nIf 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+:\n\n```sh\nnetlify recipes blobs-migrate YOUR_STORE_NAME\n```\n\n## Reference\n\nFull API and background: [Netlify Blobs docs](https://docs.netlify.com/build/data-and-storage/netlify-blobs/) and the [data & storage overview](https://docs.netlify.com/build/data-and-storage/overview/).\n\n<!-- system: agent-context/blobs/system.md — human-owned, merged by ctx-gen; edit system.md, not this section -->\n# Netlify house rules (blobs)\n\nThese are org conventions, not docs facts — merged into the rendered skill by\nctx-gen and never generated. Owned by the skills maintainer.\n\n1. Blobs is not a database. For dynamic, per-user, or transactional data,\n   use Netlify DB — Blobs is for objects, files, and cache-like state.\n2. When a store operation fails, surface the error and read the function\n   logs — do not invent REST endpoints or side-channel APIs to retry.\n3. `netlify blobs:list/get/set/delete` exist for inspection; the CLI\n   reference is their source of truth — link, don't restate.\n4. Blobs have no built-in access control — the serving function is the gate.\n   When in doubt, default to private: gate reads behind an authenticated\n   function rather than exposing blobs publicly.\n5. Site-scoped stores are shared across ALL deploy contexts — code on a\n   deploy preview reads, overwrites, and deletes production data. Never run\n   destructive tests or seed throwaway data from previews; use\n   `getDeployStore()` or a context-specific store name for isolation.\n6. Don't build counters, balances, or read-modify-write logic on a blob key —\n   even with `onlyIfMatch` retries. That's transactional data; use Netlify DB.\n7. Build plugins: state BOTH halves — they can read from any of the site's\n   stores, but write only to deploy-specific stores (`getDeployStore`).\n"}],"summary":"Fields changed: 3. /description, /included_files, /skill_md_contents.","summary_kind":"deterministic","summary_metadata":{}}