NEW Browse AI tools across categories — updated daily. See what's new →

Write Endpoints

Comprehensive guide for building OpenAPI endpoints with chanfana - schema definition, request validation, CRUD operations, D1 database integration, and…

Version1.0.0
LicenseMIT
Token count~5,814
UpdatedJun 5, 2026

Install

Quick install

via npx skills · works with 57+ agents
npx skills add https://github.com/cloudflare/chanfana/tree/HEAD/skills/write-endpoints
Or pick agent:
npx skills add cloudflare/chanfana --skill write-endpoints --agent claude-code
npx skills add cloudflare/chanfana --skill write-endpoints --agent cursor
npx skills add cloudflare/chanfana --skill write-endpoints --agent codex
npx skills add cloudflare/chanfana --skill write-endpoints --agent opencode
npx skills add cloudflare/chanfana --skill write-endpoints --agent github-copilot
npx skills add cloudflare/chanfana --skill write-endpoints --agent windsurf
More install options

Shorthand — useful for multi-skill repos:

npx skills add cloudflare/chanfana --skill write-endpoints

Manual — clone the repo and drop the folder into your agent's skills directory:

git clone https://github.com/cloudflare/chanfana.git
cp -r chanfana/skills/write-endpoints ~/.claude/skills/
How to use: Once installed, ask your agent to "use the write-endpoints skill" or describe what you want (e.g. "Comprehensive guide for building OpenAPI endpoints with chanfana - schema defini"). Requires Node.js 18+.

write-endpoints

Comprehensive guide for building OpenAPI endpoints with chanfana - schema definition, request validation, CRUD operations, D1 database integration, and…

write-endpointsby cloudflare

Comprehensive guide for building OpenAPI endpoints with chanfana - schema definition, request validation, CRUD operations, D1 database integration, and…

npx skills add https://github.com/cloudflare/chanfana --skill write-endpointsDownload ZIPGitHub

Writing OpenAPI Endpoints with Chanfana

When to Use

Use this skill when:

  • Building OpenAPI endpoints with chanfana for Cloudflare Workers
  • Defining request/response schemas with Zod v4
  • Creating CRUD auto endpoints (Create, Read, Update, Delete, List)
  • Integrating with Cloudflare D1 databases
  • Implementing error handling with exception classes

Part 1: Fundamentals

Quick Start with Hono

`import { Hono, type Context } from 'hono';
import { fromHono, OpenAPIRoute, contentJson } from 'chanfana';
import { z } from 'zod';

export type Env = {
DB: D1Database;
};
export type AppContext = Context<{ Bindings: Env }>;

class HelloEndpoint extends OpenAPIRoute {
schema = {
responses: {
"200": {
description: 'Successful response',
...contentJson(z.object({ message: z.string() })),
},
},
};

async handle(c: AppContext) {
return { message: 'Hello, Chanfana!' };
}
}

const app = new Hono<{ Bindings: Env }>();
const openapi = fromHono(app);
openapi.get('/hello', HelloEndpoint);

export default app;
`

Quick Start with itty-router

`import { Router } from 'itty-router';
import { fromIttyRouter, OpenAPIRoute, contentJson } from 'chanfana';
import { z } from 'zod';

class HelloEndpoint extends OpenAPIRoute {
schema = {
responses: {
"200": {
description: 'Successful response',
...contentJson(z.object({ message: z.string() })),
},
},
};

async handle(request: Request, env, ctx) {
return { message: 'Hello, Chanfana!' };
}
}

const router = Router();
const openapi = fromIttyRouter(router);
openapi.get('/hello', HelloEndpoint);
router.all('*', () => new Response("Not Found.", { status: 404 }));

export const fetch = router.handle;
`

Schema Definition

Define request validation for body, query, params, and headers:

`import { OpenAPIRoute, contentJson } from 'chanfana';
import { z } from 'zod';

class CreateUserEndpoint extends OpenAPIRoute {
schema = {
request: {
body: contentJson(z.object({
username: z.string().min(3).max(20),
password: z.string().min(8),
email: z.email(),
fullName: z.string().optional(),
})),
query: z.object({
notify: z.boolean().optional().default(true),
}),
params: z.object({
orgId: z.uuid(),
}),
headers: z.object({
'X-API-Key': z.string(),
}),
},
responses: {
"200": {
description: 'User created successfully',
...contentJson(z.object({
id: z.uuid(),
username: z.string(),
email: z.email(),
})),
},
"400": {
description: 'Validation error',
...contentJson(z.object({
success: z.literal(false),
errors: z.array(z.object({
code: z.number(),
message: z.string(),
})),
})),
},
},
};

async handle(c) {
const data = await this.getValidatedData<typeof this.schema>();
// data.body, data.query, data.params, data.headers are all typed
return { id: crypto.randomUUID(), username: data.body.username, email: data.body.email };
}
}
`

Zod v4 Syntax (CRITICAL)

Chanfana v3 uses Zod v4. Use the correct syntax:

`// WRONG - Zod v3 syntax (deprecated)
z.string().email()
z.string().uuid()
z.string().datetime()
z.string().date()
z.string().url()
z.string().ip({ version: "v4" })
z.object({}).strict()
z.nativeEnum(MyEnum)

// CORRECT - Zod v4 syntax
z.email()
z.uuid()
z.iso.datetime()
z.iso.date()
z.url()
z.ipv4()
z.strictObject({})
z.enum(['option1', 'option2'])
`

Common Zod Types for APIs

Use native Zod schemas for all parameter types:

`import { z } from 'zod';

// String with constraints
const nameSchema = z.string()
.min(3)
.max(50)
.describe("User's name")
.openapi({ example: 'John Doe' });

// Number with range
const priceSchema = z.number()
.min(0)
.describe('Product price')
.openapi({ example: 99.99 });

// Integer
const ageSchema = z.number()
.int()
.min(0)
.max(120)
.describe("User's age");

// Boolean with default
const isActiveSchema = z.boolean()
.default(true)
.describe('User active status');

// Date/time (ISO 8601)
const createdAtSchema = z.iso.datetime()
.describe('Creation timestamp')
.openapi({ example: '2024-01-20T10:30:00Z' });

// Date only (YYYY-MM-DD)
const birthDateSchema = z.iso.date()
.describe('Birth date')
.openapi({ example: '1990-05-15' });

// Email, UUID
const emailSchema = z.email().describe('Email address');
const userIdSchema = z.uuid().describe('User ID');

// Enumeration
const statusSchema = z.enum(['pending', 'processing', 'shipped', 'delivered'])
.default('pending')
.describe('Order status');

// Array
const tagsSchema = z.array(z.string()).openapi({
description: 'Tags',
});

// Object
const addressSchema = z.object({
street: z.string().describe('Street address'),
city: z.string().describe('City'),
zipCode: z.string().describe('Zip code'),
});

// Regex pattern
const phoneSchema = z.string()
.regex(/^\+?[1-9]\d{1,14}$/, 'Invalid phone number format')
.describe('Phone number');

// IP addresses
const ipv4Schema = z.ipv4();
const ipv6Schema = z.ipv6();
const ipSchema = z.union([z.ipv4(), z.ipv6()]);

// Hostname (regex pattern)
const hostnameSchema = z.string().regex(
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/
);
`

Validated Data Access

Always use await with getValidatedData():

`class MyEndpoint extends OpenAPIRoute {
async handle(c) {
// CORRECT - with await and type annotation
const data = await this.getValidatedData<typeof this.schema>();

// Type-safe access
const username = data.body.username;
const page = data.query.page;
const userId = data.params.userId;
const apiKey = data.headers['X-API-Key'];

return { success: true };
}
}
`

Using getUnvalidatedData() for Partial Updates

In Zod v4, optional fields with .default() always have values in validated data. Use getUnvalidatedData() to detect what was actually sent:

`class UpdateUser extends OpenAPIRoute {
schema = {
request: {
body: contentJson(z.object({
name: z.string().optional(),
status: z.enum(['active', 'inactive']).default('active'),
})),
},
};

async handle() {
const validated = await this.getValidatedData<typeof this.schema>();
// validated.body.status is 'active' even if not sent

const raw = await this.getUnvalidatedData();
// raw.body = {} if nothing was sent

// Check what was actually sent
const updates: Record<string, any> = {};
if ('name' in raw.body) updates.name = validated.body.name;
if ('status' in raw.body) updates.status = validated.body.status;

return { updated: updates };
}
}
`

Part 2: CRUD Auto Endpoints

Meta Object Definition

All auto endpoints require a _meta property:

`import { z } from 'zod';

// Define the model schema
const UserSchema = z.object({
id: z.uuid(),
username: z.string().min(3).max(20),
email: z.email(),
role: z.enum(['user', 'admin']),
createdAt: z.iso.datetime(),
});

// Define the meta object
const userMeta = {
model: {
schema: UserSchema, // Required: Zod schema for the model
primaryKeys: ['id'], // Required: Array of primary key fields
tableName: 'users', // Required for D1 endpoints
serializer: (user: any) => { // Optional: Transform output
const { passwordHash, ...safe } = user;
return safe;
},
serializerSchema: UserSchema.omit({ passwordHash: true }), // Optional: Schema for serialized output
},
pathParameters: ['id'], // Optional: Explicit path params for nested routes
tags: ['Users'], // Optional: OpenAPI tags for grouping operations
};
`

CreateEndpoint

`import { CreateEndpoint, type O } from 'chanfana';

class CreateUser extends CreateEndpoint {
_meta = userMeta;

// Optional: Pre-processing hook
async before(data: O<typeof this._meta>): Promise<O<typeof this._meta>> {
return {
...data,
id: crypto.randomUUID(),
createdAt: new Date().toISOString(),
};
}

// Required: Create logic
async create(data: O<typeof this._meta>) {
await db.users.insert(data);
return data;
}

// Optional: Post-processing hook
async after(data: O<typeof this._meta>): Promise<O<typeof this._meta>> {
await sendWelcomeEmail(data.email);
return data;
}
}

// Register route
openapi.post('/users', CreateUser);
`

ReadEndpoint

`import { ReadEndpoint, type Filters, type O } from 'chanfana';

class GetUser extends ReadEndpoint {
_meta = userMeta;

async before(filters: Filters): Promise<Filters> {
// Pre-fetch validation
return filters;
}

async fetch(filters: Filters): Promise<O<typeof this._meta> | null> {
const userId = filters.filters[0].value;
return await db.users.findById(userId);
}

async after(data: O<typeof this._meta>): Promise<O<typeof this._meta>> {
// Post-fetch processing
return data;
}
}

// Register route with path parameter
openapi.get('/users/:id', GetUser);
`

ListEndpoint

`import { ListEndpoint, type ListFilters, type ListResult, type O } from 'chanfana';

class ListUsers extends ListEndpoint {
_meta = userMeta;

// Configure filtering, search, and sorting
filterFields = ['role', 'status']; // Exact match filtering
searchFields = ['username', 'email']; // Full-text search (LIKE)
orderByFields = ['createdAt', 'username']; // Available sort fields
defaultOrderBy = 'createdAt'; // Default sort field

async before(filters: ListFilters): Promise<ListFilters> {
// Add tenant filter, etc.
return filters;
}

async list(filters: ListFilters): Promise<ListResult<O<typeof this._meta>>> {
const users = await db.users.findMany(filters);
return { result: users };
}

async after(data: ListResult<O<typeof this._meta>>): Promise<ListResult<O<typeof this._meta>>> {
return data;
}
}

// Register route
openapi.get('/users', ListUsers);

// API calls:
// GET /users?page=2&per_page=10
// GET /users?role=admin
// GET /users?search=john
// GET /users?order_by=createdAt&order_by_direction=desc
`

UpdateEndpoint

`import { UpdateEndpoint, type UpdateFilters, type O } from 'chanfana';

class UpdateUser extends UpdateEndpoint {
_meta = userMeta;

async before(oldObj: O<typeof this._meta>, filters: UpdateFilters): Promise<UpdateFilters> {
filters.updatedData = {
...filters.updatedData,
updatedAt: new Date().toISOString(),
};
return filters;
}

async getObject(filters: UpdateFilters): Promise<O<typeof this._meta> | null> {
const userId = filters.filters[0].value;
return await db.users.findById(userId);
}

async update(oldObj: O<typeof this._meta>, filters: UpdateFilters): Promise<O<typeof this._meta>> {
const userId = filters.filters[0].value;
return await db.users.update(userId, { ...oldObj, ...filters.updatedData });
}

async after(data: O<typeof this._meta>): Promise<O<typeof this._meta>> {
await cache.invalidate(`user:${data.id}`);
return data;
}
}

// Register route
openapi.put('/users/:id', UpdateUser);
`

DeleteEndpoint

`import { DeleteEndpoint, type Filters, type O } from 'chanfana';

class DeleteUser extends DeleteEndpoint {
_meta = userMeta;

async before(oldObj: O<typeof this._meta>, filters: Filters): Promise<Filters> {
await checkDeletionPermissions(oldObj.id);
return filters;
}

async getObject(filters: Filters): Promise<O<typeof this._meta> | null> {
const userId = filters.filters[0].value;
return await db.users.findById(userId);
}

async delete(oldObj: O<typeof this._meta>, filters: Filters): Promise<O<typeof this._meta> | null> {
const userId = filters.filters[0].value;
await db.users.delete(userId);
return oldObj;
}

async after(data: O<typeof this._meta>): Promise<O<typeof this._meta>> {
await auditLog.record('user_deleted', data.id);
return data;
}
}

// Register route
openapi.delete('/users/:id', DeleteUser);
`

Nested Routes with pathParameters

For composite primary keys in nested routes:

`const PostSchema = z.object({
userId: z.uuid(),
id: z.uuid(),
title: z.string(),
content: z.string(),
});

const postMeta = {
model: {
schema: PostSchema,
primaryKeys: ['userId', 'id'], // Composite primary key
tableName: 'posts',
},
pathParameters: ['userId', 'id'], // Explicit path params
};

class GetPost extends ReadEndpoint {
_meta = postMeta;

async fetch(filters: Filters) {
const userId = filters.filters.find(f => f.field === 'userId')?.value;
const postId = filters.filters.find(f => f.field === 'id')?.value;
return await db.posts.findOne({ userId, id: postId });
}
}

// Nested route: /users/:userId/posts/:id
const postsRouter = new Hono();
const postsOpenapi = fromHono(postsRouter);
postsOpenapi.get('/:id', GetPost);

// Mount nested router
openapi.route('/:userId/posts', postsOpenapi);
`

Part 3: D1 Database Integration

D1 Endpoint Classes

D1 endpoints extend CRUD endpoints with built-in database operations:

`import {
D1CreateEndpoint,
D1ReadEndpoint,
D1UpdateEndpoint,
D1DeleteEndpoint,
D1ListEndpoint,
InputValidationException,
} from 'chanfana';

// wrangler.toml:
// [[d1_databases]]
// binding = "DB"
// database_name = "my-database"
// database_id = "your-database-id"

class CreateUser extends D1CreateEndpoint {
_meta = userMeta;
dbName = 'DB'; // Must match wrangler.toml binding name

// Optional: Handle UNIQUE constraint violations
constraintsMessages = {
'users_email_unique': new InputValidationException(
'Email already registered',
['body', 'email']
),
'users_username_unique': new InputValidationException(
'Username already taken',
['body', 'username']
),
};

// Optional: Enable logging
logger = console;
}

class GetUser extends D1ReadEndpoint {
_meta = userMeta;
dbName = 'DB';
}

class UpdateUser extends D1UpdateEndpoint {
_meta = userMeta;
dbName = 'DB';
}

class DeleteUser extends D1DeleteEndpoint {
_meta = userMeta;
dbName = 'DB';
}

class ListUsers extends D1ListEndpoint {
_meta = userMeta;
dbName = 'DB';
filterFields = ['role', 'status'];
searchFields = ['username', 'email'];
orderByFields = ['createdAt', 'username'];
defaultOrderBy = 'createdAt';
}

// Register routes
const app = new Hono<{ Bindings: { DB: D1Database } }>();
const openapi = fromHono(app);

openapi.post('/users', CreateUser);
openapi.get('/users', ListUsers);
openapi.get('/users/:id', GetUser);
openapi.put('/users/:id', UpdateUser);
openapi.delete('/users/:id', DeleteUser);
`

SQL Injection Prevention

D1 endpoints include built-in security utilities:

`import {
validateSqlIdentifier,
validateTableName,
validateColumnName,
buildSafeFilters,
} from 'chanfana/endpoints/d1/base';

// Validate identifiers
const table = validateTableName('users'); // OK
const column = validateColumnName('email'); // OK
validateTableName('DROP TABLE--'); // Throws ApiException

// Build safe WHERE clauses
const filters = [
{ field: 'status', operator: 'EQ', value: 'active' },
{ field: 'role', operator: 'EQ', value: 'admin' },
];
const validColumns = ['id', 'status', 'role', 'name'];
const { conditions, conditionsParams } = buildSafeFilters(filters, validColumns);
// conditions: ['status = ?1', 'role = ?2']
// conditionsParams: ['active', 'admin']
`

Part 4: Error Handling

Exception Classes

ExceptionStatusCodeDefault MessageSpecial PropertiesApiException5007000"Internal Error"Base classInputValidationException4007001"Input Validation Error"pathNotFoundException4047002"Not Found"-UnauthorizedException4017003"Unauthorized"-ForbiddenException4037004"Forbidden"-MethodNotAllowedException4057005"Method Not Allowed"-ConflictException4097006"Conflict"-UnprocessableEntityException4227007"Unprocessable Entity"pathTooManyRequestsException4297008"Too Many Requests"retryAfterInternalServerErrorException5007009"Internal Server Error"isVisible: falseBadGatewayException5027010"Bad Gateway"-ServiceUnavailableException5037011"Service Unavailable"retryAfterGatewayTimeoutException5047012"Gateway Timeout"-

Throwing Exceptions

`import {
InputValidationException,
NotFoundException,
UnauthorizedException,
ForbiddenException,
ConflictException,
TooManyRequestsException,
MultiException,
} from 'chanfana';

class MyEndpoint extends OpenAPIRoute {
async handle(c) {
// Validation error with path
if (!isValidEmail(email)) {
throw new InputValidationException('Invalid email format', ['body', 'email']);
}

// Not found
const user = await db.users.findById(id);
if (!user) {
throw new NotFoundException(`User ${id} not found`);
}

// Authentication required
if (!c.req.header('Authorization')) {
throw new UnauthorizedException('Authentication required');
}

// Permission denied
if (!user.hasPermission('admin')) {
throw new ForbiddenException('Admin access required');
}

// Resource conflict
if (await db.users.existsByEmail(email)) {
throw new ConflictException('Email already registered');
}

// Rate limiting
if (rateLimitExceeded) {
throw new TooManyRequestsException('Rate limit exceeded', 60); // retry after 60s
}

// Multiple errors
const errors = [];
if (field1Invalid) errors.push(new InputValidationException('Field 1 invalid', ['body', 'field1']));
if (field2Invalid) errors.push(new InputValidationException('Field 2 invalid', ['body', 'field2']));
if (errors.length > 0) {
throw new MultiException(errors);
}

return { success: true };
}
}
`

Documenting Exceptions in Schema

`import {
OpenAPIRoute,
contentJson,
InputValidationException,
NotFoundException,
UnauthorizedException,
} from 'chanfana';

class GetUser extends OpenAPIRoute {
schema = {
request: {
params: z.object({ id: z.uuid() }),
},
responses: {
"200": {
description: 'User found',
...contentJson(UserSchema),
},
...InputValidationException.schema(), // Documents 400 response
...UnauthorizedException.schema(), // Documents 401 response
...NotFoundException.schema(), // Documents 404 response
},
};
}
`

Part 5: Verification

Checklist

Basic Endpoints:

  • Schema defines responses (required, even if just 200)
  • Using contentJson() wrapper for JSON request/response bodies
  • Using await this.getValidatedData<typeof this.schema>() for type-safe access
  • Using Zod v4 syntax (z.email() not z.string().email())
  • Path parameters in schema match route definition (:userId -> params: z.object({ userId: ... }))
  • Exception responses documented using ...ExceptionClass.schema() spread

CRUD Auto Endpoints:

  • _meta property is defined on the endpoint class
  • _meta.model.schema is a valid Zod object schema
  • _meta.model.primaryKeys is an array of primary key field names
  • _meta.model.tableName is set (required for D1 endpoints)
  • Nested routes use pathParameters in meta for composite primary keys
  • _meta.tags is set to group related endpoints under OpenAPI tags
  • ListEndpoint has filterFields, searchFields, orderByFields configured as needed

D1 Endpoints:

  • dbName matches the binding name in wrangler.toml
  • constraintsMessages defined for UNIQUE constraint handling
  • Hono app typed with { Bindings: { DB: D1Database } }

Common Mistakes

  1. Missing contentJson wrapper
`// WRONG - response body not properly documented
responses: {
"200": {
description: 'Success',
content: { 'application/json': { schema: z.object({...}) } }
}
}

// CORRECT - use contentJson helper
responses: {
"200": {
description: 'Success',
...contentJson(z.object({...}))
}
}
`
  1. Not awaiting getValidatedData
`// WRONG - missing await
const data = this.getValidatedData<typeof this.schema>();

// CORRECT
const data = await this.getValidatedData<typeof this.schema>();
`
  1. Using Zod v3 syntax
`// WRONG - Zod v3 syntax
z.string().email()
z.string().datetime()
z.object({}).strict()

// CORRECT - Zod v4 syntax
z.email()
z.iso.datetime()
z.strictObject({})
`
  1. Forgetting response schema
`// WRONG - no responses defined
schema = { request: { ... } }

// CORRECT - always define responses
schema = {
request: { ... },
responses: { "200": { description: 'Success', ...contentJson(...) } }
}
`
  1. Primary key mismatch in nested routes
`// WRONG - composite key not reflected in pathParameters
const postMeta = {
model: {
primaryKeys: ['userId', 'postId'],
}
};
// Route: /users/:userId/posts/:postId but no pathParameters

// CORRECT - explicitly define pathParameters
const postMeta = {
model: {
primaryKeys: ['userId', 'postId'],
},
pathParameters: ['userId', 'postId'],
};
`
  1. Optional fields with defaults in Zod v4
`// GOTCHA - Zod v4 always provides default values
const data = await this.getValidatedData();
// data.body.status is 'active' even if not sent in request

// SOLUTION - use getUnvalidatedData() to check what was actually sent
const raw = await this.getUnvalidatedData();
if ('status' in raw.body) {
// status was actually sent
}
`
  1. D1 binding name mismatch
`// WRONG - binding name doesn't match wrangler.toml
class MyEndpoint extends D1CreateEndpoint {
dbName = 'DATABASE'; // wrangler.toml has binding = "DB"
}

// CORRECT
class MyEndpoint extends D1CreateEndpoint {
dbName = 'DB'; // matches wrangler.toml [[d1_databases]] binding
}
`
  1. Missing _meta in auto endpoints
`// WRONG - no _meta defined
class CreateUser extends CreateEndpoint {
async create(data) { ... }
}

// CORRECT - _meta is required
class CreateUser extends CreateEndpoint {
_meta = {
model: {
schema: UserSchema,
primaryKeys: ['id'],
tableName: 'users',
},
};
async create(data) { ... }
}
`
  1. Using nativeEnum in Zod v4
`// WRONG - Zod v3 syntax
enum Status { Active = 'active', Inactive = 'inactive' }
z.nativeEnum(Status)

// CORRECT - Zod v4 syntax
z.enum(['active', 'inactive'])
`

More skills from cloudflare

agents-sdkby cloudflareBuild AI agents on Cloudflare Workers using the Agents SDK. Load when creating stateful agents, durable workflows, real-time WebSocket apps, scheduled tasks,…building-ai-agent-on-cloudflareby cloudflareCreates AI-powered agents using Cloudflare's Agents SDK with persistent state, real-time communication, and tool integration.building-mcp-server-on-cloudflareby cloudflareCreates production-ready Model Context Protocol servers on Cloudflare Workers with tools, authentication, and deployment.changelogby cloudflareCreates, updates, and reviews product changelog entries for the Cloudflare documentation site. Load when generating changelog MDX files, editing existing…cloudflareby cloudflareComprehensive Cloudflare platform skill covering Workers, Pages, storage (KV, D1, R2), AI (Workers AI, Vectorize, Agents SDK), networking (Tunnel, Spectrum),…code-reviewby cloudflareReviews Workers and Cloudflare Developer Platform code for type correctness, API usage, and configuration validity. Load when reviewing TypeScript/JavaScript…docs-reviewby cloudflareReviews documentation PRs and provides GitHub PR suggestions. Load when asked to review, suggest changes, or provide feedback on docs content. Covers MDX,…durable-objectsby cloudflareCreate and review Cloudflare Durable Objects. Use when building stateful coordination (chat rooms, multiplayer games, booking systems), implementing RPC…

---

Source: https://github.com/cloudflare/chanfana/tree/HEAD/skills/write-endpoints
Author: cloudflare
Discovered via: mcpservers.org

SKILL.md source

---
name: write-endpoints
description: Comprehensive guide for building OpenAPI endpoints with chanfana - schema definition, request validation, CRUD operations, D1 database integration, and…
---

# write-endpoints

Comprehensive guide for building OpenAPI endpoints with chanfana - schema definition, request validation, CRUD operations, D1 database integration, and…

# write-endpointsby cloudflare
Comprehensive guide for building OpenAPI endpoints with chanfana - schema definition, request validation, CRUD operations, D1 database integration, and…

`npx skills add https://github.com/cloudflare/chanfana --skill write-endpoints`Download ZIPGitHub

## Writing OpenAPI Endpoints with Chanfana

## When to Use

Use this skill when:

* Building OpenAPI endpoints with chanfana for Cloudflare Workers

* Defining request/response schemas with Zod v4

* Creating CRUD auto endpoints (Create, Read, Update, Delete, List)

* Integrating with Cloudflare D1 databases

* Implementing error handling with exception classes

## Part 1: Fundamentals

### Quick Start with Hono

```
`import { Hono, type Context } from 'hono';
import { fromHono, OpenAPIRoute, contentJson } from 'chanfana';
import { z } from 'zod';

export type Env = {
DB: D1Database;
};
export type AppContext = Context<{ Bindings: Env }>;

class HelloEndpoint extends OpenAPIRoute {
schema = {
responses: {
"200": {
description: 'Successful response',
...contentJson(z.object({ message: z.string() })),
},
},
};

async handle(c: AppContext) {
return { message: 'Hello, Chanfana!' };
}
}

const app = new Hono<{ Bindings: Env }>();
const openapi = fromHono(app);
openapi.get('/hello', HelloEndpoint);

export default app;
`
```

### Quick Start with itty-router

```
`import { Router } from 'itty-router';
import { fromIttyRouter, OpenAPIRoute, contentJson } from 'chanfana';
import { z } from 'zod';

class HelloEndpoint extends OpenAPIRoute {
schema = {
responses: {
"200": {
description: 'Successful response',
...contentJson(z.object({ message: z.string() })),
},
},
};

async handle(request: Request, env, ctx) {
return { message: 'Hello, Chanfana!' };
}
}

const router = Router();
const openapi = fromIttyRouter(router);
openapi.get('/hello', HelloEndpoint);
router.all('*', () => new Response("Not Found.", { status: 404 }));

export const fetch = router.handle;
`
```

### Schema Definition

Define request validation for body, query, params, and headers:

```
`import { OpenAPIRoute, contentJson } from 'chanfana';
import { z } from 'zod';

class CreateUserEndpoint extends OpenAPIRoute {
schema = {
request: {
body: contentJson(z.object({
username: z.string().min(3).max(20),
password: z.string().min(8),
email: z.email(),
fullName: z.string().optional(),
})),
query: z.object({
notify: z.boolean().optional().default(true),
}),
params: z.object({
orgId: z.uuid(),
}),
headers: z.object({
'X-API-Key': z.string(),
}),
},
responses: {
"200": {
description: 'User created successfully',
...contentJson(z.object({
id: z.uuid(),
username: z.string(),
email: z.email(),
})),
},
"400": {
description: 'Validation error',
...contentJson(z.object({
success: z.literal(false),
errors: z.array(z.object({
code: z.number(),
message: z.string(),
})),
})),
},
},
};

async handle(c) {
const data = await this.getValidatedData<typeof this.schema>();
// data.body, data.query, data.params, data.headers are all typed
return { id: crypto.randomUUID(), username: data.body.username, email: data.body.email };
}
}
`
```

### Zod v4 Syntax (CRITICAL)

Chanfana v3 uses Zod v4. Use the correct syntax:

```
`// WRONG - Zod v3 syntax (deprecated)
z.string().email()
z.string().uuid()
z.string().datetime()
z.string().date()
z.string().url()
z.string().ip({ version: "v4" })
z.object({}).strict()
z.nativeEnum(MyEnum)

// CORRECT - Zod v4 syntax
z.email()
z.uuid()
z.iso.datetime()
z.iso.date()
z.url()
z.ipv4()
z.strictObject({})
z.enum(['option1', 'option2'])
`
```

### Common Zod Types for APIs

Use native Zod schemas for all parameter types:

```
`import { z } from 'zod';

// String with constraints
const nameSchema = z.string()
.min(3)
.max(50)
.describe("User's name")
.openapi({ example: 'John Doe' });

// Number with range
const priceSchema = z.number()
.min(0)
.describe('Product price')
.openapi({ example: 99.99 });

// Integer
const ageSchema = z.number()
.int()
.min(0)
.max(120)
.describe("User's age");

// Boolean with default
const isActiveSchema = z.boolean()
.default(true)
.describe('User active status');

// Date/time (ISO 8601)
const createdAtSchema = z.iso.datetime()
.describe('Creation timestamp')
.openapi({ example: '2024-01-20T10:30:00Z' });

// Date only (YYYY-MM-DD)
const birthDateSchema = z.iso.date()
.describe('Birth date')
.openapi({ example: '1990-05-15' });

// Email, UUID
const emailSchema = z.email().describe('Email address');
const userIdSchema = z.uuid().describe('User ID');

// Enumeration
const statusSchema = z.enum(['pending', 'processing', 'shipped', 'delivered'])
.default('pending')
.describe('Order status');

// Array
const tagsSchema = z.array(z.string()).openapi({
description: 'Tags',
});

// Object
const addressSchema = z.object({
street: z.string().describe('Street address'),
city: z.string().describe('City'),
zipCode: z.string().describe('Zip code'),
});

// Regex pattern
const phoneSchema = z.string()
.regex(/^\+?[1-9]\d{1,14}$/, 'Invalid phone number format')
.describe('Phone number');

// IP addresses
const ipv4Schema = z.ipv4();
const ipv6Schema = z.ipv6();
const ipSchema = z.union([z.ipv4(), z.ipv6()]);

// Hostname (regex pattern)
const hostnameSchema = z.string().regex(
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/
);
`
```

### Validated Data Access

Always use `await` with `getValidatedData()`:

```
`class MyEndpoint extends OpenAPIRoute {
async handle(c) {
// CORRECT - with await and type annotation
const data = await this.getValidatedData<typeof this.schema>();

// Type-safe access
const username = data.body.username;
const page = data.query.page;
const userId = data.params.userId;
const apiKey = data.headers['X-API-Key'];

return { success: true };
}
}
`
```

### Using getUnvalidatedData() for Partial Updates

In Zod v4, optional fields with `.default()` always have values in validated data. Use `getUnvalidatedData()` to detect what was actually sent:

```
`class UpdateUser extends OpenAPIRoute {
schema = {
request: {
body: contentJson(z.object({
name: z.string().optional(),
status: z.enum(['active', 'inactive']).default('active'),
})),
},
};

async handle() {
const validated = await this.getValidatedData<typeof this.schema>();
// validated.body.status is 'active' even if not sent

const raw = await this.getUnvalidatedData();
// raw.body = {} if nothing was sent

// Check what was actually sent
const updates: Record<string, any> = {};
if ('name' in raw.body) updates.name = validated.body.name;
if ('status' in raw.body) updates.status = validated.body.status;

return { updated: updates };
}
}
`
```

## Part 2: CRUD Auto Endpoints

### Meta Object Definition

All auto endpoints require a `_meta` property:

```
`import { z } from 'zod';

// Define the model schema
const UserSchema = z.object({
id: z.uuid(),
username: z.string().min(3).max(20),
email: z.email(),
role: z.enum(['user', 'admin']),
createdAt: z.iso.datetime(),
});

// Define the meta object
const userMeta = {
model: {
schema: UserSchema, // Required: Zod schema for the model
primaryKeys: ['id'], // Required: Array of primary key fields
tableName: 'users', // Required for D1 endpoints
serializer: (user: any) => { // Optional: Transform output
const { passwordHash, ...safe } = user;
return safe;
},
serializerSchema: UserSchema.omit({ passwordHash: true }), // Optional: Schema for serialized output
},
pathParameters: ['id'], // Optional: Explicit path params for nested routes
tags: ['Users'], // Optional: OpenAPI tags for grouping operations
};
`
```

### CreateEndpoint

```
`import { CreateEndpoint, type O } from 'chanfana';

class CreateUser extends CreateEndpoint {
_meta = userMeta;

// Optional: Pre-processing hook
async before(data: O<typeof this._meta>): Promise<O<typeof this._meta>> {
return {
...data,
id: crypto.randomUUID(),
createdAt: new Date().toISOString(),
};
}

// Required: Create logic
async create(data: O<typeof this._meta>) {
await db.users.insert(data);
return data;
}

// Optional: Post-processing hook
async after(data: O<typeof this._meta>): Promise<O<typeof this._meta>> {
await sendWelcomeEmail(data.email);
return data;
}
}

// Register route
openapi.post('/users', CreateUser);
`
```

### ReadEndpoint

```
`import { ReadEndpoint, type Filters, type O } from 'chanfana';

class GetUser extends ReadEndpoint {
_meta = userMeta;

async before(filters: Filters): Promise<Filters> {
// Pre-fetch validation
return filters;
}

async fetch(filters: Filters): Promise<O<typeof this._meta> | null> {
const userId = filters.filters[0].value;
return await db.users.findById(userId);
}

async after(data: O<typeof this._meta>): Promise<O<typeof this._meta>> {
// Post-fetch processing
return data;
}
}

// Register route with path parameter
openapi.get('/users/:id', GetUser);
`
```

### ListEndpoint

```
`import { ListEndpoint, type ListFilters, type ListResult, type O } from 'chanfana';

class ListUsers extends ListEndpoint {
_meta = userMeta;

// Configure filtering, search, and sorting
filterFields = ['role', 'status']; // Exact match filtering
searchFields = ['username', 'email']; // Full-text search (LIKE)
orderByFields = ['createdAt', 'username']; // Available sort fields
defaultOrderBy = 'createdAt'; // Default sort field

async before(filters: ListFilters): Promise<ListFilters> {
// Add tenant filter, etc.
return filters;
}

async list(filters: ListFilters): Promise<ListResult<O<typeof this._meta>>> {
const users = await db.users.findMany(filters);
return { result: users };
}

async after(data: ListResult<O<typeof this._meta>>): Promise<ListResult<O<typeof this._meta>>> {
return data;
}
}

// Register route
openapi.get('/users', ListUsers);

// API calls:
// GET /users?page=2&per_page=10
// GET /users?role=admin
// GET /users?search=john
// GET /users?order_by=createdAt&order_by_direction=desc
`
```

### UpdateEndpoint

```
`import { UpdateEndpoint, type UpdateFilters, type O } from 'chanfana';

class UpdateUser extends UpdateEndpoint {
_meta = userMeta;

async before(oldObj: O<typeof this._meta>, filters: UpdateFilters): Promise<UpdateFilters> {
filters.updatedData = {
...filters.updatedData,
updatedAt: new Date().toISOString(),
};
return filters;
}

async getObject(filters: UpdateFilters): Promise<O<typeof this._meta> | null> {
const userId = filters.filters[0].value;
return await db.users.findById(userId);
}

async update(oldObj: O<typeof this._meta>, filters: UpdateFilters): Promise<O<typeof this._meta>> {
const userId = filters.filters[0].value;
return await db.users.update(userId, { ...oldObj, ...filters.updatedData });
}

async after(data: O<typeof this._meta>): Promise<O<typeof this._meta>> {
await cache.invalidate(`user:${data.id}`);
return data;
}
}

// Register route
openapi.put('/users/:id', UpdateUser);
`
```

### DeleteEndpoint

```
`import { DeleteEndpoint, type Filters, type O } from 'chanfana';

class DeleteUser extends DeleteEndpoint {
_meta = userMeta;

async before(oldObj: O<typeof this._meta>, filters: Filters): Promise<Filters> {
await checkDeletionPermissions(oldObj.id);
return filters;
}

async getObject(filters: Filters): Promise<O<typeof this._meta> | null> {
const userId = filters.filters[0].value;
return await db.users.findById(userId);
}

async delete(oldObj: O<typeof this._meta>, filters: Filters): Promise<O<typeof this._meta> | null> {
const userId = filters.filters[0].value;
await db.users.delete(userId);
return oldObj;
}

async after(data: O<typeof this._meta>): Promise<O<typeof this._meta>> {
await auditLog.record('user_deleted', data.id);
return data;
}
}

// Register route
openapi.delete('/users/:id', DeleteUser);
`
```

### Nested Routes with pathParameters

For composite primary keys in nested routes:

```
`const PostSchema = z.object({
userId: z.uuid(),
id: z.uuid(),
title: z.string(),
content: z.string(),
});

const postMeta = {
model: {
schema: PostSchema,
primaryKeys: ['userId', 'id'], // Composite primary key
tableName: 'posts',
},
pathParameters: ['userId', 'id'], // Explicit path params
};

class GetPost extends ReadEndpoint {
_meta = postMeta;

async fetch(filters: Filters) {
const userId = filters.filters.find(f => f.field === 'userId')?.value;
const postId = filters.filters.find(f => f.field === 'id')?.value;
return await db.posts.findOne({ userId, id: postId });
}
}

// Nested route: /users/:userId/posts/:id
const postsRouter = new Hono();
const postsOpenapi = fromHono(postsRouter);
postsOpenapi.get('/:id', GetPost);

// Mount nested router
openapi.route('/:userId/posts', postsOpenapi);
`
```

## Part 3: D1 Database Integration

### D1 Endpoint Classes

D1 endpoints extend CRUD endpoints with built-in database operations:

```
`import {
D1CreateEndpoint,
D1ReadEndpoint,
D1UpdateEndpoint,
D1DeleteEndpoint,
D1ListEndpoint,
InputValidationException,
} from 'chanfana';

// wrangler.toml:
// [[d1_databases]]
// binding = "DB"
// database_name = "my-database"
// database_id = "your-database-id"

class CreateUser extends D1CreateEndpoint {
_meta = userMeta;
dbName = 'DB'; // Must match wrangler.toml binding name

// Optional: Handle UNIQUE constraint violations
constraintsMessages = {
'users_email_unique': new InputValidationException(
'Email already registered',
['body', 'email']
),
'users_username_unique': new InputValidationException(
'Username already taken',
['body', 'username']
),
};

// Optional: Enable logging
logger = console;
}

class GetUser extends D1ReadEndpoint {
_meta = userMeta;
dbName = 'DB';
}

class UpdateUser extends D1UpdateEndpoint {
_meta = userMeta;
dbName = 'DB';
}

class DeleteUser extends D1DeleteEndpoint {
_meta = userMeta;
dbName = 'DB';
}

class ListUsers extends D1ListEndpoint {
_meta = userMeta;
dbName = 'DB';
filterFields = ['role', 'status'];
searchFields = ['username', 'email'];
orderByFields = ['createdAt', 'username'];
defaultOrderBy = 'createdAt';
}

// Register routes
const app = new Hono<{ Bindings: { DB: D1Database } }>();
const openapi = fromHono(app);

openapi.post('/users', CreateUser);
openapi.get('/users', ListUsers);
openapi.get('/users/:id', GetUser);
openapi.put('/users/:id', UpdateUser);
openapi.delete('/users/:id', DeleteUser);
`
```

### SQL Injection Prevention

D1 endpoints include built-in security utilities:

```
`import {
validateSqlIdentifier,
validateTableName,
validateColumnName,
buildSafeFilters,
} from 'chanfana/endpoints/d1/base';

// Validate identifiers
const table = validateTableName('users'); // OK
const column = validateColumnName('email'); // OK
validateTableName('DROP TABLE--'); // Throws ApiException

// Build safe WHERE clauses
const filters = [
{ field: 'status', operator: 'EQ', value: 'active' },
{ field: 'role', operator: 'EQ', value: 'admin' },
];
const validColumns = ['id', 'status', 'role', 'name'];
const { conditions, conditionsParams } = buildSafeFilters(filters, validColumns);
// conditions: ['status = ?1', 'role = ?2']
// conditionsParams: ['active', 'admin']
`
```

## Part 4: Error Handling

### Exception Classes

ExceptionStatusCodeDefault MessageSpecial Properties`ApiException`5007000"Internal Error"Base class`InputValidationException`4007001"Input Validation Error"`path``NotFoundException`4047002"Not Found"-`UnauthorizedException`4017003"Unauthorized"-`ForbiddenException`4037004"Forbidden"-`MethodNotAllowedException`4057005"Method Not Allowed"-`ConflictException`4097006"Conflict"-`UnprocessableEntityException`4227007"Unprocessable Entity"`path``TooManyRequestsException`4297008"Too Many Requests"`retryAfter``InternalServerErrorException`5007009"Internal Server Error"`isVisible: false``BadGatewayException`5027010"Bad Gateway"-`ServiceUnavailableException`5037011"Service Unavailable"`retryAfter``GatewayTimeoutException`5047012"Gateway Timeout"-

### Throwing Exceptions

```
`import {
InputValidationException,
NotFoundException,
UnauthorizedException,
ForbiddenException,
ConflictException,
TooManyRequestsException,
MultiException,
} from 'chanfana';

class MyEndpoint extends OpenAPIRoute {
async handle(c) {
// Validation error with path
if (!isValidEmail(email)) {
throw new InputValidationException('Invalid email format', ['body', 'email']);
}

// Not found
const user = await db.users.findById(id);
if (!user) {
throw new NotFoundException(`User ${id} not found`);
}

// Authentication required
if (!c.req.header('Authorization')) {
throw new UnauthorizedException('Authentication required');
}

// Permission denied
if (!user.hasPermission('admin')) {
throw new ForbiddenException('Admin access required');
}

// Resource conflict
if (await db.users.existsByEmail(email)) {
throw new ConflictException('Email already registered');
}

// Rate limiting
if (rateLimitExceeded) {
throw new TooManyRequestsException('Rate limit exceeded', 60); // retry after 60s
}

// Multiple errors
const errors = [];
if (field1Invalid) errors.push(new InputValidationException('Field 1 invalid', ['body', 'field1']));
if (field2Invalid) errors.push(new InputValidationException('Field 2 invalid', ['body', 'field2']));
if (errors.length > 0) {
throw new MultiException(errors);
}

return { success: true };
}
}
`
```

### Documenting Exceptions in Schema

```
`import {
OpenAPIRoute,
contentJson,
InputValidationException,
NotFoundException,
UnauthorizedException,
} from 'chanfana';

class GetUser extends OpenAPIRoute {
schema = {
request: {
params: z.object({ id: z.uuid() }),
},
responses: {
"200": {
description: 'User found',
...contentJson(UserSchema),
},
...InputValidationException.schema(), // Documents 400 response
...UnauthorizedException.schema(), // Documents 401 response
...NotFoundException.schema(), // Documents 404 response
},
};
}
`
```

## Part 5: Verification

### Checklist

Basic Endpoints:

* Schema defines `responses` (required, even if just 200)

* Using `contentJson()` wrapper for JSON request/response bodies

* Using `await this.getValidatedData<typeof this.schema>()` for type-safe access

* Using Zod v4 syntax (`z.email()` not `z.string().email()`)

* Path parameters in schema match route definition (`:userId` -> `params: z.object({ userId: ... })`)

* Exception responses documented using `...ExceptionClass.schema()` spread

CRUD Auto Endpoints:

* `_meta` property is defined on the endpoint class

* `_meta.model.schema` is a valid Zod object schema

* `_meta.model.primaryKeys` is an array of primary key field names

* `_meta.model.tableName` is set (required for D1 endpoints)

* Nested routes use `pathParameters` in meta for composite primary keys

* `_meta.tags` is set to group related endpoints under OpenAPI tags

* ListEndpoint has `filterFields`, `searchFields`, `orderByFields` configured as needed

D1 Endpoints:

* `dbName` matches the binding name in wrangler.toml

* `constraintsMessages` defined for UNIQUE constraint handling

* Hono app typed with `{ Bindings: { DB: D1Database } }`

### Common Mistakes

1. Missing contentJson wrapper

```
`// WRONG - response body not properly documented
responses: {
"200": {
description: 'Success',
content: { 'application/json': { schema: z.object({...}) } }
}
}

// CORRECT - use contentJson helper
responses: {
"200": {
description: 'Success',
...contentJson(z.object({...}))
}
}
`
```

2. Not awaiting getValidatedData

```
`// WRONG - missing await
const data = this.getValidatedData<typeof this.schema>();

// CORRECT
const data = await this.getValidatedData<typeof this.schema>();
`
```

3. Using Zod v3 syntax

```
`// WRONG - Zod v3 syntax
z.string().email()
z.string().datetime()
z.object({}).strict()

// CORRECT - Zod v4 syntax
z.email()
z.iso.datetime()
z.strictObject({})
`
```

4. Forgetting response schema

```
`// WRONG - no responses defined
schema = { request: { ... } }

// CORRECT - always define responses
schema = {
request: { ... },
responses: { "200": { description: 'Success', ...contentJson(...) } }
}
`
```

5. Primary key mismatch in nested routes

```
`// WRONG - composite key not reflected in pathParameters
const postMeta = {
model: {
primaryKeys: ['userId', 'postId'],
}
};
// Route: /users/:userId/posts/:postId but no pathParameters

// CORRECT - explicitly define pathParameters
const postMeta = {
model: {
primaryKeys: ['userId', 'postId'],
},
pathParameters: ['userId', 'postId'],
};
`
```

6. Optional fields with defaults in Zod v4

```
`// GOTCHA - Zod v4 always provides default values
const data = await this.getValidatedData();
// data.body.status is 'active' even if not sent in request

// SOLUTION - use getUnvalidatedData() to check what was actually sent
const raw = await this.getUnvalidatedData();
if ('status' in raw.body) {
// status was actually sent
}
`
```

7. D1 binding name mismatch

```
`// WRONG - binding name doesn't match wrangler.toml
class MyEndpoint extends D1CreateEndpoint {
dbName = 'DATABASE'; // wrangler.toml has binding = "DB"
}

// CORRECT
class MyEndpoint extends D1CreateEndpoint {
dbName = 'DB'; // matches wrangler.toml [[d1_databases]] binding
}
`
```

8. Missing _meta in auto endpoints

```
`// WRONG - no _meta defined
class CreateUser extends CreateEndpoint {
async create(data) { ... }
}

// CORRECT - _meta is required
class CreateUser extends CreateEndpoint {
_meta = {
model: {
schema: UserSchema,
primaryKeys: ['id'],
tableName: 'users',
},
};
async create(data) { ... }
}
`
```

9. Using nativeEnum in Zod v4

```
`// WRONG - Zod v3 syntax
enum Status { Active = 'active', Inactive = 'inactive' }
z.nativeEnum(Status)

// CORRECT - Zod v4 syntax
z.enum(['active', 'inactive'])
`
```

## More skills from cloudflare
agents-sdkby cloudflareBuild AI agents on Cloudflare Workers using the Agents SDK. Load when creating stateful agents, durable workflows, real-time WebSocket apps, scheduled tasks,…building-ai-agent-on-cloudflareby cloudflareCreates AI-powered agents using Cloudflare's Agents SDK with persistent state, real-time communication, and tool integration.building-mcp-server-on-cloudflareby cloudflareCreates production-ready Model Context Protocol servers on Cloudflare Workers with tools, authentication, and deployment.changelogby cloudflareCreates, updates, and reviews product changelog entries for the Cloudflare documentation site. Load when generating changelog MDX files, editing existing…cloudflareby cloudflareComprehensive Cloudflare platform skill covering Workers, Pages, storage (KV, D1, R2), AI (Workers AI, Vectorize, Agents SDK), networking (Tunnel, Spectrum),…code-reviewby cloudflareReviews Workers and Cloudflare Developer Platform code for type correctness, API usage, and configuration validity. Load when reviewing TypeScript/JavaScript…docs-reviewby cloudflareReviews documentation PRs and provides GitHub PR suggestions. Load when asked to review, suggest changes, or provide feedback on docs content. Covers MDX,…durable-objectsby cloudflareCreate and review Cloudflare Durable Objects. Use when building stateful coordination (chat rooms, multiplayer games, booking systems), implementing RPC…

---

**Source**: https://github.com/cloudflare/chanfana/tree/HEAD/skills/write-endpoints
**Author**: cloudflare
**Discovered via**: mcpservers.org

Related skills 6

caveman

★ Featured

Ultra-compressed communication mode. Cuts token usage ~75% by speaking like caveman while keeping full technical accuracy. Supports intensity levels: lite, full (default), ultra, wenyan-lite, wenyan-full, wenyan-ultra. Use when user says "caveman mode", "talk like caveman", "use caveman", "less tokens", "be brief", or invokes /caveman. Also auto-triggers when token efficiency is requested.

juliusbrussee 167k
Development

secure-linux-web-hosting

★ Featured

Use when setting up, hardening, or reviewing a cloud server for self-hosting, including DNS, SSH, firewalls, Nginx, static-site hosting, reverse-proxying an app, HTTPS with Let's Encrypt or ACME clients, safe HTTP-to-HTTPS redirects, or optional post-launch network tuning such as BBR.

xixu-me 155k
Development

readme-i18n

★ Featured

Use when the user wants to translate a repository README, make a repo multilingual, localize docs, add a language switcher, internationalize the README, or update localized README variants in a GitHub-style repository.

xixu-me 155k
Development

lark-shared

★ Featured

Use when first setting up lark-cli, running auth login, switching user/bot identity (--as), handling permission denied or scope errors, needing to update lark-cli, or seeing _notice in JSON output.

larksuite 155k
Development

improve-codebase-architecture

★ Featured

Find deepening opportunities in a codebase, informed by the domain language in CONTEXT.md and the decisions in docs/adr/. Use when the user wants to improve architecture, find refactoring opportunities, consolidate tightly-coupled modules, or make a codebase more testable and AI-navigable.

mattpocock 151k
Development

paper-context-resolver

★ Featured

Optional RigorPilot helper for README-first deep learning repo reproduction. Use only when the README and repository files leave a narrow reproduction-critical gap and the task is to resolve a specific paper detail such as dataset split, preprocessing, evaluation protocol, checkpoint mapping, or runtime assumption from primary paper sources while recording conflicts. Do not use for general paper summary, repo scanning, environment setup, command execution, title-only paper lookup, or replacin...

lllllllama 127k
Development