# Functions API

## File-Based Routing

```
/functions/index.ts              → example.com/
/functions/api/users.ts          → example.com/api/users
/functions/api/users/[id].ts     → example.com/api/users/:id
/functions/api/users/[[path]].ts → example.com/api/users/* (catchall)
/functions/_middleware.ts        → Runs before all routes
```

**Rules**: `[param]` = single segment, `[[param]]` = multi-segment catchall, more specific wins.

## Request Handlers

```typescript
import type { PagesFunction } from '@cloudflare/workers-types'

interface Env {
  DB: D1Database
  KV: KVNamespace
}

// All methods
export const onRequest: PagesFunction<Env> = async (context) => {
  return new Response('All methods')
}

// Method-specific
export const onRequestGet: PagesFunction<Env> = async (context) => {
  const { request, env, params, data } = context

  const user = await env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(params.id).first()

  return Response.json(user)
}

export const onRequestPost: PagesFunction<Env> = async (context) => {
  const body = await context.request.json()
  return Response.json({ success: true })
}

// Also: onRequestPut, onRequestPatch, onRequestDelete, onRequestHead, onRequestOptions
```

## Context Object

```typescript
interface EventContext<Env, Params, Data> {
  request: Request // HTTP request
  env: Env // Bindings (KV, D1, R2, etc.)
  params: Params // Route parameters
  data: Data // Middleware-shared data
  waitUntil: (promise: Promise<any>) => void // Background tasks
  next: () => Promise<Response> // Next handler
  passThroughOnException: () => void // Error fallback (not in advanced mode)
}
```

## Dynamic Routes

```typescript
// Single segment: functions/users/[id].ts
export const onRequestGet: PagesFunction = async ({ params }) => {
  // /users/123 → params.id = "123"
  return Response.json({ userId: params.id })
}

// Multi-segment: functions/files/[[path]].ts
export const onRequestGet: PagesFunction = async ({ params }) => {
  // /files/docs/api/v1.md → params.path = ["docs", "api", "v1.md"]
  const filePath = (params.path as string[]).join('/')
  return new Response(filePath)
}
```

## Middleware

```typescript
// functions/_middleware.ts
// Single
export const onRequest: PagesFunction = async (context) => {
  const response = await context.next()
  response.headers.set('X-Custom-Header', 'value')
  return response
}

// Chained (runs in order)
const errorHandler: PagesFunction = async (context) => {
  try {
    return await context.next()
  } catch (err) {
    return new Response(err.message, { status: 500 })
  }
}

const auth: PagesFunction = async (context) => {
  const token = context.request.headers.get('Authorization')
  if (!token) return new Response('Unauthorized', { status: 401 })
  context.data.userId = await verifyToken(token)
  return context.next()
}

export const onRequest = [errorHandler, auth]
```

**Scope**: `functions/_middleware.ts` → all; `functions/api/_middleware.ts` → `/api/*` only

## Bindings Usage

```typescript
export const onRequestGet: PagesFunction<Env> = async ({ env }) => {
  // KV
  const cached = await env.KV.get('key', 'json')
  await env.KV.put('key', JSON.stringify({ data: 'value' }), { expirationTtl: 3600 })

  // D1
  const result = await env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(userId).first()

  // R2, Queue, AI - see respective reference docs

  return Response.json({ success: true })
}
```

## Advanced Mode

Full Workers API, bypasses file-based routing:

```javascript
// functions/_worker.js
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url)

    // Custom routing
    if (url.pathname.startsWith('/api/')) {
      return new Response('API response')
    }

    // REQUIRED: Serve static assets
    return env.ASSETS.fetch(request)
  },
}
```

**When to use**: WebSockets, complex routing, scheduled handlers, email handlers.

## Smart Placement

Automatically optimizes function execution location based on traffic patterns.

**Configuration** (in wrangler.jsonc):

```jsonc
{
  "placement": {
    "mode": "smart", // Enables optimization (default: off)
  },
}
```

**How it works**: Analyzes traffic patterns over time and places functions closer to users or data sources (e.g., D1 databases). Requires no code changes.

**Trade-offs**: Initial requests may see slightly higher latency during learning period (hours-days). Performance improves as system optimizes.

**When to use**: Global apps with centralized databases or geographically concentrated traffic sources.

## getRequestContext (Framework SSR)

Access bindings in framework code:

```typescript
// SvelteKit
import type { RequestEvent } from '@sveltejs/kit'
export async function load({ platform }: RequestEvent) {
  const data = await platform.env.DB.prepare('SELECT * FROM users').all()
  return { users: data.results }
}

// Astro
const { DB } = Astro.locals.runtime.env
const data = await DB.prepare('SELECT * FROM users').all()

// Solid Start (server function)
import { getRequestEvent } from 'solid-js/web'
const event = getRequestEvent()
const data = await event.locals.runtime.env.DB.prepare('SELECT * FROM users').all()
```

**✅ Supported adapters** (2026):

- **SvelteKit**: `@sveltejs/adapter-cloudflare`
- **Astro**: Built-in Cloudflare adapter
- **Nuxt**: Set `nitro.preset: 'cloudflare-pages'` in `nuxt.config.ts`
- **Qwik**: Built-in Cloudflare adapter
- **Solid Start**: `@solidjs/start-cloudflare-pages`

**❌ Deprecated/Unsupported**:

- **Next.js**: Official adapter (`@cloudflare/next-on-pages`) deprecated. Use Vercel or self-host on Workers.
- **Remix**: Official adapter (`@remix-run/cloudflare-pages`) deprecated. Migrate to supported frameworks.

See [gotchas.md](./gotchas.md#framework-specific) for migration guidance.
