Skip to main content

Overview

The kv.store() function provides access to a persistent key-value store unique to each guild. Store bot configuration, user data, and other persistent state.

Signature

function store(name: string): KvStore

Parameters

name
string
required
Name of the store namespace (e.g., ‘settings’, ‘userdata’)

Return Value

Returns a KvStore instance with methods for reading and writing data.

KvStore Methods

get()

Retrieve a value from the store.
async get(key: string): Promise<string | null>
Parameters:
  • key - The key to retrieve
Returns: The stored string value, or null if not found Example:
const db = kv.store('settings')
const prefix = await db.get('prefix')
console.log(prefix) // '!' or null

getWithMetadata()

Retrieve a value along with its metadata.
async getWithMetadata(key: string): Promise<{
  value: string | null
  metadata?: Record<string, unknown>
}>
Example:
const result = await db.getWithMetadata('premium_status')
console.log(result.value)              // 'active'
console.log(result.metadata?.expiresAt) // '2024-12-31'

set()

Store a value in the database.
async set(
  key: string,
  value: string,
  options?: {
    expiration?: bigint
    metadata?: JsonValue
  }
): Promise<void>
Parameters:
  • key - The key to set
  • value - The value to store (max 1MB)
  • options.expiration - Optional Unix timestamp (seconds) when the key expires
  • options.metadata - Optional JSON metadata to attach
Example:
// Simple set
await db.set('prefix', '!')

// With expiration (expires in 1 hour)
const oneHourFromNow = BigInt(Math.floor(Date.now() / 1000) + 3600)
await db.set('temp_token', 'abc123', { expiration: oneHourFromNow })

// With metadata
await db.set('user_points', '100', {
  metadata: { lastUpdated: new Date().toISOString() }
})

updateMetadata()

Update only the metadata for a key without changing its value.
async updateMetadata(key: string, metadata: JsonValue | undefined): Promise<void>
Example:
await db.updateMetadata('user_points', {
  lastModified: new Date().toISOString(),
  modifiedBy: 'admin'
})

delete()

Remove a key from the store.
async delete(key: string): Promise<void>
Example:
await db.delete('old_setting')

list()

List all keys in the store with pagination support.
async list(options?: {
  prefix?: string
  limit?: bigint
  cursor?: string
}): Promise<{
  keys: Array<{
    name: string
    expiration?: bigint
    metadata?: JsonValue
  }>
  listComplete: boolean
  cursor?: string
}>
Parameters:
  • options.prefix - Only return keys starting with this prefix
  • options.limit - Maximum keys to return (default: 100, max: 1000)
  • options.cursor - Pagination cursor from previous list() call
Returns:
  • keys - Array of key information objects
  • listComplete - Whether all matching keys were returned
  • cursor - Cursor for fetching the next page (if listComplete is false)
Example:
// List all keys
const result = await db.list()
for (const key of result.keys) {
  console.log(key.name)
}

// List with prefix filter
const userKeys = await db.list({ prefix: 'user:' })

// Paginated listing
let cursor: string | undefined
do {
  const page = await db.list({ limit: BigInt(100), cursor })
  for (const key of page.keys) {
    console.log(key.name)
  }
  cursor = page.cursor
} while (cursor)

Usage Examples

Simple Configuration Storage

const config = kv.store('config')

// Save settings
await config.set('welcome_channel', '123456789')
await config.set('log_channel', '987654321')

// Load settings
const welcomeChannel = await config.get('welcome_channel')

User Points System

const points = kv.store('points')

slash({
  name: 'points',
  description: 'Check your points',
  run: async (ctx) => {
    const userId = ctx.msg.user.id
    const userPoints = await points.get(`user:${userId}`) ?? '0'
    await ctx.reply(`You have ${userPoints} points`)
  }
})

slash({
  name: 'addpoints',
  description: 'Award points to a user',
  options: [
    { name: 'user', type: 'string', required: true },
    { name: 'amount', type: 'integer', required: true }
  ],
  run: async (ctx) => {
    const userId = ctx.options.user as string
    const amount = ctx.options.amount as number
    
    const current = parseInt(await points.get(`user:${userId}`) ?? '0')
    const newTotal = current + amount
    
    await points.set(`user:${userId}`, String(newTotal), {
      metadata: {
        lastUpdated: new Date().toISOString(),
        awardedBy: ctx.msg.user.id
      }
    })
    
    await ctx.reply(`Awarded ${amount} points! New total: ${newTotal}`)
  }
})

Temporary Data with Expiration

const temp = kv.store('temporary')

// Store verification code that expires in 5 minutes
const fiveMinutes = BigInt(Math.floor(Date.now() / 1000) + 300)
await temp.set('verify:' + userId, verificationCode, {
  expiration: fiveMinutes
})

// Check verification (will be null after expiration)
const code = await temp.get('verify:' + userId)
if (code === expectedCode) {
  await ctx.reply('Verified!')
}

List and Cleanup

const cache = kv.store('cache')

// Delete all keys with a certain prefix
const result = await cache.list({ prefix: 'old:' })
for (const key of result.keys) {
  await cache.delete(key.name)
}
console.log(`Deleted ${result.keys.length} old entries`)

Limits

  • Value size: Maximum 1MB per value
  • Keys per list: Default 100, maximum 1000 per request
  • Key naming: Any string (recommended: use prefixes like user:123, config:prefix)

Storage Scope

  • Each store name creates a separate namespace
  • All data is scoped per guild automatically
  • Different guilds cannot access each other’s data
  • Store names are case-sensitive

Notes

  • Values are stored as strings - parse JSON or numbers as needed
  • Expiration uses Unix timestamps in seconds (not milliseconds)
  • Use BigInt for expiration and limit values
  • Expired keys are automatically deleted and return null
  • Metadata is optional and can store any JSON-serializable data
  • List operations support cursor-based pagination for large datasets

Build docs developers (and LLMs) love