Elysia v2 Start here 1. Inspect , the lockfile, runtime, and app structure. Keep existing versions and conventions unless the user requests a migration. 2. This skill describes 2.0.0-rc.0 or newer. For v1, use migration to recognize differences, not as permission to upgrade. Check installed types when a different beta disagrees. 3. Read only the references the task needs. Versions lists the source revision, package baselines, and verification limits. 4. Check types and exercise the affected route, including its rejection path. See testing. Essential rules - Options precede the handler. , , an…

)({\n\tserver: {\n\t\thandlers: {\n\t\t\tGET: ({ request }) => app.fetch(request)\n\t\t}\n\t}\n})\n```\n\nTo call from loaders on both server and browser, use `createIsomorphicFn` from `@tanstack/react-start`. Its server branch returns `treaty(app).api`, and its client branch returns `treaty\u003ctypeof app>(origin).api`. Keep the server instance in the server branch and verify it is excluded from the client bundle. See [TanStack Start](https://tanstack.com/start).\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":11159,"content_sha256":"719e1c4b79656836b08093bb8d14755eea28399387fd01cf2279403239abf92a"},{"filename":"references/testing.md","content":"# Testing Elysia routes\n\nUse the project's test runner. `app.handle()` tests routing, hooks, validation, and response conversion without listening. These examples use Bun.\n\n## Request and response assertions\n\n```ts\nimport { expect, test } from 'bun:test'\nimport { Elysia, t } from 'elysia'\n\nconst app = new Elysia()\n\t.post('/users', {\n\t\tbody: t.Object({ name: t.String({ minLength: 1 }) })\n\t}, ({ body, status }) => status(201, body))\n\ntest('creates a user', async () => {\n\tconst response = await app.handle('/users', {\n\t\tmethod: 'POST',\n\t\theaders: { 'content-type': 'application/json' },\n\t\tbody: JSON.stringify({ name: 'Ada' })\n\t})\n\n\texpect(response.status).toBe(201)\n\texpect(await response.json()).toEqual({ name: 'Ada' })\n})\n\ntest('rejects invalid input', async () => {\n\tconst response = await app.handle('/users', {\n\t\tmethod: 'POST',\n\t\theaders: { 'content-type': 'application/json' },\n\t\tbody: JSON.stringify({ name: '' })\n\t})\n\n\texpect(response.status).toBe(422)\n})\n```\n\nRun with `bun test`. `app.handle(new Request('http://localhost/users', init))` is equivalent. Constructed `Request` URLs must be absolute.\n\nTest the changed behavior, including rejection statuses and hook scope where relevant. Use function handlers for hook-set headers; static values have a [header caveat](api.md#responses).\n\n## Async plugins\n\nAwait pending modules before calling `app.handle()`, or deferred routes may return 404.\n\n```ts\nconst app = new Elysia().use(async app => app.get('/async', () => 'ready'))\nawait app.modules\nconst response = await app.handle('/async')\nexpect(await response.text()).toBe('ready')\n```\n\n`app.fetch()` and `.listen()` wait for modules themselves.\n\n## Typed client tests\n\nPass the app directly to Eden Treaty without a network server:\n\n```ts\nimport { treaty } from '@elysia/eden'\n\nconst app = new Elysia().get('/hello', () => 'hi')\nconst { data, error } = await treaty(app).hello.get()\nexpect(error).toBeNull()\nexpect(data).toBe('hi')\n```\n\nSee [eden.md](eden.md) for error narrowing, streaming, and configuration. Run the TypeScript build after changing route schemas, macros, or exported app types.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":2113,"content_sha256":"4c4ffbd8166009939e1a9df0a64928fe02699c873c0ee60f1752632c40836046"},{"filename":"references/validation.md","content":"# Validation\n\nRoute schemas validate inputs and infer handler types. They also feed [OpenAPI](openapi.md). Options precede the handler in v2.\n\nContents: [slots](#schema-slots), [coercion](#coercion), [Standard Schema](#standard-schema), [files](#files), [models and types](#models-and-types).\n\n## Schema slots\n\n| Option | Validates |\n| --- | --- |\n| `body` | Parsed request body |\n| `query` | Query string |\n| `params` | Path parameters |\n| `headers` | Request headers |\n| `cookie` | Incoming cookies, with typed `.value` access; see [cookies](cookie.md) |\n| `response` | Handler result, using one schema or a status-to-schema map |\n\n```ts\nimport { Elysia, t } from 'elysia'\n\nexport const app = new Elysia()\n\t.get('/users/:id', {\n\t\tparams: t.Object({ id: t.Number() }),\n\t\tquery: t.Object({ name: t.String() }),\n\t\tresponse: t.Object({ id: t.Number(), name: t.String() })\n\t}, ({ params, query }) => ({ id: params.id, name: query.name }))\n```\n\n`/users/42?name=Ada` passes with numeric `params.id`. Missing `name` or a nonnumeric `id` returns `422`. Response schema failures return `500`. See [errors.md](errors.md#validation-errors) for shapes and messages.\n\n## TypeBox schemas\n\nImport `t` from `elysia`. It combines TypeBox 1.x with HTTP-specific types. Common TypeBox builders include `String`, `Number`, `Boolean`, `Object`, `Array`, `Literal`, `Union`, `Optional`, and `Partial`. Constraints use JSON Schema options such as `minimum`, `minLength`, `format`, and `maxItems`.\n\n| Elysia builder | Behavior |\n| --- | --- |\n| `t.Numeric(options)` | Accepts a number or numeric string; decodes to number |\n| `t.BooleanString()` | Accepts a boolean or `'true'`/`'false'`; decodes to boolean |\n| `t.ObjectString({ ... })` | Accepts an object or its JSON string; decodes to object |\n| `t.UnionEnum(['a', 'b', 1])` | Union of the listed literals |\n| `t.Nullable(schema)` | Schema or `null` |\n| `t.MaybeEmpty(schema)` | Schema, `null`, or `undefined`; not automatically an empty string |\n| `t.File(options)` / `t.Files(options)` | One file or multiple files in one field |\n| `t.Cookie({ ... }, options)` | Cookie schema with cookie configuration |\n| `t.Form({ ... })` | FormData response created with `form()`; see [responses](api.md) |\n| `t.Uint8Array()` | Accepts ArrayBuffer or Uint8Array; decodes to Uint8Array |\n| `t.ArrayBuffer()` | Accepts ArrayBuffer without conversion |\n\nTypeBox 1.x uses `t.Codec`, not `t.Transform`. `t.Recursive`, `t.Not`, and `t.RegExp` are not exported. Schemas have no `.static` property. See [migration.md](migration.md) for v1 changes.\n\n## Coercion\n\nElysia automatically coerces route inputs through TypeBox schemas. Standalone schemas get no route conversion.\n\n- Direct number and boolean fields in params, query, and headers accept their string representations.\n- Root numeric and boolean bodies also accept string representations.\n- Properties inside JSON body objects retain their declared types. With `body: t.Object({ count: t.Number() })`, `{\"count\": \"1\"}` fails and `{\"count\": 1}` passes.\n- Use `t.Numeric`, `t.BooleanString`, or `t.ObjectString` for conversion beyond those defaults.\n\n### Query arrays and optional input\n\nAn array schema accepts comma-separated and repeated keys. Both `?name=ada,lin` and `?name=ada&name=lin` become `['ada', 'lin']` with `name: t.Array(t.String())`. Without an array schema, a comma-separated value stays a string and a repeated key keeps its last value.\n\n`t.Optional` can wrap a field or an entire request schema. `query: t.Optional(t.Object({ name: t.String() }))` permits an absent query; a nonempty query still needs `name`.\n\n### Extra properties\n\nHeader schemas allow undeclared headers by default. `additionalProperties: false` rejects them, including ordinary browser headers, so use it deliberately. Cookie schemas also allow extra names by default.\n\nTypeBox response schemas validate the result and, under the default normalization setting, remove undeclared fields. Standard Schema output follows the chosen validator's behavior. Do not depend on a database row's sensitive fields being safe without a defined output contract.\n\n## Responses by status\n\nReturn `status(code, body)` to preserve status-specific body types.\n\n```ts\nimport { Elysia, t } from 'elysia'\n\nnew Elysia()\n\t.get('/result', {\n\t\tresponse: {\n\t\t\t200: t.Object({ name: t.String() }),\n\t\t\t400: t.Object({ error: t.String() })\n\t\t}\n\t}, ({ query, status }) => query.fail === 'true'\n\t\t? status(400, { error: 'Invalid request' })\n\t\t: { name: 'Ada' })\n```\n\n## Standard Schema\n\nUse compatible Standard Schema validators in any schema slot, alone or mixed with TypeBox on one route. Use each library's coercion and type helpers; Elysia's TypeBox coercion does not apply to other validators.\n\n```ts\nimport { Elysia } from 'elysia'\nimport { z } from 'zod'\n\nnew Elysia()\n\t.get('/users/:id', {\n\t\tparams: z.object({ id: z.coerce.number() })\n\t}, ({ params }) => params.id)\n```\n\nFor example, use `z.infer\u003ctypeof schema>`, Valibot's `InferOutput`, or ArkType's `typeof schema.infer`.\n\n## Files\n\n`t.File` options include `type`, `minSize`, and `maxSize`. `t.Files` also accepts `minItems` and `maxItems`. Sizes are bytes or strings suffixed with `k` or `m`, using powers of 1,024. Media types accept a string or array, including groups such as `'image'` or `'image/*'`.\n\nElysia 2 does not bundle a file detector. Register one before validating `type`; otherwise type-checked uploads fail validation.\n\n```ts\nimport { Elysia, t, setFileTypeDetector } from 'elysia'\nimport { fileTypeFromBlob } from 'file-type'\n\nsetFileTypeDetector(fileTypeFromBlob)\n\nnew Elysia()\n\t.post('/upload', {\n\t\tbody: t.Object({\n\t\t\tfile: t.File({ type: ['image/png', 'image/jpeg'], maxSize: '5m' })\n\t\t})\n\t}, ({ body }) => ({ name: body.file.name }))\n```\n\nFor Standard Schema uploads, set `parse: 'formdata'` and validate file content with Elysia's async `fileType()` helper. `file.type` alone is client-supplied metadata. This example includes detector setup:\n\n```ts\nimport { Elysia, fileType, setFileTypeDetector } from 'elysia'\nimport { fileTypeFromBlob } from 'file-type'\nimport { z } from 'zod'\n\nsetFileTypeDetector(fileTypeFromBlob)\n\nnew Elysia()\n\t.post('/upload', {\n\t\tparse: 'formdata',\n\t\tbody: z.object({\n\t\t\tfile: z.file().refine((file) => fileType(file, 'image/jpeg'), 'Expected JPEG')\n\t\t})\n\t}, ({ body }) => ({ name: body.file.name }))\n```\n\nFor raw binary uploads, pair `parse: 'arrayBuffer'` with `body: t.Uint8Array()` or `body: t.ArrayBuffer()`.\n\n## Shared schemas and guards\n\nA route schema replaces the inherited guard schema for the same slot by default; object fields do not merge. `schema: 'merge'` accumulates constraints so both schemas must pass. Guard scope and examples belong in [composition.md](composition.md).\n\n## Models and types\n\nShare schema variables directly. Register them with `.model()` for string references or named OpenAPI components, before the routes that use them.\n\n```ts\nimport { Elysia, t, type UnwrapSchema, type UnwrapModels } from 'elysia'\n\nconst User = t.Object({ id: t.String(), name: t.String() })\ntype User = UnwrapSchema\u003ctypeof User>\n\nconst ByIdGet = { Params: t.Object({ id: t.String() }), Response: User }\ntype ByIdGet = UnwrapModels\u003ctypeof ByIdGet>\n\nnew Elysia()\n\t.model({ 'user.profile': User })\n\t.post('/profile', { body: 'user.profile', response: 'user.profile' }, ({ body }) => body)\n```\n\n`ByIdGet.Params` is the schema; `ByIdGet['Params']` is its TypeScript type. See [project structure](project-structure.md) for a feature model convention.\n\n## Type performance\n\nFor slow inference, run `tsc --generateTrace trace --noEmit --incremental false` and inspect `trace/trace.json` in Perfetto. If [Eden](eden.md) needs only a sub-app, export that sub-app's type and preserve its URL prefix in the client.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7753,"content_sha256":"4b79bfde09aa988ea37e35705b31fbca86cc9493ef8c0ac5d2f83f07637fa63a"},{"filename":"references/versions.md","content":"# Versions and verification\n\n## Select a version\n\n- Read the manifest and lockfile. Keep the project's package manager, resolved versions, runtime, and TypeScript settings.\n- These references describe v2. Do not apply v2 signatures to v1 or upgrade an existing project without a migration request.\n- For another beta, check its installed exports and types. Prefer that release's source and tests over conflicting docs.\n- For a new project at this baseline, pin the versions below. Avoid `@next` in a reproducible setup. Plugin beta numbers are independent of the framework's beta number.\n- Enable TypeScript `strict`. With Eden, resolve the same `elysia` version on the client and server.\n\n## Baseline\n\nReviewed on 2026-10-17 against the local Elysia checkout declaring `2.0.0-beta.32`. Its base commit is `c91f61f245d2c381946ac198ca6a2f393bc8af99` in `elysiajs/elysia`, with uncommitted source changes present. The commit alone does not reproduce the reviewed working tree; an installed or published package may differ.\n\nThis review used source, tests, and v2 docs under `interdoc/src/content`. Using the skill requires neither sibling checkout.\n\nThese versions come from local repositories. They do not prove release availability or end-to-end integration compatibility. Before installing, check availability and peer requirements.\n\n| Package | Baseline |\n| --- | --- |\n| `elysia` | `2.0.0-beta.32` |\n| `@elysia/eden` | `2.0.0-beta.7` |\n| `@elysia/openapi` | `2.0.0-beta.6` |\n| `@elysia/node` | `2.0.0-beta.3` |\n| `@elysia/static` | `2.0.0-beta.3` |\n| `@elysia/cors` | `2.0.0-beta.2` |\n| `@elysia/opentelemetry` | `2.0.0-beta.2` |\n| `@elysia/bearer`, `@elysia/cron`, `@elysia/apollo`, `@elysia/graphql-yoga`, `@elysia/html`, `@elysia/jwt`, `@elysia/server-timing` | `2.0.0-beta.1` |\n\nInstall only what the task needs. New plugin packages use `@elysia/*`. Check peer requirements before mixing releases. Baseline cron requires Elysia beta.32 or newer; CORS, static, OpenTelemetry, and server-timing require at least beta.30.\n\n## Peer dependencies\n\nElysia v2 uses `typebox` 1.x, not `@sinclair/typebox` 0.x. Baseline peers include `typebox >= 1.3.0`, `exact-mirror >= 1.2.6`, and `openapi-types >= 12.0.0`. Install these explicitly if automatic peer installation is disabled. TypeScript must be at least 5.7; Bun types must be at least 1.3 when used.\n\nFor a new Bun project at this baseline:\n\n```sh\nbun add [email protected]\nbun add -d typescript @types/bun\n```\n\nCommit the lockfile. See [runtimes](runtimes.md) for entrypoints and scripts.\n\n## Evidence and limits\n\nCore guidance follows source signatures, implementation, and focused regression tests. Executable checks cover the minimal route, type preservation, and selected lifecycle and error behavior. They do not verify every third-party version or host.\n\nIntegration references list adapters and known constraints. Check installed exports and test the integration before shipping. Renaming imports does not make v1 schema adapters v2-compatible.\n\nUpdate the baseline and install commands together. Re-run [testing checks](testing.md) when signatures or examples change.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":3131,"content_sha256":"8285e4c6ba8d7c068fe84db048568011ba59260e7128f262ea01c842d3821126"},{"filename":"references/websocket.md","content":"# WebSocket\n\nRegister `websocket()` from `elysia/websocket` before `.ws()` routes. Use a standard WebSocket client; Socket.IO uses another protocol. Support depends on the [adapter](runtimes.md).\n\n## Typed messages\n\n```ts\nimport { Elysia, t } from 'elysia'\nimport { websocket } from 'elysia/websocket'\n\nnew Elysia()\n\t.use(websocket())\n\t.ws('/chat', {\n\t\tquery: t.Object({ room: t.String() }),\n\t\tbody: t.Object({ message: t.String() }),\n\t\tresponse: t.String()\n\t}, function* ({ query, body }) {\n\t\tyield `Room: ${query.room}`\n\t\tyield body.message\n\t})\n\t.listen(3000)\n```\n\nConnect to `ws://localhost:3000/chat?room=general` and send `{\"message\":\"hello\"}`. Each yield sends a separate message. Returns and yields preserve inferred response types; `ws.send()` also works.\n\n| Schema | Validates |\n| --- | --- |\n| `body` | Each incoming message |\n| `response` | Returned, yielded, or sent messages |\n| `query`, `params`, `headers`, `cookie` | Connection upgrade request |\n\nDefault parsing recognizes JSON strings, arrays, objects, booleans, `null`, and safely convertible numeric strings. Other text remains text and binary remains binary.\n\n## Route forms and context\n\nSupported forms are `.ws(path, handler)`, `.ws(path, options, handler)`, and `.ws(path, optionsWithMessage)`. Do not set `options.message` when supplying a separate handler.\n\nSockets expose `body`, `query`, `params`, and derived values directly. Use `ws.query`, not v1 `ws.data.query`. Members include `send`, `publish`, `subscribe`, `close`, `id`, `remoteAddress`, and `readyState`.\n\n## Lifecycle\n\n| Option | Behavior |\n| --- | --- |\n| `parse(ws, message)` | Runs after default message parsing and before validation; return a replacement or `undefined` to keep the value |\n| `transform` | Runs before validation on upgrade and again for messages |\n| `beforeHandle` | Runs on upgrade and before message handlers; return a status to reject the upgrade |\n| `upgrade` | Handshake response headers, as an object or function of the upgrade context |\n| `open(ws)` | Connection opened |\n| `message(ws, message)` | Validated message, also available as `ws.body` |\n| `drain(ws)` | Backpressure cleared |\n| `close(ws, code, reason)` | Connection closed |\n\nAuthentication belongs in `beforeHandle`. Avoid assuming `transform` or `beforeHandle` runs only once per connection. Per-message `parse` is distinct from an HTTP body parser. The v1 `transformMessage` option is replaced by `parse`.\n\nUse this complete callback-form example:\n\n```ts\nimport { Elysia, t } from 'elysia'\nimport { websocket } from 'elysia/websocket'\n\nnew Elysia()\n\t.use(websocket())\n\t.ws('/echo', {\n\t\tbody: t.String(),\n\t\tresponse: t.String(),\n\t\tparse(_ws, message) {\n\t\t\tif (typeof message === 'string') return message.trim()\n\t\t},\n\t\tmessage(_ws, message) {\n\t\t\treturn message.toUpperCase()\n\t\t}\n\t})\n\t.listen(3000)\n```\n\n## Server configuration\n\nSet server defaults through `websocket({ idleTimeout: 30 })`, not the old constructor `websocket` option. Routes can override WebSocket tuning. Limits and defaults depend on the adapter. Bun options include:\n\n| Option | Meaning |\n| --- | --- |\n| `perMessageDeflate` | Compression negotiation |\n| `maxPayloadLength` | Maximum incoming message bytes |\n| `idleTimeout` | Idle timeout in seconds |\n| `backpressureLimit` | Maximum buffered bytes per connection |\n| `closeOnBackpressureLimit` | Close when that buffer limit is reached |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":3389,"content_sha256":"023deb2d976b594b4ed76b5977be9b12ed5d1a543ad54e1f36cf3efc0ea26722"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Elysia v2","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Start here","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Inspect ","type":"text"},{"text":"package.json","type":"text","marks":[{"type":"code_inline"}]},{"text":", the lockfile, runtime, and app structure. Keep existing versions and conventions unless the user requests a migration.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"This skill describes ","type":"text"},{"text":"2.0.0-rc.0","type":"text","marks":[{"type":"strong"}]},{"text":" or newer. For v1, use ","type":"text"},{"text":"migration","type":"text","marks":[{"type":"link","attrs":{"href":"references/migration.md","title":null}}]},{"text":" to recognize differences, not as permission to upgrade. Check installed types when a different beta disagrees.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read only the references the task needs. ","type":"text"},{"text":"Versions","type":"text","marks":[{"type":"link","attrs":{"href":"references/versions.md","title":null}}]},{"text":" lists the source revision, package baselines, and verification limits.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check types and exercise the affected route, including its rejection path. See ","type":"text"},{"text":"testing","type":"text","marks":[{"type":"link","attrs":{"href":"references/testing.md","title":null}}]},{"text":".","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Essential rules","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Options precede the handler.","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":".get(path, options, handler)","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":".get(path, handler)","type":"text","marks":[{"type":"code_inline"}]},{"text":", and ","type":"text"},{"text":".get(path, staticValue)","type":"text","marks":[{"type":"code_inline"}]},{"text":" are valid.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Keep the returned type.","type":"text","marks":[{"type":"strong"}]},{"text":" Chain calls or bind the result to a new variable. Reassigning ","type":"text"},{"text":"let app = new Elysia()","type":"text","marks":[{"type":"code_inline"}]},{"text":" does not widen its inferred type.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Order hooks before routes.","type":"text","marks":[{"type":"strong"}]},{"text":" Parent hooks before ","type":"text"},{"text":".use(child)","type":"text","marks":[{"type":"code_inline"}]},{"text":" reach the child's routes. ","type":"text"},{"text":"onRequest","type":"text","marks":[{"type":"code_inline"}]},{"text":" runs before routing and is the exception to registration order. See ","type":"text"},{"text":"lifecycle","type":"text","marks":[{"type":"link","attrs":{"href":"references/lifecycle.md","title":null}}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Declare typed dependencies.","type":"text","marks":[{"type":"strong"}]},{"text":" Each independently defined instance should ","type":"text"},{"text":".use()","type":"text","marks":[{"type":"code_inline"}]},{"text":" its providers. Parent runtime inheritance does not give a child those static types. See ","type":"text"},{"text":"composition","type":"text","marks":[{"type":"link","attrs":{"href":"references/composition.md","title":null}}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Name shared plugins.","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"name","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"seed","type":"text","marks":[{"type":"code_inline"}]},{"text":" deduplicate hooks. Plugin factories still execute on each call.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Return expected failures.","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"status()","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"problem()","type":"text","marks":[{"type":"code_inline"}]},{"text":" send ordinary responses. A returned ","type":"text"},{"text":"Error","type":"text","marks":[{"type":"code_inline"}]},{"text":" enters error hooks and contributes to the route's return type. Thrown errors are absent from that inferred type. Explicit error details can remain public in production. See ","type":"text"},{"text":"errors","type":"text","marks":[{"type":"link","attrs":{"href":"references/errors.md","title":null}}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Infer types from schemas.","type":"text","marks":[{"type":"strong"}]},{"text":" Use ","type":"text"},{"text":"UnwrapSchema","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"UnwrapModels","type":"text","marks":[{"type":"code_inline"}]},{"text":". TypeBox 1.x has no schema ","type":"text"},{"text":".static","type":"text","marks":[{"type":"code_inline"}]},{"text":". Keep handlers inline and pass plain values to services.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Choose context lifetime.","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"decorate","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"state","type":"text","marks":[{"type":"code_inline"}]},{"text":" are shared. ","type":"text"},{"text":"derive","type":"text","marks":[{"type":"code_inline"}]},{"text":" runs per request after validation, in the ","type":"text"},{"text":"beforeHandle","type":"text","marks":[{"type":"code_inline"}]},{"text":" queue.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Guard schemas override by default.","type":"text","marks":[{"type":"strong"}]},{"text":" A route replaces the corresponding inherited schema. Use ","type":"text"},{"text":"schema: 'merge'","type":"text","marks":[{"type":"code_inline"}]},{"text":" on the guard when both must apply.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Register optional capabilities.","type":"text","marks":[{"type":"strong"}]},{"text":" File MIME validation needs a detector. WebSockets need ","type":"text"},{"text":"elysia/websocket","type":"text","marks":[{"type":"code_inline"}]},{"text":". Node needs ","type":"text"},{"text":"@elysia/node","type":"text","marks":[{"type":"code_inline"}]},{"text":" and Node 22.18 or newer.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Always consult with an actual documentation","type":"text","marks":[{"type":"strong"}]},{"text":" for the latest features, examples, and references. The skill is a guide to the documentation, not a replacement.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"For Elysia 1 use: https://elysiajs.com/llms.txt For Elysia 2 use: https://next.elysiajs.com/llms.txt","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Minimal app and test","type":"text"}]},{"type":"paragraph","content":[{"text":"Use the project's test runner. This Bun example opens no port.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"ts"},"content":[{"text":"// src/app.ts\nimport { Elysia, t } from 'elysia'\n\nexport const app = new Elysia()\n\t.get('/users/:id', {\n\t\tparams: t.Object({ id: t.Number() })\n\t}, ({ params }) => ({ id: params.id }))\n\nexport type App = typeof app","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"ts"},"content":[{"text":"// test/app.test.ts\nimport { expect, test } from 'bun:test'\nimport { app } from '../src/app'\n\ntest('coerces the id and rejects nonnumeric input', async () => {\n\tconst response = await app.handle('/users/42')\n\texpect(response.status).toBe(200)\n\texpect(await response.json()).toEqual({ id: 42 })\n\texpect((await app.handle('/users/nope')).status).toBe(422)\n})","type":"text"}]},{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"bun test","type":"text","marks":[{"type":"code_inline"}]},{"text":" and the project's type check. For new projects, see ","type":"text"},{"text":"runtime setup","type":"text","marks":[{"type":"link","attrs":{"href":"references/runtimes.md","title":null}}]},{"text":". Keep ","type":"text"},{"text":".listen()","type":"text","marks":[{"type":"code_inline"}]},{"text":" in the process entry. See ","type":"text"},{"text":"project structure","type":"text","marks":[{"type":"link","attrs":{"href":"references/project-structure.md","title":null}}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Choose a reference","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Task","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Read","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Versions, peer dependencies, source versus docs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Versions","type":"text","marks":[{"type":"link","attrs":{"href":"references/versions.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Configuration, routing, context, files, streams","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Core API","type":"text","marks":[{"type":"link","attrs":{"href":"references/api.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Hooks, parsing, derived context, disposal","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Lifecycle","type":"text","marks":[{"type":"link","attrs":{"href":"references/lifecycle.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Plugins, hook scope, guards, macros","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Composition","type":"text","marks":[{"type":"link","attrs":{"href":"references/composition.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Schemas, coercion, uploads, inferred types","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Validation","type":"text","marks":[{"type":"link","attrs":{"href":"references/validation.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Expected errors, exception mapping, production responses","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Errors","type":"text","marks":[{"type":"link","attrs":{"href":"references/errors.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Folders, services, models, dependency injection","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Project structure","type":"text","marks":[{"type":"link","attrs":{"href":"references/project-structure.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cookie values, validation, signing","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Cookies","type":"text","marks":[{"type":"link","attrs":{"href":"references/cookie.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"WebSocket routes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"WebSockets","type":"text","marks":[{"type":"link","attrs":{"href":"references/websocket.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Route tests, type checks, lazy plugins","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Testing","type":"text","marks":[{"type":"link","attrs":{"href":"references/testing.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Typed client calls","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Eden","type":"text","marks":[{"type":"link","attrs":{"href":"references/eden.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OpenAPI schemas and documentation","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OpenAPI","type":"text","marks":[{"type":"link","attrs":{"href":"references/openapi.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bun, Node, Deno, Workers, frontend framework integration","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Runtimes","type":"text","marks":[{"type":"link","attrs":{"href":"references/runtimes.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Bundles, AOT, binaries, Docker, deployment","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Deployment","type":"text","marks":[{"type":"link","attrs":{"href":"references/deploy.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Traces, spans, request timing","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Observability","type":"text","marks":[{"type":"link","attrs":{"href":"references/observability.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"CORS, JWT, static files, cron, HTML, GraphQL","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Official plugins","type":"text","marks":[{"type":"link","attrs":{"href":"references/plugins.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Databases and external libraries","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Drizzle","type":"text","marks":[{"type":"link","attrs":{"href":"references/drizzle.md","title":null}}]},{"text":", ","type":"text"},{"text":"Prisma","type":"text","marks":[{"type":"link","attrs":{"href":"references/prisma.md","title":null}}]},{"text":", ","type":"text"},{"text":"Better Auth","type":"text","marks":[{"type":"link","attrs":{"href":"references/better-auth.md","title":null}}]},{"text":", ","type":"text"},{"text":"AI SDK","type":"text","marks":[{"type":"link","attrs":{"href":"references/ai-sdk.md","title":null}}]},{"text":", ","type":"text"},{"text":"React Email","type":"text","marks":[{"type":"link","attrs":{"href":"references/react-email.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"v1 syntax and migration","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Migration","type":"text","marks":[{"type":"link","attrs":{"href":"references/migration.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Find an example by pattern","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pattern index","type":"text","marks":[{"type":"link","attrs":{"href":"references/cheat-sheet.md","title":null}}]}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Defaults for a new project","type":"text"}]},{"type":"paragraph","content":[{"text":"Default to Bun and ","type":"text"},{"text":"t","type":"text","marks":[{"type":"code_inline"}]},{"text":" schemas. Keep small apps in one file; split by feature as needed. Add Eden for typed clients and OpenAPI for API documentation.","type":"text"}]}]},"metadata":{"date":"2026-11-02","name":"elysiajs","author":"@skillopedia","source":{"stars":70,"repo_name":"skills","origin_url":"https://github.com/elysiajs/skills/blob/HEAD/elysia/SKILL.md","repo_owner":"elysiajs","body_sha256":"b96c53efb2edbe63b7f34675e499ba165311d992f24dd3bbe8ac40a79fec411b","cluster_key":"fd9aa03dea406142705702c7ed243136753b548d6e8bfdcd34b1355d1b7bf899","clean_bundle":{"format":"clean-skill-bundle-v1","source":"elysiajs/skills/elysia/SKILL.md","text_bytes":129733,"source_commit":"1466d85aa4f2a0fec91f71311e65bbfa91aa7dc5","skills_sh_rank":1790,"attachment_count":24,"text_attachments":24,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"elysia/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"web-development","category_label":"Web"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"web-development","import_tag":"skills-sh-top20k-2026-11","description":"Build, debug, or migrate Elysia v2 TypeScript backends with validation, plugins, typed clients, and deployment. Use for Elysia projects or explicit Elysia requests."}},"renderedAt":1793661660108}

Elysia v2 Start here 1. Inspect , the lockfile, runtime, and app structure. Keep existing versions and conventions unless the user requests a migration. 2. This skill describes 2.0.0-rc.0 or newer. For v1, use migration to recognize differences, not as permission to upgrade. Check installed types when a different beta disagrees. 3. Read only the references the task needs. Versions lists the source revision, package baselines, and verification limits. 4. Check types and exercise the affected route, including its rejection path. See testing. Essential rules - Options precede the handler. , , an…