With Netlify Blobs, you can store and retrieve blobs and unstructured data. You can also use this feature as a simple key/value store.
Netlify Blobs is a highly-available, eventually-consistent data store optimized for frequent reads and infrequent writes.
Data is stored in a single region for consistency 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.
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.
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 interact with Netlify Blobs from the following Netlify features:
metadata (optional): a JSON object with arbitrary metadata to attach to the object
import{ getStore }from"@netlify/blobs";importtype{ Context }from"@netlify/functions";exportdefaultasync(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},});returnnewResponse("Nail blobs set for Construction and Beauty stores");};
import{ getStore }from"@netlify/blobs";importtype{ Context }from"@netlify/edge-functions";exportdefaultasync(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},});returnnewResponse("Nail blobs set for Construction and Beauty stores");};
import{ getDeployStore }from"@netlify/blobs";exportconstonPostBuild=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");};
metadata (optional): a JSON object with arbitrary metadata to attach to the object
import{ getStore }from"@netlify/blobs";importtype{ Context }from"@netlify/functions";exportdefaultasync(req: Request, context: Context)=>{const construction =getStore("construction");await construction.setJSON("nails",{ type:"common", finish:"bright"});returnnewResponse("Nail blob set in JSON for Construction store");};
import{ getStore }from"@netlify/blobs";importtype{ Context }from"@netlify/edge-functions";exportdefaultasync(req: Request, context: Context)=>{const construction =getStore("construction");await construction.setJSON("nails",{ type:"common", finish:"bright"});returnnewResponse("Nail blob set in JSON for Construction store");};
import{ getDeployStore }from"@netlify/blobs";exportconstonPostBuild=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");};
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:
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:
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";importtype{ Context }from"@netlify/functions";exportdefaultasync(req: Request, context: Context)=>{// Mock implementation of a system for locally persisting blobs and their etagsconst 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 freshreturnnewResponse("Still fresh");}// `data` contains the new blob, store it locally alongside the new ETagwriteInMockCache("nails", data, etag);returnnewResponse("Updated");};
import{ getStore }from"@netlify/blobs";importtype{ Context }from"@netlify/edge-functions";exportdefaultasync(req: Request, context: Context)=>{// Mock implementation of a system for locally persisting blobs and their etagsconst 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 freshreturnnewResponse("Still fresh");}// `data` contains the new blob, store it locally alongside the new ETagwriteInMockCache("nails", data, etag);returnnewResponse("Updated");};
You can use object metadata to create client-side expiration logic. To delete blobs you consider expired, do the following:
import{ getStore }from"@netlify/blobs";importtype{ Context }from"@netlify/functions";exportdefaultasync(req: Request, context: Context)=>{const beauty =getStore("beauty");// Set the entry with an `expiration` metadata propertyawait beauty.set("nails","For formal events",{
metadata:{
expiration:newDate("2024-01-01").getTime()}});// Read the entry and compare the `expiration` metadata// property against the current timestampconst entry =await beauty.getWithMetadata("nails");if(entry ===null){returnnewResponse("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 itif(expiration && expiration < Date.now()){returnnewResponse(entry.data);}// Delete the expired entryawait beauty.delete("nails");returnnewResponse("Blob has expired");};
import{ getStore }from"@netlify/blobs";importtype{ Context }from"@netlify/edge-functions";exportdefaultasync(req: Request, context: Context)=>{const beauty =getStore("beauty");// Set the entry with an `expiration` metadata propertyawait beauty.set("nails","For formal events",{
metadata:{
expiration:newDate("2024-01-01").getTime()}});// Read the entry and compare the `expiration` metadata// property against the current timestampconst entry =await beauty.getWithMetadata("nails");if(entry ===null){returnnewResponse("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 itif(expiration && expiration < Date.now()){returnnewResponse(entry.data);}// Delete the expired entryawait beauty.delete("nails");returnnewResponse("Blob has expired");};
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, { etag?, type? })
It takes the following parameters:
key: a string representing the object key
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:
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
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:
Group keys hierarchically with the / character in your key names.
List entries hierarchically with the directories parameter.
Drill down into a specific directory with the prefix parameter.
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.
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.
The namespaces you make with getStore are shared across all deploys of your site. This is 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 must use deploy-specific stores. This requirement makes it so that a build plugin running as part of a deploy that fails cannot overwrite production data.
To make a deploy-specific namespace, use the getDeployStore method.
import{ getDeployStore }from"@netlify/blobs";importtype{ Context }from"@netlify/functions";exportdefaultasync(req: Request, context: Context)=>{const store =getDeployStore();await store.set("nails","For general carpentry");returnnewResponse("Nail blob set for this deploy");};
import{ getDeployStore }from"@netlify/blobs";importtype{ Context }from"@netlify/edge-functions";exportdefaultasync(req: Request, context: Context)=>{const store =getDeployStore();await store.set("nails","For general carpentry");returnnewResponse("Nail blob set for this deploy");};
import{ getDeployStore }from"@netlify/blobs";exportconstonPostBuild=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.
Keep the following requirement 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.
Keep the following rules in mind when creating namespaces and blobs:
Store names cannot include the / character.
Store names cannot start with deploy: as it is a reserved keyword.
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.
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. You cannot read production data during local development.
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.