Skip to content

Configuration

Maat separates environment input from application configuration. Read process values with env() while building the configuration maps, then use config() everywhere else:

final app = {'name': env('APP_NAME', 'Maat')};
final name = config('app.name');

This gives your application one stable configuration repository after boot.

Environment Files

A generated application loads .env before evaluating files in config/:

APP_NAME=Blog
APP_ENV=local
APP_DEBUG=true
APP_KEY=base64:...
APP_PORT=8000

Real process environment variables override values from .env. You can deploy the same application artifact to several environments without editing files.

Never commit .env. It contains secrets and local values. Commit .env.example with safe placeholders so every required key remains visible.

Read Environment Values

Use the helper that matches the type you need:

env('APP_NAME'); // String?
env('MISSING', 'fallback');
envBool('APP_DEBUG'); // bool
envInt('APP_PORT', 8000); // int

envBool() treats true, 1, yes, and on as true, ignoring case. Invalid or missing integers use the default passed to envInt().

Call these helpers in configuration files. Application classes should read the resolved value with config() instead of depending directly on the process environment.

Configuration Files

Configuration files export ordinary Dart maps:

config/app.dart
import 'package:maat/maat.dart';
final Map<String, dynamic> app = {
'name': env('APP_NAME', 'Maat'),
'env': env('APP_ENV', 'production'),
'debug': envBool('APP_DEBUG'),
'url': env('APP_URL', 'http://localhost:8000'),
'key': env('APP_KEY'),
};

config/config.dart collects every file under its top-level key:

import 'app.dart';
import 'database.dart';
final Map<String, dynamic> appConfig = {
'app': app,
'database': database,
};

Maat does not discover files through reflection. A configuration file becomes available only when it is added to appConfig.

Read Configuration

Use dot notation to read nested values:

config('app.name');
config('app.debug');
config('database.default');
config('services.stripe.key');
config('app.missing', 'fallback');

The helper returns dynamic, so cast at the boundary when a class requires a specific type:

final debug = config('app.debug', false) as bool;
final appName = config('app.name', 'Maat') as String;

The application also exposes its repository directly:

app.config.has('app.debug');
app.config.get('app.name');
app.config.set('app.name', 'Test Blog');

set() changes the in-memory repository only. It does not edit .env or a configuration file, which makes it useful for focused tests.

Add a Configuration File

Create config/services.dart:

import 'package:maat/maat.dart';
final Map<String, dynamic> services = {
'stripe': {'key': env('STRIPE_KEY')},
};

Then register it in config/config.dart:

import 'services.dart';
final Map<String, dynamic> appConfig = {
// Existing entries...
'services': services,
};

The value is now available through config('services.stripe.key').

Generate the Application Key

The installer generates APP_KEY automatically. Generate a new key manually when creating an environment from an existing checkout:

Terminal window
maat key:generate

Use --show to print a key without editing .env:

Terminal window
maat key:generate --show

Do not reuse production application keys in local or test environments.

Debug Mode

app.debug controls exception detail. In local development, a 500 response may include the exception message and stack trace. In production, disable debug mode so the same failure returns only a safe message:

{"message":"Server Error"}

See Error Handling for custom reporting and rendering.

Determine the Environment

The application reads APP_ENV, defaulting to production:

app.environment;

Maat also includes the environment in log lines, such as local.INFO or production.ERROR, so logs from different deployments remain distinguishable.