Configuration
Spry uses spry.config.dart as the single place where runtime behavior is selected. Route files describe your server surface. Config describes how that server should be built and run.
Minimal config
import 'package:spry/config.dart';
void main() {
defineSpryConfig(host: '127.0.0.1', port: 4000, target: BuildTarget.vm);
}This file writes JSON that the Spry CLI reads during spry serve and spry build.
API
The public entrypoint is defineSpryConfig(...):
import 'dart:convert';
import 'dart:io';
import 'src/openapi/config.dart';
export 'src/openapi/config.dart';
/// Supported deployment targets for a Spry application.
enum BuildTarget {
/// Runs the application on the Dart VM (dev/serve mode, no compilation).
vm,
/// Compiles the application to a native executable using `dart compile exe`.
exe,
/// Compiles the application to an AOT snapshot using `dart compile aot-snapshot`.
aot,
/// Compiles the application to a JIT snapshot using `dart compile jit-snapshot`.
jit,
/// Compiles the application to a kernel snapshot using `dart compile kernel`.
kernel,
/// Compiles the application for the Node.js runtime.
node,
/// Compiles the application for the Bun runtime.
bun,
/// Compiles the application for the Deno runtime.
deno,
/// Compiles the application for the Cloudflare Workers runtime.
cloudflare,
/// Compiles the application for the Vercel runtime.
vercel,
/// Compiles the application for the Netlify Functions runtime.
netlify,
}
/// Reload behavior used by `spry serve`.
enum ReloadStrategy {
/// Restarts the runtime process after a rebuild.
restart,
/// Keeps the runtime process alive when the target supports hotswap.
hotswap,
}
/// Emits Spry build configuration as JSON for `spry.config.dart`.
void defineSpryConfig({
/// Overrides the host used by `spry serve`.
String? host,
/// Overrides the port used by `spry serve`.
int? port,
/// Selects the target runtime.
BuildTarget? target,
/// Overrides the routes directory.
String? routesDir,
/// Overrides the middleware directory.
String? middlewareDir,
/// Overrides the public asset directory.
String? publicDir,
/// Overrides the generated output directory.
String? outputDir,
/// Overrides the reload strategy used by `spry serve`.
ReloadStrategy? reload,
/// Overrides the Wrangler config path for Cloudflare targets.
String? wranglerConfig,
/// Enables OpenAPI document generation.
OpenAPIConfig? openapi,
}) {
final config = <String, dynamic>{
'host': ?host,
'port': ?port,
'target': ?target?.name,
'routesDir': ?routesDir,
'middlewareDir': ?middlewareDir,
'publicDir': ?publicDir,
'outputDir': ?outputDir,
'reload': ?reload?.name,
'wranglerConfig': ?wranglerConfig,
'openapi': ?openapi,
};
stdout.writeln(json.encode(config));
}Core options
target
Selects which runtime Spry should emit for.
Available values:
BuildTarget.vm— Dart VM, no compilationBuildTarget.exe— native executable (dart compile exe)BuildTarget.aot— AOT snapshotBuildTarget.jit— JIT snapshotBuildTarget.kernel— kernel snapshotBuildTarget.node— Node.jsBuildTarget.bun— BunBuildTarget.deno— DenoBuildTarget.cloudflare— Cloudflare WorkersBuildTarget.vercel— VercelBuildTarget.netlify— Netlify Functions
This is the most important config field. Everything else should usually stay runtime-agnostic.
host
Overrides the hostname used by spry serve.
defineSpryConfig(
host: '127.0.0.1',
target: BuildTarget.vm,
);port
Overrides the local port used by spry serve.
defineSpryConfig(
port: 4000,
target: BuildTarget.vm,
);reload
Controls how spry serve reloads during development.
Available values:
ReloadStrategy.restartReloadStrategy.hotswap
Use hotswap for targets such as Cloudflare Workers where keeping the runtime model aligned with the platform is useful.
Project layout options
routesDir
Changes the directory that Spry scans for route files.
defineSpryConfig(
routesDir: 'server/routes',
target: BuildTarget.node,
);middlewareDir
Changes the directory used for global middleware files.
defineSpryConfig(
middlewareDir: 'server/middleware',
target: BuildTarget.node,
);publicDir
Changes the static asset root. Spry checks this directory before it falls through to route handlers for GET and HEAD requests.
defineSpryConfig(
publicDir: 'static',
target: BuildTarget.vm,
);Build output options
outputDir
Controls where Spry writes generated output.
defineSpryConfig(
outputDir: 'dist/server',
target: BuildTarget.node,
);By default, Spry emits generated files into .spry/.
wranglerConfig
Lets Cloudflare targets point at a custom Wrangler config file.
defineSpryConfig(
target: BuildTarget.cloudflare,
wranglerConfig: 'deploy/wrangler.toml',
reload: ReloadStrategy.hotswap,
);OpenAPI
Spry can generate an OpenAPI 3.1 document during spry build and spry serve.
Configuration lives in defineSpryConfig(...), but the document objects are imported from package:spry/openapi.dart.
For the full guide, including route-level metadata, merge strategy, and webhooks, see OpenAPI.
Minimal openapi config
import 'package:spry/config.dart';
import 'package:spry/openapi.dart';
void main() {
defineSpryConfig(
openapi: OpenAPIConfig(
document: OpenAPIDocumentConfig(
info: OpenAPIInfo(
title: 'Spry API',
version: '1.0.0',
description: 'Public HTTP API',
),
components: OpenAPIComponents(
schemas: {
'User': OpenAPISchema.object({'id': OpenAPISchema.string()}),
},
pathItems: {
'UserCreatedWebhook': OpenAPIPathItem(
post: OpenAPIOperation(
responses: {
'202': OpenAPIRef.inline(
OpenAPIResponse(description: 'Accepted'),
),
},
),
),
},
),
webhooks: {
'userCreated': OpenAPIPathItem(
$ref: '#/components/pathItems/UserCreatedWebhook',
),
},
),
output: OpenAPIOutput.route('openapi.json'),
componentsMergeStrategy: OpenAPIComponentsMergeStrategy.strict,
),
);
}Important points:
OpenAPIConfig.documentis the document seed.pathsare still generated from the route tree.OpenAPIOutput.route('openapi.json')writes the output intopublic/.OpenAPIOutput.local(...)can write to another project-relative path.componentsMergeStrategydefaults tostrict.ui: Scalar()generates aGET /_docsroute serving an interactive Scalar API reference UI. Only active whenoutputisOpenAPIOutput.route(...).
Route-level metadata
Route files can expose a top-level openapi value:
import 'package:spry/openapi.dart';
import 'package:spry/spry.dart';
final openapi = OpenAPI(
summary: 'List users',
tags: ['users'],
responses: {
'200': OpenAPIRef.inline(
OpenAPIResponse(
description: 'OK',
content: {
'application/json': OpenAPIMediaType(
schema: OpenAPISchema.array(
OpenAPISchema.ref('#/components/schemas/User'),
),
),
},
),
),
},
);
Response handler(Event event) => Response.json([]);Spry resolves this value through the analyzer, so nested reusable top-level spec values can live in shared files and be re-exported through user barrels.
Route-level globalComponents
If a route needs to contribute shared schemas or callbacks to the final document, use globalComponents:
import 'package:spry/openapi.dart';
import 'package:spry/spry.dart';
final openapi = OpenAPI(
summary: 'Any-method user operation',
globalComponents: OpenAPIComponents(
schemas: {'UserId': OpenAPISchema.string()},
),
);
Response handler(Event event) => Response('any');These values are lifted into document-level components during generation. They are not kept as operation-local fields in the final openapi.json.
Method expansion rules
OpenAPI generation follows the Spry route model, with one deliberate exception for HEAD:
routes/users/[id].dartexpands toget,post,put,patch,delete, andoptions.- If the same path also has explicit method files such as
routes/users/[id].get.dart, the explicit operation wins for that method. HEADis only emitted when the route explicitly defines.head.dart.- Spry may still fall back from
HEADtoGETat runtime, but that fallback is not mirrored into OpenAPI.
Components merge strategy
Spry supports two merge modes when document-level components and lifted globalComponents collide:
OpenAPIComponentsMergeStrategy.strictidentical definitions are deduplicated; conflicting definitions fail the build.OpenAPIComponentsMergeStrategy.deepMergerecursively merges map-shaped component values; conflicting leaf values still fail the build.
Conflict errors include both the component key and the sources involved, so you can tell whether the conflict came from openapi.document.components or from a specific route file.
Recommended mental model
- Put routing concerns in files under
routes/. - Put shared request behavior in
middleware/and_middleware.dart. - Put runtime choice in
target. - Put local dev and output behavior in
spry.config.dart.
If a setting changes how requests are matched, it probably belongs in the file tree. If it changes how the generated app runs, it belongs in config.