Netlify Blobs

With Netlify Blobs, you can store and retrieve blobs and unstructured data. You can also use this feature as a simple key/value store or basic database.

Netlify Blobs is a highly-available data store optimized for frequent reads and infrequent writes.

For maximum flexibility, it offers a configurable consistency model. If multiple write calls to the same key are issued, the last write wins.

We automatically handle provisioning, configuration, and access control for you. This integrated zero-configuration solution helps you focus on building business value in your project rather than toil on setting up and scaling a separate blob storage solution.

# Overview

Each blob belongs to a single site. A site can have multiple namespaces for blobs. We call these stores. This allows you to, for example, have the key nails exist as an object in a store for beauty and separately as an object in a store for construction with different data. Every blob must be associated with a store, even if a site is not using multiple namespaces.

You can perform CRUD operations for Netlify Blobs from the following Netlify features:

You can also:

# Use cases

Netlify Blobs is a platform primitive that developers and frameworks can use as a building block for many different purposes. Here are a few examples of powerful patterns that you can use:

  • Data store for functions. With Background Functions, you can trigger asynchronous serverless workflows for long-running operations like generating a site map, processing media assets, or sending emails in bulk. You can then use Netlify Blobs to persist the output of those computations.
  • Programmable cache for HTTP requests. If your application interacts with a third-party API that is slow, unreliable, or expensive, you may benefit from caching its responses with Netlify Blobs. This may let you call the third-party API less often.
  • Processing user uploads. If your application takes user submissions, like reviews on a product page or image files for a gallery, Netlify Blobs can store that data. When paired with Functions or Edge Functions, you can create an endpoint to receive an upload, validate the contents, and persist the validated data.

For more advanced use cases — such as those that require complex queries, concurrency control, or a relational data model — explore our integrations with the best-in-class database vendors.

# Netlify Blobs API

To use the Netlify Blobs API, first install the @netlify/blobs module using the package manager of your choice:

npm install @netlify/blobs

Then use the below methods in your functions, edge functions, or build plugins.

# set

Creates an object with the given key and value. If an entry with the given key already exists, its value is overwritten.

set(key, value, { metadata? })

It takes the following parameters:

  • key: a string representing the object key
  • value: the value as an ArrayBuffer, Blob, or string
  • metadata (optional): a JSON object with arbitrary metadata to attach to the object
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  const construction = getStore("construction");
  await construction.set("nails", "For general carpentry");

  const beauty = getStore("beauty");
  await beauty.set("nails", "For formal events", {
    metadata: { material: "acrylic", sale: true },
  });

  return new Response("Nail blobs set for Construction and Beauty stores");
};
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/edge-functions";

export default async (req: Request, context: Context) => {
  const construction = getStore("construction");
  await construction.set("nails", "For general carpentry");

  const beauty = getStore("beauty");
  await beauty.set("nails", "For formal events", {
    metadata: { material: "acrylic", sale: true },
  });

  return new Response("Nail blobs set for Construction and Beauty stores");
};
import { getDeployStore } from "@netlify/blobs";

export const onPostBuild = async ({ constants }) => {
  const store = getDeployStore({
    siteID: constants.SITE_ID,
    token: constants.NETLIFY_API_TOKEN,
  });
  await beauty.set("nails", "For formal events", {
    metadata: { material: "acrylic", sale: true },
  });

  console.log("Nail blob set for this deploy");
};

# setJSON

Convenience method for creating a JSON-serialized object. If an entry with the given key already exists, its value is overwritten.

setJSON(key, value, { metadata? })

It takes the following parameters:

  • key: a string representing the object key
  • value: any value that is serializable to JSON
  • metadata (optional): a JSON object with arbitrary metadata to attach to the object
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  const construction = getStore("construction");

  await construction.setJSON("nails", { type: "common", finish: "bright" });

  return new Response("Nail blob set in JSON for Construction store");
};
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/edge-functions";

export default async (req: Request, context: Context) => {
  const construction = getStore("construction");

  await construction.setJSON("nails", { type: "common", finish: "bright" });

  return new Response("Nail blob set in JSON for Construction store");
};
import { getDeployStore } from "@netlify/blobs";

export const onPostBuild = async ({ constants }) => {
  const store = getDeployStore({
    siteID: constants.SITE_ID,
    token: constants.NETLIFY_API_TOKEN,
  });

  await store.setJSON("nails", { type: "common", finish: "bright" });

  console.log("Nail blob set in JSON for this deploy");
};

# get

Retrieves an object with the given key.

get(key, { consistency?, type? })

It takes the following parameters:

  • key: a string representing the object key
  • consistency (optional): a string representing the consistency model for the operation
  • type (optional): the format in which the object should be returned — the default format is a string but you can specify one of the following values instead:
    • arrayBuffer: returns the entry as an ArrayBuffer
    • blob: returns the entry as a Blob
    • json: parses the entry as JSON and returns the resulting object
    • stream: returns the entry as a ReadableStream
    • text: default, returns the entry as a string of plain text

It returns the blob in the format specified by type. If an object with the given key is not found, null is returned.

import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  const construction = getStore("construction");
  const entry1 = await construction.get("nails");

  const beauty = getStore("beauty");
  const entry2 = await beauty.get("nails");

  const text = [entry1, entry2].join(" ");

  return new Response(text);
};
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/edge-functions";

export default async (req: Request, context: Context) => {
  const construction = getStore("construction");
  const entry1 = await construction.get("nails");

  const beauty = getStore("beauty");
  const entry2 = await beauty.get("nails");

  const text = [entry1, entry2].join(" ");

  return new Response(text);
};
import { getDeployStore } from "@netlify/blobs";

export const onPostBuild = async ({ constants }) => {
  const store = getDeployStore({
    siteID: constants.SITE_ID,
    token: constants.NETLIFY_API_TOKEN,
  });
  const entry = await store.get("nails");

  console.log(entry);
};

# getWithMetadata

Retrieves an object along with its metadata.

getWithMetadata(key, { consistency?, etag?, type? })

It takes the following parameters:

  • key: a string representing the object key
  • consistency (optional): a string representing the consistency model for the operation
  • etag (optional): an opaque quoted string, possibly prefixed by a weakness indicator, representing the ETag value of any version of this blob you may have cached — this allows you to do conditional requests
  • type (optional): the format in which the object should be returned — the default format is a string but you can specify one of the following values instead:
    • arrayBuffer: returns the entry as an ArrayBuffer
    • blob: returns the entry as a Blob
    • json: parses the entry as JSON and returns the resulting object
    • stream: returns the entry as a ReadableStream
    • text: default, returns the entry as a string of plain text

It returns an object with the following properties:

  • data: the blob contents in the format specified by the type parameter, or null if the etag property is the same as the etag parameter (meaning the cached object is still fresh)
  • etag: an opaque quoted string, possibly prefixed by a weakness indicator, representing the ETag value of the object
  • metadata: object with arbitrary metadata

If an object with the given key is not found, null is returned.

import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  const beauty = getStore("beauty");
  const { data, etag, metadata } = await beauty.getWithMetadata("nails");

  return Response.json({ data, etag, metadata });
};
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/edge-functions";

export default async (req: Request, context: Context) => {
  const beauty = getStore("beauty");
  const { data, etag, metadata } = await beauty.getWithMetadata("nails");

  return Response.json({ data, etag, metadata });
};
import { getDeployStore } from "@netlify/blobs";

export const onPostBuild = async ({ constants }) => {
  const store = getDeployStore({
    siteID: constants.SITE_ID,
    token: constants.NETLIFY_API_TOKEN,
  });
  const entry = await store.getWithMetadata("nails");

  console.log(entry.data, entry.etag, entry.metadata);
};
Conditional request examples
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  // Mock implementation of a system for locally persisting blobs and their etags
  const cachedETag = getFromMockCache("nails");

  const beauty = getStore("beauty");

  // Get entry from the blob store only if its ETag is different from the one you
  // have locally, which means the entry has changed since you last retrieved it.
  // Compare the whole value including surrounding quotes and any weakness prefix.
  const { data, etag } = await beauty.getWithMetadata("nails", {
    etag: cachedETag,
  });

  if (etag === cachedETag) {
    // `data` is `null` because the local blob is fresh
    return new Response("Still fresh");
  }

  // `data` contains the new blob, store it locally alongside the new ETag
  writeInMockCache("nails", data, etag);

  return new Response("Updated");
};
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/edge-functions";

export default async (req: Request, context: Context) => {
  // Mock implementation of a system for locally persisting blobs and their etags
  const cachedETag = getFromMockCache("nails");

  const beauty = getStore("beauty");

  // Get entry from the blob store only if its ETag is different from the one you
  // have locally, which means the entry has changed since you last retrieved it.
  // Compare the whole value including surrounding quotes and any weakness prefix.
  const { data, etag } = await beauty.getWithMetadata("nails", {
    etag: cachedETag,
  });

  if (etag === cachedETag) {
    // `data` is `null` because the local blob is fresh
    return new Response("Still fresh");
  }

  // `data` contains the new blob, store it locally alongside the new ETag
  writeInMockCache("nails", data, etag);
	
  return new Response("Updated");
};

You can use object metadata to create client-side expiration logic. To delete blobs you consider expired, do the following:

  1. set your objects with metadata that you can base the expiration logic on, such as a timestamp
  2. getWithMetadata to check if an object is expired
  3. delete the expired object
Expiration logic examples
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  const beauty = getStore("beauty");

  // Set the entry with an `expiration` metadata property
  await beauty.set("nails", "For formal events", {
    metadata: {
      expiration: new Date("2024-01-01").getTime()
    }
  });

  // Read the entry and compare the `expiration` metadata
  // property against the current timestamp
  const entry = await beauty.getWithMetadata("nails");

  if (entry === null) {
    return new Response("Blob does not exist");
  }

  const { expiration } = entry.metadata;

  // If the expiration date is in the future, it means
  // the blob is still fresh, so return it
  if (expiration && expiration < Date.now()) {
    return new Response(entry.data);
  }

  // Delete the expired entry
  await beauty.delete("nails");

  return new Response("Blob has expired");
};
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/edge-functions";

export default async (req: Request, context: Context) => {
  const beauty = getStore("beauty");

  // Set the entry with an `expiration` metadata property
  await beauty.set("nails", "For formal events", {
    metadata: {
      expiration: new Date("2024-01-01").getTime()
    }
  });

  // Read the entry and compare the `expiration` metadata
  // property against the current timestamp
  const entry = await beauty.getWithMetadata("nails");

  if (entry === null) {
    return new Response("Blob does not exist");
  }

  const { expiration } = entry.metadata;

  // If the expiration date is in the future, it means
  // the blob is still fresh, so return it
  if (expiration && expiration < Date.now()) {
    return new Response(entry.data);
  }

  // Delete the expired entry
  await beauty.delete("nails");

  return new Response("Blob has expired");
};

# getMetadata

Retrieves the metadata for an object, if the object exists.

This method is useful to check if a blob exists without actually retrieving it and having to download a potentially large blob over the network.

getMetadata(key, { consistency?, etag?, type? })

It takes the following parameters:

  • key: a string representing the object key
  • consistency (optional): a string representing the consistency model for the operation
  • etag (optional): an opaque quoted string, possibly prefixed by a weakness indicator, representing the ETag value of any version of this blob you may have cached — this allows you to do conditional requests
  • type (optional): the format in which the object should be returned — the default format is a string but you can specify one of the following values instead:
    • arrayBuffer: returns the entry as an ArrayBuffer
    • blob: returns the entry as a Blob
    • json: parses the entry as JSON and returns the resulting object
    • stream: returns the entry as a ReadableStream
    • text: default, returns the entry as a string of plain text

It returns an object with the following properties:

  • metadata: object with arbitrary metadata
  • etag: an opaque quoted string, possibly prefixed by a weakness indicator, representing the ETag value of the object

If an object with the given key is not found, null is returned.

import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  const beauty = getStore("beauty");
  const entry = await beauty.getMetadata("nails");

  if (entry === null) {
    return new Response("Blob does not exist");
  }

  return Response.json({
    etag: entry.etag,
    metadata: entry.metadata
  });
};
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/edge-functions";

export default async (req: Request, context: Context) => {
  const beauty = getStore("beauty");
  const entry = await beauty.getMetadata("nails");

  if (entry === null) {
    return new Response("Blob does not exist");
  }

  return Response.json({
    etag: entry.etag,
    metadata: entry.metadata
  });
};
import { getDeployStore } from "@netlify/blobs";

export const onPostBuild = async ({ constants }) => {
  const store = getDeployStore({
    siteID: constants.SITE_ID,
    token: constants.NETLIFY_API_TOKEN,
  });
  const entry = await store.getMetadata("nails");

  console.log(entry.etag, entry.metadata);
};

# list

Returns a list of blobs in a given store.

list({ directories?, paginate?, prefix? })

It takes the following parameters:

  • directories (optional): a boolean that indicates whether keys with the / character should be treated as directories, returning a list of sub-directories at a given level rather than all the keys inside them
  • paginate (optional): a boolean that specifies whether you want to handle pagination manually — by default, it is handled automatically
  • prefix (optional): a string for filtering down the entries; when specified, only the entries whose key starts with that prefix are returned

It returns an object with the following properties:

  • blobs: an array of blobs that match the query parameters, shown as objects with etag and key properties, which represent an object’s ETag value and key, respectively
  • directories: an array of strings representing any directories matching the query parameters
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  const beauty = getStore("beauty");
  const { blobs } = await beauty.list();
  
  // [ { etag: "\"etag1\"", key: "nails" }, { etag: "W/\"etag2\"", key: "hair" } ]
  console.log(blobs);

  return new Response(`Found ${blobs.length} blobs`);
};
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/edge-functions";

export default async (req: Request, context: Context) => {
  const beauty = getStore("beauty");
  const { blobs } = await beauty.list();
  
  // [ { etag: "\"etag1\"", key: "nails" }, { etag: "W/\"etag2\"", key: "hair" } ]
  console.log(blobs);

  return new Response(`Found ${blobs.length} blobs`);
};
import { getDeployStore } from "@netlify/blobs";

export const onPostBuild = async ({ constants }) => {
  const store = getDeployStore({
    siteID: constants.SITE_ID,
    token: constants.NETLIFY_API_TOKEN,
  });
  const { blobs } = await store.list();

  // [ { etag: "\"etag1\"", key: "nails" }, { etag: "W/\"etag2\"", key: "hair" } ]
  console.log(blobs);
};

Optionally, you can group blobs together under a common prefix and then browse them hierarchically when listing a store. This is similar to grouping files in a directory. To browse hierarchically, do the following:

  1. Group keys hierarchically with the / character in your key names.
  2. List entries hierarchically with the directories parameter.
  3. Drill down into a specific directory with the prefix parameter.
Hierarchical browsing examples

Take the following set of keys as an example:

cats/shorthair.jpg
cats/longhair.jpg
dogs/beagle.jpg
dogs/corgi.jpg
bird.jpg

By default, list will return all five keys.

import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  const animals = getStore("animals");
  const { blobs } = await animals.list();

  // [
  //   { etag: "\"etag1\"", key: "cats/shorthair.jpg" },
  //   { etag: "\"etag2\"", key: "cats/longhair.jpg" },
  //   { etag: "\"etag3\"", key: "dogs/beagle.jpg" },
  //   { etag: "\"etag4\"", key: "dogs/corgi.jpg" },
  //   { etag: "\"etag5\"", key: "bird.jpg" },
  // ]
  console.log(blobs);

  return new Response(`Found ${blobs.length} blobs`);
};
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/edge-functions";

export default async (req: Request, context: Context) => {
  const animals = getStore("animals");
  const { blobs } = await animals.list();

  // [
  //   { etag: "\"etag1\"", key: "cats/shorthair.jpg" },
  //   { etag: "\"etag2\"", key: "cats/longhair.jpg" },
  //   { etag: "\"etag3\"", key: "dogs/beagle.jpg" },
  //   { etag: "\"etag4\"", key: "dogs/corgi.jpg" },
  //   { etag: "\"etag5\"", key: "bird.jpg" },
  // ]
  console.log(blobs);

  return new Response(`Found ${blobs.length} blobs`);
};
import { getDeployStore } from "@netlify/blobs";

export const onPostBuild = async ({ constants }) => {
  const store = getDeployStore({
    siteID: constants.SITE_ID,
    token: constants.NETLIFY_API_TOKEN,
  });
  const { blobs } = await store.list();

  // [
  //   { etag: "\"etag1\"", key: "cats/shorthair.jpg" },
  //   { etag: "\"etag2\"", key: "cats/longhair.jpg" },
  //   { etag: "\"etag3\"", key: "dogs/beagle.jpg" },
  //   { etag: "\"etag4\"", key: "dogs/corgi.jpg" },
  //   { etag: "\"etag5\"", key: "bird.jpg" },
  // ]
  console.log(blobs);
};

To list entries hierarchically, set the directories parameter to true.

import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  const animals = getStore("animals");
  const { blobs, directories } = await animals.list({ directories: true });

  // [ { etag: "\"etag5\"", key: "bird.jpg" } ]
  console.log(blobs);

  // [ "cats", "dogs" ]
  console.log(directories);

  return new Response(`Found ${blobs.length} blobs and ${directories.length} directories`);
};
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/edge-functions";

export default async (req: Request, context: Context) => {
  const animals = getStore("animals");
  const { blobs, directories } = await animals.list({ directories: true });

  // [ { etag: "\"etag5\"", key: "bird.jpg" } ]
  console.log(blobs);

  // [ "cats", "dogs" ]
  console.log(directories);

  return new Response(`Found ${blobs.length} blobs and ${directories.length} directories`);
};
import { getDeployStore } from "@netlify/blobs";

export const onPostBuild = async ({ constants }) => {
  const store = getDeployStore({
    siteID: constants.SITE_ID,
    token: constants.NETLIFY_API_TOKEN,
  });
  const { blobs, directories } = await store.list({ directories: true });

  // [ { etag: "\"etag5\"", key: "bird.jpg" } ]
  console.log(blobs);

  // [ "cats", "dogs" ]
  console.log(directories);
};

To drill down into a directory and get a list of its items, set the prefix parameter to the directory name.

import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  const animals = getStore("animals");
  const { blobs, directories } = await animals.list({
    directories: true,
    prefix: "cats/",
  });

  // [ { etag: "\"etag1\"", key: "cats/shorthair.jpg" }, { etag: "\"etag2\"", key: "cats/longhair.jpg" } ]
  console.log(blobs);

  // [ ]
  console.log(directories);

  return new Response(`Found ${blobs.length} blobs and ${directories.length} directories`);
};
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/edge-functions";

export default async (req: Request, context: Context) => {
  const animals = getStore("animals");
  const { blobs, directories } = await animals.list({
    directories: true,
    prefix: "cats/",
  });

  // [ { etag: "\"etag1\"", key: "cats/shorthair.jpg" }, { etag: "\"etag2\"", key: "cats/longhair.jpg" } ]
  console.log(blobs);

  // [ ]
  console.log(directories);

  return new Response(`Found ${blobs.length} blobs and ${directories.length} directories`);
};
import { getDeployStore } from "@netlify/blobs";

export const onPostBuild = async ({ constants }) => {
  const store = getDeployStore({
    siteID: constants.SITE_ID,
    token: constants.NETLIFY_API_TOKEN,
  });
  const { blobs, directories } = await store.list({
    directories: true,
    prefix: "cats/",
  });

  // [ { etag: "\"etag1\"", key: "cats/shorthair.jpg" }, { etag: "\"etag2\"", key: "cats/longhair.jpg" } ]
  console.log(blobs);

  // [ ]
  console.log(directories);
};

Note that the prefix includes a trailing slash. This ensures that only entries under the cats directory are returned. Without a trailing slash, other keys like catsuit would also be returned.

For performance reasons, the server groups results into pages of up to 1,000 entries. By default, the list method automatically retrieves all pages, meaning you’ll always get the full list of results.

To handle pagination manually, set the paginate parameter to true. This makes list return an AsyncIterator, which lets you take full control over the pagination process. This means you can fetch only the data you need when you need it.

Manual blob pagination examples
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  const store = getStore("animals");

  let blobCount = 0;

  for await (const entry of store.list({ paginate: true })) {
    blobCount += entry.blobs.length;

    console.log(entry.blobs);
  }

  return new Response(`Found ${blobCount} blobs`);
};
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/edge-functions";

export default async (req: Request, context: Context) => {
  const store = getStore("animals");

  let blobCount = 0;

  for await (const entry of store.list({ paginate: true })) {
    blobCount += entry.blobs.length;

    console.log(entry.blobs);
  }

  return new Response(`Found ${blobCount} blobs`);
};
import { getDeployStore } from "@netlify/blobs";

export const onPostBuild = async ({ constants }) => {
  const store = getDeployStore({
    siteID: constants.SITE_ID,
    token: constants.NETLIFY_API_TOKEN,
  });

  let blobCount = 0;

  for await (const entry of store.list({ paginate: true })) {
    blobCount += entry.blobs.length;

    console.log(entry.blobs);
  }

  console.log(`Found ${blobCount} blobs`);
};

# listStores

Returns a list of stores for a site. Does not include deploy-specific stores.

listStores({ paginate? })

It takes the following parameter:

  • paginate (optional): a boolean that specifies whether you want to handle pagination manually — by default, it is handled automatically

It returns an object with the following array:

  • stores: an array of strings representing any stores matching the query parameters
import { listStores } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  const { stores } = await listStores();
  
  // [ "beauty", "construction" ]
  console.log(stores);

  return new Response(`Found ${stores.length} stores`);
};
import { listStores } from "@netlify/blobs";
import type { Context } from "@netlify/edge-functions";

export default async (req: Request, context: Context) => {
  const { stores } = await listStores();
  
  // [ "beauty", "construction" ]
  console.log(stores);

  return new Response(`Found ${stores.length} stores`);
};
import { listStores } from "@netlify/blobs";

export const onPostBuild = async ({ constants }) => {
  const { stores } = await listStores({
    siteID: constants.SITE_ID,
    token: constants.NETLIFY_API_TOKEN,
  });

  // [ "beauty", "construction" ]
  console.log(stores);
};

For performance reasons, the server groups results into pages of up to 1,000 stores. By default, the listStores method automatically retrieves all pages, meaning you’ll always get the full list of results.

To handle pagination manually, set the paginate parameter to true. This makes listStores return an AsyncIterator, which lets you take full control over the pagination process. This means you can fetch only the data you need when you need it.

Manual store pagination examples
import { listStores } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  let storeCount = 0;

  for await (const entry of listStores({ paginate: true })) {
    storeCount += entry.stores.length;

    console.log(entry.stores);
  }

  return new Response(`Found ${storeCount} stores`);
};
import { listStores } from "@netlify/blobs";
import type { Context } from "@netlify/edge-functions";

export default async (req: Request, context: Context) => {
  let storeCount = 0;

  for await (const entry of listStores({ paginate: true })) {
    storeCount += entry.stores.length;

    console.log(entry.stores);
  }

  return new Response(`Found ${storeCount} stores`);
};
import { listStores } from "@netlify/blobs";

export const onPostBuild = async ({ constants }) => {
    const { stores } = await listStores({
    siteID: constants.SITE_ID,
    token: constants.NETLIFY_API_TOKEN,
    paginate: true,
  });

  let storeCount = 0;

  for await (const entry of stores) {
    storeCount += entry.stores.length;

    console.log(entry.stores);
  }

  console.log(`Found ${storeCount} stores`);
};

# delete

Deletes an object with the given key, if one exists.

delete(key)

It takes the following parameters:

  • key: a string representing the object key

The return value is always undefined, regardless of whether an object was actually deleted.

import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  const construction = getStore("construction");
  await construction.delete("nails");

  const beauty = getStore("beauty");
  await beauty.delete("nails");

  return new Response("Nail blobs deleted from Construction and Beauty stores");
};
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/edge-functions";

export default async (req: Request, context: Context) => {
  const construction = getStore("construction");
  await construction.delete("nails");

  const beauty = getStore("beauty");
  await beauty.delete("nails");

  return new Response("Nail blobs deleted from Construction and Beauty stores");
};
import { getDeployStore } from "@netlify/blobs";

export const onPostBuild = async ({ constants }) => {
  const store = getDeployStore({
    siteID: constants.SITE_ID,
    token: constants.NETLIFY_API_TOKEN,
  });
  await store.delete("nails");

  console.log("Nail blob deleted for this deploy");
};

# File-based uploads

With file-based uploads, you can write blobs to deploy-specific stores after the build completes and before the deploy starts. This can be useful for authors of frameworks and other tools integrating with Netlify as it does not require a build plugin.

To make file-based uploads, place blob files in .netlify/blobs/deploy in your site’s base directory. Netlify uploads these files to blob storage maintaining their directory structure. Here is an example file tree:

.netlify/
├─ blobs/
|  ├─ deploy/
│  |  ├─ beauty/
│  │  |  └─ nails.jpg
│  |  ├─ construction/
│  │  |  └─ nails.jpg
│  |  └─ dog.jpg

This uploads the following blobs:

  • beauty/nails.jpg
  • construction/nails.jpg
  • dog.jpg

To attach metadata to a blob, include a JSON file that prefixes the corresponding blob filename with $ and has a .json extension. For example:

.netlify/
├─ blobs/
|  ├─ deploy/
│  |  ├─ beauty/
│  │  |  ├─ nails.jpg
│  │  |  └─ $nails.jpg.json
│  |  ├─ construction/
│  │  |  └─ nails.jpg
│  |  ├─ dog.jpg
│  |  ├─ mouse.jpg
│  |  └─ $mouse.jpg.json

This uploads the following blobs:

  • beauty/nails.jpg with the metadata from beauty/$nails.jpg.json
  • construction/nails.jpg without metadata
  • dog.jpg without metadata
  • mouse.jpg with the metadata from $mouse.jpg.json

Metadata files must contain valid JSON or the deploy will fail. Here’s an example of valid JSON metadata:

{
  "type": "common",
  "finish": "bright"
}

# Consistency

By default, the Netlify Blobs API uses an eventual consistency model, where data is stored in a single region and cached at the edge for fast access across the globe. When a blob is added, it becomes globally available immediately. Updates and deletions are guaranteed to be propagated to all edge locations within 60 seconds.

You can configure this behavior and opt-in to strong consistency with the Netlify Blobs API, either for an entire store or for individual read operations. Netlify CLI always uses strong consistency.

Choosing the right consistency model depends on your use case and each option comes with tradeoffs:

  • if it’s important for your application that updates and deletions become immediately available to all readers, you should consider using strong consistency, which comes with the cost of slower reads
  • if that is not a hard requirement and you’re optimizing for fast reads, you should consider using eventual consistency
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  const store = getStore({ name: "animals", consistency: "strong" });

  await store.set("dog", "🐶");

  // This is a strongly-consistent read.
  const dog = await store.get("dog");

  return new Response(dog);
};
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  const store = getStore("animals");

  await store.set("dog", "🐶");

  // This is an eventually-consistent read.
  const dog1 = await store.get("dog");

  // This is a strongly-consistent read.
  const dog2 = await store.get("dog", { consistency: "strong" });

  return new Response(dog1 + dog2);
};

# Netlify Blobs UI

In addition to using the Netlify Blobs API to list and get blobs, you can use the Netlify UI to browse and download blobs.

To explore and retrieve your site’s blobs:

  1. In the Netlify UI, go to the

    page for your site.

  2. If your site has more than one store, select the store of interest.

  3. Then, drill into directories to explore the blobs in the store or Download an individual blob to examine it.

# Sensitive data

You can store sensitive data with Netlify Blobs. To keep your data secure, we encrypt your blobs at rest and in transit.

Your blobs can only be accessed through your own site. You are responsible for making sure the code you use to access your blobs doesn’t allow data to leak. We recommend that you consider the following best practices:

  • Do not allow incoming requests for arbitrary keys if you have sensitive data. Treat user input as unsafe and scope your keys with something that callers cannot tamper with.
  • Review the code of any build plugin you install from the npm public registry to make sure it doesn’t have malicious blob interactions.

Visit our security checklist for general security measures we recommend you consider for your site.

# Deploy-specific stores

The namespaces you make with getStore are shared across all deploys of your site. This is required when using Netlify CLI and desirable for most use cases with functions and edge functions because it means that a new production deploy can read previously written data without you having to replicate blobs for each new production deploy. This also means you can test your Deploy Previews with production data. This does, however, mean that you should be careful to avoid scenarios such as a branch deploy deleting blobs that your published deploy depends on.

As mentioned above, build plugins and file-based uploads must write to deploy-specific stores. This requirement makes it so that a deploy that fails cannot overwrite production data.

To make a deploy-specific namespace with the Netlify Blobs API, use the getDeployStore method.

import { getDeployStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  const store = getDeployStore();
  await store.set("nails", "For general carpentry");

  return new Response("Nail blob set for this deploy");
};
import { getDeployStore } from "@netlify/blobs";
import type { Context } from "@netlify/edge-functions";

export default async (req: Request, context: Context) => {
  const store = getDeployStore();
  await store.set("nails", "For general carpentry");

  return new Response("Nail blob set for this deploy");
};
import { getDeployStore } from "@netlify/blobs";

export const onPostBuild = async ({ constants }) => {
  const store = getDeployStore({
    siteID: constants.SITE_ID,
    token: constants.NETLIFY_API_TOKEN,
  });
  await store.set("nails", "For general carpentry");

  console.log("Nail blob set for this deploy");
};

In general, blobs in deploy-specific stores are managed by Netlify like other atomic deploy assets. This means they’re kept in sync with their relative deploys if you do a rollback and that they’re cleaned up with automatic deploy deletion.

However, downloading a deploy does not download deploy-specific blobs, and locking a published deploy does not prevent you from writing to associated deploy-specific stores.

# Requirements and limitations

Keep the following requirements in mind while working with Netlify Blobs:

  • Netlify Blobs uses the web platform fetch() to make HTTP calls, so Fetch API support is required. This is included with Node.js 18. If for some reason you can’t use Node.js 18, you can provide your own Fetch API support by supplying a fetch property to the getStore or getDeployStore method.
  • File-based uploads require continuous deployment or CLI deploys.
Custom fetch examples
import { fetch } from "whatwg-fetch";
import { getStore } from "@netlify/blobs";
import type { Context } from "@netlify/functions";

export default async (req: Request, context: Context) => {
  const construction = getStore({ fetch, name: "construction" });
  const entry = await construction.get("nails");

  return new Response(entry);
};
import { fetch } from "whatwg-fetch";
import { getDeployStore } from "@netlify/blobs";

export const onPostBuild = async ({ constants }) => {
  const store = getDeployStore({
    fetch,
    siteID: constants.SITE_ID,
    token: constants.NETLIFY_API_TOKEN,
  });
  const entry = await store.get("nails");

  console.log(entry);
};

Keep the following rules in mind when creating namespaces and blobs:

  • Store names cannot include the / character.
  • Store names cannot include the : character.
  • Store names cannot exceed 64 bytes.
  • Empty keys are not supported.
  • Object keys can include any Unicode characters.
  • Object keys cannot start with the / character.
  • Object keys cannot exceed 600 bytes.
  • An individual object’s total size cannot exceed 5 GB.
  • An individual object’s metadata size cannot exceed 2 KB.

Most characters use 1 byte

Most Unicode characters with UTF-8 encoding take 1 byte. So, for convenience, you can think of the above size limits as roughly a 64-character limit for store names and a 600-character limit for object keys. But, be aware that some characters take more than one byte. For example, à takes 2 bytes.

Keep the following limitations in mind when working with Netlify Blobs:

  • Functions written in Go cannot access Netlify Blobs.
  • Cross-site blob access is not allowed. To share data between sites, you can use a rewrite on site A to a path on site B that pulls data from site B’s blobs.
  • Local development with Netlify Dev uses a sandboxed local store that does not support file-based uploads. You cannot read production data during local development.
  • Deploy deletion deletes deploy-specific stores only. For other stores, you can create custom expiration logic or delete objects manually as needed.

# Troubleshooting tips

  • Last write wins. If two overlapping calls try to write the same object, the last write wins. Netlify Blobs does not include a concurrency control mechanism. To manage the potential for race conditions, you can build an object-locking mechanism into your application.

  • Store access depends on @netlify/blobs module version. If you wrote to site-wide stores with @netlify/blobs version 6.5.0 or earlier, and you then upgrade the module to a more recent version, you will no longer be able to access data in those stores. This is due to an internal change to namespacing logic. You can migrate affected stores by running the following command in the project directory using the latest version of the Netlify CLI.

    netlify recipes blobs-migrate YOUR_STORE_NAME
    

    This makes the migrated store accessible with @netlify/blobs module version 7.0.0 and later.