
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
    <channel>
        <title><![CDATA[ The Cloudflare Blog ]]></title>
        <description><![CDATA[ Get the latest news on how products at Cloudflare are built, technologies used, and join the teams helping to build a better Internet. ]]></description>
        <link>https://blog.cloudflare.com</link>
        <atom:link href="https://blog.cloudflare.com/" rel="self" type="application/rss+xml"/>
        <language>en-us</language>
        <image>
            <url>https://blog.cloudflare.com/favicon.png</url>
            <title>The Cloudflare Blog</title>
            <link>https://blog.cloudflare.com</link>
        </image>
        <lastBuildDate>Sun, 12 Apr 2026 08:51:42 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Catching up with Workers KV]]></title>
            <link>https://blog.cloudflare.com/catching-up-with-workers-kv/</link>
            <pubDate>Mon, 29 Jun 2020 11:00:00 GMT</pubDate>
            <description><![CDATA[ Today, we’d like to share with you some of the stuff that has recently shipped in Workers KV: a new feature and an internal change that should significantly improve latency in some cases. Let’s dig in! ]]></description>
            <content:encoded><![CDATA[ <p></p><p>The Workers Distributed Data team has been hard at work since <a href="/whats-new-with-workers-kv/">we gave you an update last November</a>. Today, we’d like to share with you some of the stuff that has recently shipped in Workers KV: a new feature and an internal change that should significantly improve latency in some cases. Let’s dig in!</p>
    <div>
      <h3>KV Metadata</h3>
      <a href="#kv-metadata">
        
      </a>
    </div>
    <p>Workers KV has a fairly straightforward interface: you can put keys and values into KV, and then fetch the value back out by key:</p>
            <pre><code>await contents.put(“index.html”, someHtmlContent);
await contents.put(“index.css”, someCssContent);
await contents.put(“index.js”, someJsContent);

// later

let index = await contents.get(“index.html”);</code></pre>
            <p>Pretty straightforward. But as you can see from this example, you may store different kinds of content in KV, even if the type is identical. All of the values are strings, but one is HTML, one is CSS, and one is JavaScript. If we were going to serve this content to users, we would have to construct a response. And when we do, we have to let the client know what the content type of that request is: text/html for HTML, text/css for CSS, and text/javascript for JavaScript. If we serve the incorrect content type to our clients, they won’t display the pages correctly.</p><p>One possible solution to this problem is using the <a href="https://www.npmjs.com/package/mime">mime package from npm</a>. This lets us write code that looks like this:</p>
            <pre><code>// pathKey is a variable with a value like “index.html”
const mimeType = mime.getType(pathKey) || ‘text/plain’</code></pre>
            <p>Nice and easy. But there are some drawbacks. First of all, because we have to detect the content type at runtime which means we’re figuring this out on every request. It would be nicer to figure it out only once instead. Second, if we look at how the package implements getType, it does this by <a href="https://github.com/broofa/mime/blob/d97bfaeabf8b5ff0124692244f921836ea405c41/types/standard.js">including an array of possible extensions and their types</a>. This means that this array is included in our worker, taking up 9kb of space. That’s also less than ideal.</p><p>But now, we have a better way. Workers KV will now allow you to add some extra JSON to each key/value pair, to use however you’d like. So we could start inserting the contents of those files like this, instead:</p>
            <pre><code>await contents.put(“index.html”, someHtmlContent, {“Content-Type”: “text/html”});
await contents.put(“index.css”, someCssContent, {“Content-Type”: “text/css”});
await contents.put(“index.js”, someJsContent, {“Content-Type”: “text/javascript”});</code></pre>
            <p>You could determine these content types in various ways: by looking at the file extension like the mime package, or by using a library that inspects the file’s contents to figure out its type like libmagic. Regardless, the type would be stored in KV alongside the contents of the file. This way, there’s no need to recompute the type on every request. Additionally, the detection code would live in your uploading tool, not in your worker, creating a smaller bundle. Win-win!</p><p>The worker code would pass along this metadata by using a new method:</p>
            <pre><code>let {value, metadata} = await contents.getWithMetadata(“index.js”);</code></pre>
            <p>Here, <code>value</code> would have the contents, like before. But <code>metadata</code> contains the JSON of the metadata that was stored: <code>metadata[“Content-Type”]</code>would return <code>“text/javascript”</code>. You’ll also see this metadata come back when you make a list request as well.</p><p>Given that you can store arbitrary JSON, it’s useful for more than just content types: we’ve had folks <a href="https://community.cloudflare.com/t/etag-and-content-type-support-in-kv-storage/150331?u=sklabnik">post to the forums asking about etags</a>, for example. We’re excited to see what you do with this new capability!</p>
    <div>
      <h3>Significantly faster writes</h3>
      <a href="#significantly-faster-writes">
        
      </a>
    </div>
    <p>Our documentation states:</p><p><i>Very infrequently read values are stored centrally, while more popular values are maintained in all of our data centers around the world.</i></p><p>This is why Workers KV is optimized for higher read volumes than writes. We distribute popular data across the globe, close to users wherever they are. However, for infrequently accessed data, we store the data in a central location until access is requested. Each write (and delete) must go back to the central data store, as do reads on less popular values. The central store was located in the United States, and so the speed for writes would be variable. In the US, it would be much faster than say, in Europe or Asia.</p><p>Recently, we have rolled out a major internal change. We have added a second source of truth on the European continent. These two sources of truth will still coordinate between themselves, ensuring that any data you write or update will be available in both places as soon as possible. But latencies from Europe, as well as places closer to Europe than the United States, should be much faster, as they do not have to go the full way to the US.</p><p>How much faster? Well, it will depend on your workload. Several other Cloudflare products use Workers KV, and here’s a graph of response times from one of them:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4rWSdJkZyGgRnuRQepJzmE/bcb9ef29ac2bfb276f4592c6638dc143/image2-10.png" />
            
            </figure><p>As you can see, there’s a sharp drop in the graph when the switchover happened.</p><p>We can also measure this time across all customers:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6bZoPcQ3CbtsahcNKvTExa/d5751591954fe86fd1416b9853f04bff/image1-11.png" />
            
            </figure><p>The long tail has been significantly shortened. (We’ve redacted the exact numbers, but you can still see the magnitude of the changes.)</p>
    <div>
      <h3>More to come</h3>
      <a href="#more-to-come">
        
      </a>
    </div>
    <p>The distributed data team has been working on some additional things, but we’re not quite ready to share them with you yet! We hope that you’ll find these changes make Workers KV even better for you, and we’ll be sharing more updates on the blog as we ship.</p> ]]></content:encoded>
            <category><![CDATA[Cloudflare Workers KV]]></category>
            <guid isPermaLink="false">7ee4ME9b9t45i48XMjKejB</guid>
            <dc:creator>Steve Klabnik</dc:creator>
        </item>
        <item>
            <title><![CDATA[What’s new with Workers KV?]]></title>
            <link>https://blog.cloudflare.com/whats-new-with-workers-kv/</link>
            <pubDate>Wed, 06 Nov 2019 14:00:00 GMT</pubDate>
            <description><![CDATA[ The Storage team has shipped some new features for Workers KV that folks have been asking for. In this post, we'll talk about some of these new features and how to use them. ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2tL9Ez4EXvfYdtkiwRnZDv/d0f51bcde409896742bdf48cfa55d02c/workers-KV-dark-back_2x.png" />
            
            </figure><p>The Storage team here at Cloudflare shipped Workers KV, our global, low-latency, key-value store, <a href="/workers-kv-is-ga/">earlier this year</a>. As people have started using it, we’ve gotten some feature requests, and have shipped some new features in response! In this post, we’ll talk about some of these use cases and how these new features enable them.</p>
    <div>
      <h2>New KV APIs</h2>
      <a href="#new-kv-apis">
        
      </a>
    </div>
    <p>We’ve shipped some new APIs, both via <code>api.cloudflare.com</code>, as well as inside of a Worker. The first one provides the ability to upload and delete more than one key/value pair at once. Given that Workers KV is great for read-heavy, write-light workloads, a common pattern when getting started with KV is to write a bunch of data via the API, and then read that data from within a Worker. You can now do these bulk uploads without needing a separate API call for every key/value pair. This feature is available via <code>api.cloudflare.com</code>, but is not yet available from within a Worker.</p><p>For example, say we’re using KV to redirect legacy URLs to their new homes. We have a list of URLs to redirect, and where they should redirect to. We can turn this list into JSON that looks like this:</p>
            <pre><code>[
  {
    "key": "/old/post/1",
    "value": "/new-post-slug-1"
  },
  {
    "key": "/old/post/2",
    "value": "/new-post-slug-2"
  }
]</code></pre>
            <p>And then POST this JSON to the new bulk endpoint, <code>/storage/kv/namespaces/:namespace_id/bulk</code>. This will add both key/value pairs to our namespace.</p><p>Likewise, if we wanted to drop support for these redirects, we could issue a DELETE that has this body:</p>
            <pre><code>[
    "/old/post/1",
    "/old/post/2"
]</code></pre>
            <p>to <code>/storage/kv/namespaces/:namespace_id/bulk</code>, and we’d delete both key/value pairs in a single call to the API.</p><p>The bulk upload API has one more trick up its sleeve: not all data is a string. For example, you may have an image as a value, which is just a bag of bytes. if you need to write some binary data, you’ll have to base64 the value’s contents so that it’s valid JSON. You’ll also need to set one more key:</p>
            <pre><code>[
  {
    "key": "profile-picture",
    "value": "aGVsbG8gd29ybGQ=",
    "base64": true
  }
]</code></pre>
            <p>Workers KV will decode the value from base64, and then store the resulting bytes.</p><p>Beyond bulk upload and delete, we’ve also given you the ability to list all of the keys you’ve stored in any of your namespaces, from both the API and within a Worker. For example, if you wrote a blog powered by Workers + Workers KV, you might have each blog post stored as a key/value pair in a namespace called “contents”. Most blogs have some sort of “index” page that lists all of the posts that you can read. To create this page, we need to get a listing of all of the keys, since each key corresponds to a given post. We could do this from within a Worker by calling <code>list()</code> on our namespace binding:</p>
            <pre><code>const value = await contents.list()</code></pre>
            <p>But what we get back isn’t only a list of keys. The object looks like this:</p>
            <pre><code>{
  keys: [
    { name: "Title 1” },
    { name: "Title 2” }
  ],
  list_complete: false,
  cursor: "6Ck1la0VxJ0djhidm1MdX2FyD"
}</code></pre>
            <p>We’ll talk about this “cursor” stuff in a second, but if we wanted to get the list of titles, we’d have to iterate over the keys property, and pull out the names:</p>
            <pre><code>const keyNames = value.keys.map(e =&gt; e.name)</code></pre>
            <p><code>keyNames</code> would be an array of strings:</p>
            <pre><code>[“Title 1”, “Title 2”, “Title 3”, “Title 4”, “Title 5”]</code></pre>
            <p>We could take <code>keyNames</code> and those titles to build our page.</p><p>So what’s up with the <code>list_complete</code> and <code>cursor</code> properties? Well, imagine that we’ve been a <i>very</i> prolific blogger, and we’ve now written thousands of posts. The list API is paginated, meaning that it will only return the first thousand keys. To see if there are more pages available, you can check the <code>list_complete</code> property. If it is false, you can use the cursor to fetch another page of results. The value of <code>cursor</code> is an opaque token that you pass to another call to list:</p>
            <pre><code>const value = await NAMESPACE.list()
const cursor = value.cursor
const next_value = await NAMESPACE.list({"cursor": cursor})</code></pre>
            <p>This will give us another page of results, and we can repeat this process until <code>list_complete</code> is true.</p><p>Listing keys has one more trick up its sleeve: you can also return only keys that have a certain prefix. Imagine we want to have a list of posts, but only the posts that were made in October of 2019. While Workers KV is only a key/value store, we can use the prefix functionality to do interesting things by filtering the list. In our original implementation, we had stored the titles of keys only:</p><ul><li><p><code>Title 1</code></p></li><li><p><code>Title 2</code></p></li></ul><p>We could change this to include the date in YYYY-MM-DD format, with a colon separating the two:</p><ul><li><p><code>2019-09-01:Title 1</code></p></li><li><p><code>2019-10-15:Title 2</code></p></li></ul><p>We can now ask for a list of all posts made in 2019:</p>
            <pre><code>const value = await NAMESPACE.list({"prefix": "2019"})</code></pre>
            <p>Or a list of all posts made in October of 2019:</p>
            <pre><code>const value = await NAMESPACE.list({"prefix": "2019-10"})</code></pre>
            <p>These calls will only return keys with the given prefix, which in our case, corresponds to a date. This technique can let you group keys together in interesting ways. We’re looking forward to seeing what you all do with this new functionality!</p>
    <div>
      <h2>Relaxing limits</h2>
      <a href="#relaxing-limits">
        
      </a>
    </div>
    <p>For various reasons, there are a few hard limits with what you can do with Workers KV. We’ve decided to raise some of these limits, which expands what you can do.</p><p>The first is the limit of the number of namespaces any account could have. This was previously set at 20, but some of you have made a lot of namespaces! We’ve decided to relax this limit to 100 instead. This means you can create five times the number of namespaces you previously could.</p><p>Additionally, we had a two megabyte maximum size for values. We’ve increased the limit for values to ten megabytes. With the release of Workers Sites, folks are keeping things like images inside of Workers KV, and two megabytes felt a bit cramped. While Workers KV is not a great fit for truly large values, ten megabytes gives you the ability to store larger images easily. As an example, a 4k monitor has a native resolution of 4096 x 2160 pixels. If we had an image at this resolution as a lossless PNG, for example, it would be just over five megabytes in size.</p>
    <div>
      <h2>KV browser</h2>
      <a href="#kv-browser">
        
      </a>
    </div>
    <p>Finally, you may have noticed that there’s now a KV browser in the dashboard! Needing to type out a cURL command just to see what’s in your namespace was a real pain, and so we’ve given you the ability to check out the contents of your namespaces right on the web. When you look at a namespace, you’ll also see a table of keys and values:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7dGqVgzdqSPhXX8dEygRH7/7f38220fa78c724c91508283f934c8de/image-1.png" />
            
            </figure><p>The browser has grown with a bunch of useful features since it initially shipped. You can not only see your keys and values, but also add new ones:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5tonxPblY56QeSIygD7Wzl/13a9037caf6c4f29ba4dd2a670212f96/image-2.png" />
            
            </figure><p>edit existing ones:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5wRvxC9VHpAp1YE5O8GRdM/68a77b1b50788ff8cdcc7f9039b46055/image-3.png" />
            
            </figure><p>...and even upload files!</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/36H4hNRLwoEZLL0DoHbU90/b0db8cfe05f079e325cb2b1fb173c57e/image-4.png" />
            
            </figure><p>You can also download them:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/G1BgZQxeG8LoG9zuvANok/2c4306fb1ae2afae80e632588cb829ae/image-5.png" />
            
            </figure><p>As we ship new features in Workers KV, we’ll be expanding the browser to include them too.</p>
    <div>
      <h2>Wrangler integration</h2>
      <a href="#wrangler-integration">
        
      </a>
    </div>
    <p>The Workers Developer Experience team has also been shipping some features related to Workers KV. Specifically, you can fully interact with your namespaces and the key/value pairs inside of them.</p><p>For example, my personal website is running on Workers Sites. I have a Wrangler project named “website” to manage it. If I wanted to add another namespace, I could do this:</p>
            <pre><code>$ wrangler kv:namespace create new_namespace
Creating namespace with title "website-new_namespace"
Success: WorkersKvNamespace {
    id: "&lt;id&gt;",
    title: "website-new_namespace",
}

Add the following to your wrangler.toml:

kv-namespaces = [
    { binding = "new_namespace", id = "&lt;id&gt;" }
]</code></pre>
            <p>I’ve redacted the namespace IDs here, but Wrangler let me know that the creation was successful, and provided me with the configuration I need to put in my <code>wrangler.toml</code>. Once I’ve done that, I can add new key/value pairs:</p>
            <pre><code>$ wrangler kv:key put "hello" "world" --binding new_namespace
Success</code></pre>
            <p>And read it back out again:</p>
            <pre><code>&gt; wrangler kv:key get "hello" --binding new_namespace
world</code></pre>
            <p>If you’d like to learn more about the design of these features, <a href="/how-we-design-features-for-wrangler/">“How we design features for Wrangler, the Cloudflare Workers CLI”</a> discusses them in depth.</p>
    <div>
      <h2>More to come</h2>
      <a href="#more-to-come">
        
      </a>
    </div>
    <p>The Storage team is working hard at improving Workers KV, and we’ll keep shipping new stuff every so often. Our updates will be more regular in the future. If there’s something you’d particularly like to see, please reach out!</p> ]]></content:encoded>
            <category><![CDATA[Cloudflare Workers KV]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Serverless]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[API]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <guid isPermaLink="false">1p7MWSJNs1jvrCZgGC1xma</guid>
            <dc:creator>Steve Klabnik</dc:creator>
        </item>
    </channel>
</rss>