Getting Started
Getting Started
Installation
workkit is distributed as individual packages. Install only what you need:
# Core packages (most projects start here)bun add @workkit/types @workkit/errors @workkit/env
# Cloudflare binding wrappersbun add @workkit/d1 @workkit/kv @workkit/queue @workkit/cachebun add @workkit/do @workkit/r2 @workkit/cryptobun add @workkit/auth @workkit/ratelimit @workkit/cronbun add @workkit/logger @workkit/health @workkit/turnstile @workkit/features
# AI & agentsbun add @workkit/ai @workkit/ai-gateway @workkit/agent @workkit/memory @workkit/mcp
# Orchestration & human-in-the-loopbun add @workkit/workflow @workkit/approval
# Communicationbun add @workkit/mail @workkit/notify @workkit/chat
# Browser & PDFbun add @workkit/browser @workkit/pdf
# Testing utilitiesbun add -d @workkit/testingEach package has @workkit/errors and @workkit/types as peer dependencies. Install those first.
Or use the interactive CLI to add packages to an existing project:
bunx workkit add # interactive multi-selectbunx workkit add kv d1 # add specific packages directlyYour First Worker
Here is a complete Cloudflare Worker using workkit for env validation, D1 queries, and structured error handling:
import { parseEnvSync } from '@workkit/env'import { d1 } from '@workkit/d1'import { errorToResponse, NotFoundError } from '@workkit/errors'import { z } from 'zod'
// 1. Define your env schema with any Standard Schema validatorconst envSchema = { DB: z.custom<D1Database>((v) => v != null), API_SECRET: z.string().min(1),}
// 2. Define your typesinterface User { id: number name: string email: string created_at: string}
export default { async fetch(request: Request, rawEnv: Record<string, unknown>): Promise<Response> { // 3. Validate env (fails fast with all issues listed) const env = parseEnvSync(rawEnv, envSchema)
// 4. Create typed D1 client const db = d1(env.DB, { transformColumns: 'camelCase' })
try { const url = new URL(request.url)
if (url.pathname === '/users') { const users = await db.all<User>('SELECT * FROM users ORDER BY id') return Response.json(users) }
const match = url.pathname.match(/^\/users\/(\d+)$/) if (match) { const user = await db.first<User>( 'SELECT * FROM users WHERE id = ?', [match[1]], ) if (!user) throw new NotFoundError('User', match[1]) return Response.json(user) }
return new Response('Not Found', { status: 404 }) } catch (error) { // 5. Structured errors auto-map to HTTP responses if (error instanceof WorkkitError) { return errorToResponse(error) } return new Response('Internal Server Error', { status: 500 }) } },}Key Concepts
Standard Schema
workkit’s env validation is built on Standard Schema — a shared interface implemented by Zod, Valibot, ArkType, and others. You are never locked into a specific validation library. Any schema that implements the ~standard protocol works:
import { parseEnvSync } from '@workkit/env'import { z } from 'zod' // orimport * as v from 'valibot' // orimport { type } from 'arktype' // any Standard Schema works
const schema = { API_KEY: z.string().min(1), // Zod PORT: v.pipe(v.string(), v.transform(Number)), // Valibot}Composable Wrappers
Every binding package (@workkit/d1, @workkit/kv, @workkit/queue, etc.) follows the same pattern: a factory function that wraps a raw Cloudflare binding and returns a typed client:
const db = d1(env.DB) // D1Database -> TypedD1const store = kv(env.CACHE) // KVNamespace -> WorkkitKV<T>const events = queue(env.QUEUE) // Queue -> TypedQueueProducer<T>The raw binding is always accessible via .raw for escape hatches.
Structured Errors
All workkit errors extend WorkkitError and carry:
- A stable
code(e.g.,WORKKIT_NOT_FOUND) for programmatic handling - An HTTP
statusCodefor automatic response mapping - A
retryableflag andretryStrategyso callers never guess - Optional structured
contextfor logging
import { isRetryable, getRetryDelay, errorToResponse } from '@workkit/errors'
try { await db.first('SELECT ...')} catch (error) { if (isRetryable(error)) { const delay = getRetryDelay(error.retryStrategy, attempt) // retry after delay } return errorToResponse(error) // auto HTTP status + JSON body}Tree-Shakeable
Each package exports only what you import. There are no god objects or monolithic bundles. A worker using only @workkit/kv does not pull in D1, Queue, or AI code.
Type Inference
Types flow through the entire stack. InferEnv<T> derives your env type from a schema map. TypedD1 infers row types from generics. WorkkitKV<T> ensures get/put type safety. You write types once at the boundary and they propagate everywhere.