Back to blog

Follow and Subscribe

Beyond CRUD: Advanced Features of Fastly’s KV Store

Katsuyuki Omuro

Senior Software Engineer, Developer Experience Engineering, Fastly

If you're like most developers using Fastly's KV Store, you're probably using it for basic create, read, update, and delete operations—maybe as a configuration store or a simple cache. But what if I told you that you're only scratching the surface? KV Store has some really powerful features that can transform how you build applications at the edge. Let’s dive in.

What makes Fastly's KV Store special?

Before we explore the advanced features, let's quickly recap what Fastly's KV Store brings to the table:

KV Store is a key-value store that offers global, durable storage with the power of Fastly’s platform. Below is a list of primary features:

  • Globally distributed: Your data is available worldwide, practically instantly

  • Durable storage: Data persists for as long as your Fastly account is active

  • Edge-accessible: Lightning-fast access from edge locations

  • Eventually consistent: Accepts writes immediately and syncs across the nodes later

  • Account-level persistence: Data survives service deployments and rollbacks

But here's where it gets interesting—the KV Store packs some serious extras that you probably  don't even know exist. These include metadata, generation markers, insert-only writes, and TTL. Let’s explore each of these in detail.

Metadata: Your secret weapon for smart storage

Metadata is arguably the most underutilized feature of Fastly's KV Store. You can attach up to 2048 bytes of UTF-8 string data to any entry—no schema required. This could be JSON, timestamps, flags, or even assembly code (if that's your thing).

Why is this powerful? Because metadata is accessible without streaming the body. You can make decisions based on metadata alone, saving bandwidth and processing time.

Here is a metadata example via HTTP API:

// PUT example
curl -X PUT \
-H "Fastly-Key: $FASTLY_API_TOKEN" \
-H "Fastly-Metadata: {\"size\":548,\"hash\":
\"2e892d06d61ea1319f1e18dc109e64a9eb6b2c56f37a5
1de0cde4735ad94f021\"}" \
--data-binary @./index.html \
"https://api.fastly.com/resources/$STORE_ID/
items/index.html"

// GET example
curl -i \
-H "Fastly-Key: $FASTLY_API_TOKEN" \
"https://api.fastly.com/resources/$STORE_ID/
items/index.html"
HTTP/2 200
content-type: text/html
last-modified: Thu, 19 Jun 2025 13:16:09 GMT
metadata:
{"size":548,"hash":"2e892d06d61ea1319f1e18dc109
e64a9eb6b2c56f37a51de0cde4735ad94f021"}

In your Compute code, it's just as straightforward (JavaScript example):

import { KVStore } from "fastly:kv-store";
const store = new KVStore("my-store");
await store.put("index.html", body, {
ttl: 3600,
metadata: "{\"size\":548,\"hash\":
\"2e892d06d61ea1319f1e18dc109e64a9eb6b2c56f37a51de0cde4735ad94f021\"}",
});
const entry = await store.get("index.html");
console.log(entry.metadataText()); // -> “{\"size\":548,\"hash\":
\"2e892d06d61ea1319f1e18dc109e64a9eb6b2c56f37a51de0cde4735ad94f021\"}"
const text = await entry.text();

Generation Markers: Solving the concurrency puzzle

Eventually consistent systems have one Achilles' heel: concurrent updates. Two writes at different edge nodes could overwrite each other, leading to data inconsistencies.

This is where generation markers can provide lightweight concurrency protection.

Here's how it works:

  1. Read an entry and note its generation header

  2. Include that generation in your update request

  3. If someone else has modified the entry since your read, you'll get a 412 error

  4. Your application can then re-read, merge changes, and retry

This pattern is perfect for feature toggles, configuration updates, or any scenario where you need atomic updates without complex locking mechanisms. Here are some examples:

Generation Marker via HTTP API:

// GET example
curl -i \
-H "Fastly-Key: $FASTLY_API_TOKEN" \
“https://api.fastly.com/resources/
$STORE_ID/items/my-key"
HTTP/2 200
content-type: application/octet-stream
last-modified: Thu, 19 Jun 2025 13:16:09
GMT
generation: 1750338969138613

// PUT example
curl -X PUT \
-H "Fastly-Key: $FASTLY_API_TOKEN" \
-H "if-generation-match:
1750338969138613" \
--data "new-value" \
"https://api.fastly.com/resources/
$STORE_ID/items/my-key"

Generation Marker via Compute SDK (Rust):

use fastly::KVStore;
let store = KVStore::open("my-store")?.unwrap();
let entry = store.lookup("my-key")?;
let generation = entry.current_generation(); // -> 1750856377404286000
store.build_insert()
.if_generation_match(1750856377404286000)
.execute("my-key", "new-value");

Insert-only writes: The "write-once" guarantee

Some patterns demand that a key be written exactly once. Think A/B testing where you assign a user to a test group on their first visit—and that assignment must stick.

PUT with ?add=true

  • Only inserts if the key does not exist

  • Returns 412 Precondition Failed if key is already present

There are no race conditions, no locks, and no complexity. It’s just a simple flag that enforces single-write semantics.

TTL: Automatic cleanup for ephemeral data

TTL (Time-To-Live) is great for when you need to store temporary data without manual cleanup. 

 PUT with ?time_to_live_sec=<duration>

  • Entry is garbage collected after expiration

  • No need to explicitly delete

This is perfect for when you want to create:

  • Build previews that expire after a week

  • Temporary download links

  • Cache entries with built-in expiration

Important caveat: TTL is more like garbage collection than precise expiration. Don't use it for security-critical expirations like auth tokens—the cleanup happens "eventually" (within 24 hours of expiration).

Real-world application: The static publisher pattern

These features truly shine when combined. For example, Fastly's open-source Compute JS Static Publisher uses metadata to enable efficient static site serving with zero redeployments.

With clever metadata usage we can realize the following benefits:

  1. Assets uploaded with metadata: Content-type, SHA-256 hash, deployment version

  2. E-Tag support via metadata: Pre-computed hashes enable conditional GET responses

  3. Zero-body 304 responses: Metadata comparison happens without streaming content

E-Tag implementation example:

// Simplified E-Tag implementation
async function handleRequest(event) {
  const store = new KVStore('my-store');
  const kvEntry = await store.get(new URL(event.request.url).pathname);
  const metadata = JSON.parse(kvEntry.metadata);

  // Check if client has current version
  const clientETag = event.request.headers.get('if-none-match');
  if (clientETag != null && clientETag === metadata.hash) {
    // Client has latest - return 304 without body
    return new Response(null, { 
      status: 304,
      headers: { 
        'content-type': metadata.contentType,
        'etag': metadata.hash
      }
    });
  }

  // Client needs update - stream body directly from KV Store
  return new Response(kvEntry.body, {
    headers: {
      'content-type': metadata.contentType,
      'etag': metadata.hash
    }
  });
}

This pattern eliminates redundant data transfer while maintaining cache correctness—all without touching the response body for unchanged content.

Local development gets a boost

Viceroy is Fastly’s local development environment for Compute. Recent updates to Viceroy include support for generation IDs and the ability to define test metadata in the fastly.toml make local development even smoother:

  • Generation marker support: When your code reads from the KV Store in the local development server, it provides a generation marker so that you can test your concurrency logic locally.

  • Metadata in fastly.toml: When you initialize your local development server’s KV Store using fastly.toml, you can include metadata along with your entries.

[local_server.kv_stores]
example_store = { file = "../test/data/example_kv_store.json", format = "json" }

Example of kv_store.json:

{
  "first": "This is some inline data",
  "second": {
    "file": "../test/data/some-object.bin",
    "metadata": "This is some metadata"
  },
  "third": {
    "data": "This is more inline data",
    "metadata": "This is some more metadata"
  }
}

Patterns and Use Cases Beyond CRUD

Here's how these features map to real-world use cases:

Use Case

Features to Leverage

CMS-like content

Body + Metadata (author, date, tags)

Configuration management

Generation markers for safe updates

A/B testing

Insert-only for assignment persistence

Preview deployments

TTL for automatic cleanup

Static site serving

Metadata for E-Tags and content types

Limits to consider with your projects

You will want to keep these constraints in mind:

  • No metadata queries: You can't search for entries by metadata values

  • Metadata not in list operations: Listing keys doesn't return metadata

  • 25MB value limit: Large files need chunking strategies

  • Eventually consistent: Plan for temporary inconsistencies across regions

Start building beyond CRUD

Fastly's KV Store is far more than a simple key-value database. With metadata, generation markers, insert-only semantics, and TTL, you have the tools to build sophisticated edge applications with elegant solutions to common distributed system challenges.

What’s really great is that these features are available today, they're composable, and they work seamlessly with your existing Compute applications.

Check out the Compute Static Publisher for a production-ready example, or dive into the developer documentation to start experimenting with these powerful features.

What will you build when your KV Store does more than just CRUD? Stop by the forum to share and ask questions.