PlaycademyPlaycademy

KV Storage

Fast key-value storage for caching and session data

Overview

Add fast, global key-value storage to your project backend for caching and session data.

KV Storage is perfect for:

  • User state & preferences
  • Session storage & caching
  • Rate limiting & counters
  • Quick reads with low latency worldwide

Getting Started

$ playcademy init  # Select "Yes" for KV storage
$ playcademy kv init

Not a Database

KV is a simple key-value store, not a relational database.

For complex data relationships or write-heavy workloads, use the Database integration instead.


Managing KV Storage

CLI Commands

All KV commands work with both local and remote storage:

CommandDescriptionLocalRemote
kv listList all keys
kv get <key>Get a value
kv set <key> <value>Set a value
kv delete <key>Delete a key
kv inspect <key>Show key metadata
kv statsShow storage stats
kv seed <file>Load test data
kv clearClear all keys
kv dumpExport data as JSON/CSV

View Full Command Documentation

See KV CLI Reference for all options (--raw, --json, --force, etc.)

Local Development

Start your dev server to use KV locally:

Command
$ playcademy dev # or `vite dev` if using @playcademy/vite-plugin

Example CLI commands:

Command
$ playcademy kv set user:123:state '{"score": 100}'  # Set a value$ playcademy kv get user:123:state  # Get a value$ playcademy kv list  # List all keys$ playcademy kv stats  # Show statistics

Remote Operations

Add --remote to work with your deployed app's KV storage:

Command
$ playcademy kv list --remote  # Staging$ playcademy kv list --remote --env production  # Production$ playcademy kv set config:flags '{"beta": true}' --remote  # Set in staging$ playcademy kv seed seeds/kv.json --remote  # Seed staging with test data

Using KV Storage

Access KV storage via c.env.KV in your API routes.

User-Scoped Keys

KV keys are often scoped to an authenticated user. Access the platform user via c.get('playcademyUser'):

server/api/player-state.ts
export async function GET(c: Context): Promise<Response> {
    const playcademyUser = c.get('playcademyUser')

    if (!playcademyUser) {
        return c.json({ error: 'Not authenticated' }, 401)
    }

    const state = await c.env.KV.get(`user:${playcademyUser.sub}:state`)

    return c.json({
        success: true,
        data: state ? JSON.parse(state) : null,
    })
}
server/api/player-state.ts
export async function POST(c: Context): Promise<Response> {
    const playcademyUser = c.get('playcademyUser')

    if (!playcademyUser) {
        return c.json({ error: 'Not authenticated' }, 401)
    }

    const { score, level } = await c.req.json()

    const state = {
        score,
        level,
        lastPlayed: new Date().toISOString(),
    }

    await c.env.KV.put(`user:${playcademyUser.sub}:state`, JSON.stringify(state), {
        metadata: {
            email: playcademyUser.email,
            name: playcademyUser.name,
        },
    })

    return c.json({ success: true, data: state })
}
server/api/player-state.ts
export async function DELETE(c: Context): Promise<Response> {
    const playcademyUser = c.get('playcademyUser')

    if (!playcademyUser) {
        return c.json({ error: 'Not authenticated' }, 401)
    }

    await c.env.KV.delete(`user:${playcademyUser.sub}:state`)

    return c.json({ success: true })
}

Authentication Context Required

playcademyUser is populated when your app runs on the Playcademy platform and requests are made via the SDK.

For local development:

KV API surface:

MethodDescriptionExample
get(key)Retrieve a valueawait c.env.KV.get('user:123')
put(key, value)Store a valueawait c.env.KV.put('user:123', 'data')
delete(key)Remove a keyawait c.env.KV.delete('user:123')
list(options?)List keysawait c.env.KV.list({ prefix: 'user:' })

User Identity in KV Metadata

Attaching email and name as metadata on user-scoped writes (as shown in the "Write Data" tab above) enables playcademy kv list to display human-readable identity alongside each key:

user:07167f8e-...:savedata   Wyatt Victore <wyatt@school.edu>
user:0f6133c8-...:savedata   Vivian Ajouz <vivian@school.edu>

Use playcademy kv dump to export all data as JSON or CSV with identity included.

Advanced: Expiration & Metadata

You can also use expirationTtl for data that should expire:

server/api/route.ts
await c.env.KV.put('session:abc', data, {
    expirationTtl: 3600, // Expire in 1 hour
    metadata: { version: '1.0' },
})

Key Naming Patterns

Use prefixes to organize and manage your keys:

PatternUse CaseExample
user:{sub}:*User-scoped datauser:${playcademyUser.sub}:state
session:{sessionId}:*Session datasession:abc123
ratelimit:*Rate limitingratelimit:user:${playcademyUser.sub}:api
cache:*Cached datacache:leaderboard:daily
config:*Configurationconfig:feature-flags

Why Prefixes Matter

BenefitHow It Helps
OrganizationSimilar data types can be grouped together
FilteringYou can use KV.list({ prefix: 'user:' }) to find related keys
DebuggingQuickly identify usage patterns in playcademy kv stats
CleanupBulk clear data by prefix during testing

Deployment

The remote KV namespace is automatically created when you deploy for the first time:

Command
$ playcademy deploy

What's Next?

On this page