Guarantees & Limits
Because Layeron Storage compiles directly into native Cloudflare R2 and KV resources inside your Cloudflare account, it inherits both high performance and specific structural rules. Use these limits and consistency models when you choose between bucket storage, KV storage, signed URLs, and managed encryption.
Consistency Models
Section titled “Consistency Models”Buckets and KV namespaces have completely different consistency behaviors under the hood:
Bucket (R2): Strongly Consistent
Section titled “Bucket (R2): Strongly Consistent”Cloudflare R2 provides strong global read-after-write consistency.
- Once a
bucket.put()orbucket.delete()completes successfully, any subsequentbucket.get()orbucket.list()call from anywhere in the world is guaranteed to instantly see the updated object or deletion. bucket.put(key, value, { ifNotExists: true })uses an R2 conditional create. Existing objects fail withstorage_key_already_existsand HTTP status409.- Best For: Data files, documents, user assets, and any system where immediate state updates are critical.
KV: Eventually Consistent
Section titled “KV: Eventually Consistent”Cloudflare KV provides eventual global consistency.
- When you perform a
kv.put()orkv.delete(), the update is instantly visible in the local edge region where the write occurred. However, it can take up to 60 seconds for the update to replicate to all other Cloudflare edge caches worldwide. kv.put(key, value, { ifNotExists: true })checks the current KV-visible value before writing. A visible existing key fails withstorage_key_already_existsand HTTP status409; cross-region visibility follows Cloudflare KV replication.- KV is highly optimized for read-heavy workloads (delivering near-zero latency reads directly from the edge cache).
- Best For: User configurations, feature flags, routing indexes, or static session data. Do not use KV if you need immediate global writes (e.g. distributed locking or transaction ledger records).
Size & Count Limits
Section titled “Size & Count Limits”Understand payload boundaries before writing code to prevent out-of-memory or validation errors.
| Metric | Bucket (R2) | Key-Value (KV) | Description |
|---|---|---|---|
| Max Object Size | 5 TB | 25 MB | The maximum size of a single stored value payload. |
| Max Key Size | 1,024 bytes | 512 bytes | The maximum character length of a target storage key. |
| Value Formats | Bytes, Strings, Blobs | Bytes, Strings, Blobs | Permitted data formats for Storage payloads. |
| Max Metadata Size | 2 KB per object | — | Key-value dictionary size stored with Bucket objects. |
Key Expirations & Lifecycles
Section titled “Key Expirations & Lifecycles”Both variants support automatic data cleanup, but they use different mechanics:
Bucket (R2) Lifecycle Expiration
Section titled “Bucket (R2) Lifecycle Expiration”You can configure a global lifecycle rule on a Bucket to expire and delete old objects after a specific number of days.
- Setting:
lifecycle: { deleteAfterDays: 30 } - Layeron deploys this as a native Cloudflare R2 lifecycle rule. Cloudflare R2 performs the asynchronous cleanup, so your app Worker does not scan or delete old objects.
KV Time-To-Live (TTL) Expiration
Section titled “KV Time-To-Live (TTL) Expiration”You can configure a global or per-key TTL for KV storage. Expirations automatically delete values after a specified number of seconds.
- Minimum Expiration: Expirations must be at least 60 seconds (1 minute) in the future. Cloudflare KV ignores TTL delays shorter than 60 seconds.
- Usage:
- Global default: Configure
ttlSecondsin your KV constructor. - Per-key custom override: Pass
ttlSecondsin yourputoperation.Terminal window await sessionStore.put("session_123", sessionData, {ttlSeconds: 3600, // Expire this specific key in exactly 1 hour})
- Global default: Configure
Zero-Knowledge Managed Encryption
Section titled “Zero-Knowledge Managed Encryption”When you supply a Secret reference to your Storage configuration, Layeron enables managed encryption using the industry-standard AES-GCM-256 algorithm.
const files = storage.bucket({ name: "confidential-files", encryption: { secret: secret("vault-key"), // Secure Secret binding },})- Encryption Boundary: Payloads are encrypted on-the-fly inside the secure Storage Product Worker before they are written to Cloudflare R2 or KV. When reading, payloads are decrypted inside the product worker before being returned to your Gateway route.
- Zero-Knowledge Cloud: Cloudflare’s storage layer only sees encrypted binary data and randomized initialization vectors. The plaintext secret remains securely injected into your secure worker runtime and never gets written to disk.
- Signed URLs: Encrypted buckets can still use
.signedUrl(). Layeron signs the URL with the configured Secret and routes the request through the Storage Product Worker so reads can decrypt bytes and writes can encrypt bytes.
Secret Rotation for Managed Encryption
Section titled “Secret Rotation for Managed Encryption”Storage records the active Secret version beside every encrypted value. New writes use the current Secret version. Reads use the stored keyVersion, so older objects remain readable when the Secret value is rotated and previous versions are retained.
Use retain_forever for encryption keys that protect stored data:
const vaultKey = secret.random({ name: "vault-key", namespace: "storage", bytes: 32, rotation: { everyDays: 90, retain: { mode: "retain_forever", }, },})Managed-encryption signed URLs also include the signing Secret version. A URL generated before a rotation can still validate until it expires when the old Secret version remains retained.
Signed URLs
Section titled “Signed URLs”Use .signedUrl() on a bucket when a client needs a short-lived read or write link.
const uploadUrl = await storageSignedUrl(files, "avatars/user_123.png", { action: "write", expiresInSeconds: 300, contentType: "image/png", maxSizeBytes: 5 * 1024 * 1024,})Layeron chooses the signing path from your bucket configuration:
- A bucket without managed encryption uses a Layeron-managed Cloudflare R2 API token. Layeron creates the token during deploy, stores the credentials as a managed Secret resource, and injects the Secret into the Storage Product Worker.
- A bucket with managed encryption uses the configured encryption Secret. The signed request goes through the Storage Product Worker so Layeron can encrypt uploads and decrypt downloads.
- A signed URL expires at
expiresInSeconds. The default lifetime is 600 seconds. - A write signed URL accepts
PUTorPOST. A read signed URL acceptsGETorHEAD.
One-time signed URLs
Section titled “One-time signed URLs”Set oneTime: true when the URL should work once:
const downloadUrl = await storageSignedUrl(files, "exports/report.pdf", { action: "read", expiresInSeconds: 300, oneTime: true,})Layeron records the consumed token in the Storage state database. The first valid request can read or write the object. A later replay receives HTTP 410 with storage_token_already_used.
Dedicated Signed URL Host
Section titled “Dedicated Signed URL Host”Use host and path when signed URL traffic should use a dedicated hostname and path:
const files = storage.bucket({ name: "files", host: "files.example.com", path: "/__layeron/r2",})Generated signed URLs use that prefix, for example https://files.example.com/__layeron/r2/.... Layeron deploys a Worker route and custom domain for the Storage Product Worker. In local dev, signed URLs use the local dev server with the same path prefix.