Skip to content

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 ​

dart
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(...):

dart
import 'dart:convert';
import 'dart:io';

import 'src/client/config.dart';
import 'src/mcp/config.dart';
import 'src/openapi/config.dart';

export 'package:ht/ht.dart' show Headers;
export 'src/client/config.dart';
export 'src/mcp/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,

  /// Controls whether generated routing is case-sensitive.
  bool? caseSensitive,

  /// Sets the LRU cache capacity used for generated handler lookups.
  int? handlerCacheCapacity,

  /// 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,

  /// Enables Spry client generation.
  ClientConfig? client,

  /// Enables the local MCP server for AI tool inspection.
  ///
  /// When enabled, `spry serve` starts an MCP HTTP endpoint alongside
  /// the dev server so AI agents can inspect the project in real time.
  McpConfig? mcp,
}) {
  final config = <String, dynamic>{
    'host': ?host,
    'port': ?port,
    'target': ?target?.name,
    'routesDir': ?routesDir,
    'middlewareDir': ?middlewareDir,
    'publicDir': ?publicDir,
    'outputDir': ?outputDir,
    'caseSensitive': ?caseSensitive,
    'handlerCacheCapacity': ?handlerCacheCapacity,
    'reload': ?reload?.name,
    'wranglerConfig': ?wranglerConfig,
    'openapi': ?openapi,
    'client': ?client,
    'mcp': ?mcp,
  };

  stdout.writeln(json.encode(config));
}

Core options ​

target ​

Selects which runtime Spry should emit for.

Available values:

  • BuildTarget.vm — Dart VM, no compilation
  • BuildTarget.exe — native executable (dart compile exe)
  • BuildTarget.aot — AOT snapshot
  • BuildTarget.jit — JIT snapshot
  • BuildTarget.kernel — kernel snapshot
  • BuildTarget.node — Node.js
  • BuildTarget.bun — Bun
  • BuildTarget.deno — Deno
  • BuildTarget.cloudflare — Cloudflare Workers
  • BuildTarget.vercel — Vercel
  • BuildTarget.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.

dart
defineSpryConfig(
  host: '127.0.0.1',
  target: BuildTarget.vm,
);

port ​

Overrides the local port used by spry serve.

dart
defineSpryConfig(
  port: 4000,
  target: BuildTarget.vm,
);

caseSensitive ​

Controls whether the generated Spry(...) app matches routes case-sensitively.

dart
defineSpryConfig(
  caseSensitive: false,
  target: BuildTarget.vm,
);

Default: true

handlerCacheCapacity ​

Enables roux LRU caching for generated handler lookups.

dart
defineSpryConfig(
  handlerCacheCapacity: 64,
  target: BuildTarget.vm,
);

Important points:

  • Only handler route matching uses this cache.
  • Middleware and error handler collection are unaffected.
  • Leave it unset to disable caching.

reload ​

Controls how spry serve reloads during development.

Available values:

  • ReloadStrategy.restart
  • ReloadStrategy.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.

dart
defineSpryConfig(
  routesDir: 'server/routes',
  target: BuildTarget.node,
);

middlewareDir ​

Changes the directory used for global middleware files.

dart
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.

dart
defineSpryConfig(
  publicDir: 'static',
  target: BuildTarget.vm,
);

Build output options ​

outputDir ​

Controls where Spry writes generated output.

dart
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.

dart
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 ​

dart
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.document is the document seed. paths are still generated from the route tree.
  • OpenAPIOutput.route('openapi.json') writes the output into public/.
  • OpenAPIOutput.local(...) can write to another project-relative path.
  • componentsMergeStrategy defaults to strict.
  • ui: Scalar() generates a GET /_docs route serving an interactive Scalar API reference UI. Only active when output is OpenAPIOutput.route(...).

Route-level metadata ​

Route files can expose a top-level openapi value:

dart
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:

dart
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].dart expands to get, post, put, patch, delete, and options.
  • If the same path also has explicit method files such as routes/users/[id].get.dart, the explicit operation wins for that method.
  • HEAD is only emitted when the route explicitly defines .head.dart.
  • Spry may still fall back from HEAD to GET at 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.strict identical definitions are deduplicated; conflicting definitions fail the build.
  • OpenAPIComponentsMergeStrategy.deepMerge recursively 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.

  • 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.

Released under the MIT License.