Generate typed SvelteKit remote functions (query, command, form) directly from OpenAPI specs. Point it at your API spec and get ready-to-use server functions with full type safety.
api.d.ts)The generated functions use SvelteKit's query(), command(), and form() from $app/server, wired up to your API through openapi-fetch.
npm install sveltekit-openapi-remote
# or
pnpm add sveltekit-openapi-remote
These are required in your SvelteKit project:
npm install openapi-fetch zod
# openapi-typescript is only needed if using --spec (optional)
npm install -D openapi-typescript
// src/lib/api/client.ts
import createClient from 'openapi-fetch';
export const client = createClient({
baseUrl: 'https://api.example.com',
});
// src/lib/api/remote.ts
import { createRemoteHandlers } from 'sveltekit-openapi-remote';
import { client } from './client';
export const {
handleGetQuery,
handlePostCommand,
handlePatchCommand,
handlePutCommand,
handleDeleteCommand,
handlePostForm,
handlePatchForm,
handlePutForm,
handleDeleteForm,
} = createRemoteHandlers(client);
# From an OpenAPI spec URL
npx sveltekit-openapi-remote generate \
--spec https://petstore3.swagger.io/api/v3/openapi.json \
--output src/lib/remote/generated \
--client '$lib/api/remote'
# From a local spec file (JSON or YAML)
npx sveltekit-openapi-remote generate \
--spec ./openapi.yaml \
--output src/lib/remote/generated \
--client '$lib/api/remote'
# From an existing api.d.ts (skip openapi-typescript step)
npx sveltekit-openapi-remote generate \
--types-path src/lib/types/api.d.ts \
--output src/lib/remote/generated \
--client '$lib/api/remote'
<!-- src/routes/pets/+page.svelte -->
<script>
import { getPetFindByStatus } from '$lib/remote/generated/pet.remote';
let pets = $derived(getPetFindByStatus({ query: { status: 'available' } }));
</script>
npx sveltekit-openapi-remote generate [options]
| Option | Required | Description |
|---|---|---|
--spec <path|url> |
* | OpenAPI spec file or URL. Runs openapi-typescript to generate types. |
--types-path <path> |
* | Path to an existing api.d.ts file. Skips type generation. |
--output <dir> |
Yes | Output directory for generated files. |
--client <path> |
Yes | Import path to your initialized handlers file (e.g. $lib/api/remote). |
--grouping <mode> |
No | "segment" (default) or "single". Controls file output. |
--depth <n> |
No | Segment depth for grouping (default: 1). Only used with --grouping segment. |
--dry-run |
No | Preview what files would be generated without writing. |
--quiet |
No | Suppress all output except errors. Useful for CI. |
--help |
No | Show help text. |
* Must provide either --spec or --types-path, not both.
--spec vs --types-pathUse --spec when you want the CLI to handle everything — it runs openapi-typescript internally and writes api.d.ts to the output directory.
Use --types-path if you already generate your api.d.ts separately (e.g. as part of your build pipeline) and just want the remote function generation.
--grouping and --depthControls how generated functions are split across files.
--grouping segment --depth 1 (default) groups by the first URL segment:
src/lib/remote/generated/
├── api.d.ts
├── pet.remote.ts # /pet, /pet/{petId}, /pet/findByStatus, ...
├── store.remote.ts # /store/inventory, /store/order, ...
└── user.remote.ts # /user, /user/{username}, /user/login, ...
--grouping segment --depth 2 groups by the first two non-parameter segments:
src/lib/remote/generated/
├── api.d.ts
├── pet.remote.ts
├── pet-findByStatus.remote.ts
├── pet-findByTags.remote.ts
├── store-inventory.remote.ts
├── store-order.remote.ts
├── user.remote.ts
├── user-createWithList.remote.ts
├── user-login.remote.ts
└── user-logout.remote.ts
--grouping single puts everything in one file:
src/lib/remote/generated/
├── api.d.ts
└── api.remote.ts
--clientThe import path that generated files will use to import your handlers. This should match how your SvelteKit project resolves imports — typically a $lib alias:
--client '$lib/api/remote'
The generated files will contain:
import {
handleGetQuery,
handlePostCommand,
// ...
} from '$lib/api/remote';
For each endpoint in your OpenAPI spec, the CLI generates typed SvelteKit remote functions:
| HTTP Method | Has Path Params | Generated Functions |
|---|---|---|
| GET | any | query() |
| POST | no | command() + form() |
| POST | yes | command() + form() with { path, body } |
| PATCH | no | command() + form() |
| PATCH | yes | command() + form() with { path, body } |
| PUT | no | command() + form() |
| PUT | yes | command() + form() with { path, body } |
| DELETE | any | command() + form() |
get, post, put, patch, delete/store/order becomes StoreOrderBy{Param}: /pet/{petId} becomes PetByPetIdCommand and Form suffixesExamples:
| Endpoint | Generated name |
|---|---|
GET /pet/findByStatus |
getPetFindByStatus |
GET /pet/{petId} |
getPetByPetId |
POST /pet |
postPetCommand, postPetForm |
DELETE /store/order/{orderId} |
deleteStoreOrderByOrderIdCommand, deleteStoreOrderByOrderIdForm |
PUT /user/{username} |
putUserByUsernameCommand, putUserByUsernameForm |
For the Petstore API, the generated store.remote.ts looks like:
import { query, command, form } from '$app/server';
import { z } from 'zod';
import type { paths } from './api';
import { type GetParameters, type GetRequestBody } from 'sveltekit-openapi-remote';
import {
handleDeleteCommand,
handleDeleteForm,
handleGetQuery,
handlePostCommand,
handlePostForm,
} from '$lib/api/remote';
/**
* Auto-generated remote functions
* DO NOT EDIT - Run 'npx sveltekit-openapi-remote generate' to regenerate
*/
export const getStoreInventory = query(
z.custom<GetParameters<paths, '/store/inventory', 'get'>>(),
async (params) => handleGetQuery('/store/inventory', params)
);
export const postStoreOrderCommand = command(
z.custom<GetRequestBody<paths, '/store/order', 'post'>>(),
async (body) => handlePostCommand('/store/order', body)
);
export const postStoreOrderForm = form(
z.custom<GetRequestBody<paths, '/store/order', 'post'>>(),
async (body) => handlePostForm('/store/order', body)
);
export const getStoreOrderByOrderId = query(
z.custom<GetParameters<paths, '/store/order/{orderId}', 'get'>>(),
async (params) => handleGetQuery('/store/order/{orderId}', params)
);
export const deleteStoreOrderByOrderIdCommand = command(
z.custom<GetParameters<paths, '/store/order/{orderId}', 'delete'>>(),
async (params) => handleDeleteCommand('/store/order/{orderId}', params)
);
export const deleteStoreOrderByOrderIdForm = form(
z.custom<GetParameters<paths, '/store/order/{orderId}', 'delete'>>(),
async (params) => handleDeleteForm('/store/order/{orderId}', params)
);
Running the generator again will:
.remote.ts files (identified by the DO NOT EDIT header).remote.ts files you've addedAdd the generate command to your package.json scripts:
{
"scripts": {
"generate:api": "sveltekit-openapi-remote generate --spec https://api.example.com/openapi.json --output src/lib/remote/generated --client '$lib/api/remote'"
}
}
The runtime handlers throw SvelteKit errors automatically:
error(503)error(statusCode) with the error messageerror(404)--spec)MIT