Skip to content

Configuration

View raw .md

Magic's configuration system lets you load, read, write, and inspect application settings at runtime using the Config facade with dot-notation keys.

On this page

Introduction#

All of the configuration files for Magic are stored in the lib/config directory. Each option is documented, so feel free to look through the files and get familiar with the options available to you.

Magic uses a modular configuration system. The framework loads sensible defaults automatically for all services. You only need to create config files for values you wish to override.

These configuration files allow you to configure things like your API connection information, your cache stores, as well as various other core configuration values such as your application URL and log channels.

Environment Configuration#

It is often helpful to have different configuration values based on the environment the application is running in. For example, you may wish to use a different API endpoint locally than you do on your production server.

Magic uses .env files located in your project root. To set up environment-based configuration:

1. Create Your Environment File#

Create a .env file in your project root:

bash
APP_NAME="My Magic App"
APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost
APP_KEY=base64:your-generated-key

API_BASE_URL=https://api.example.com

2. Register as Flutter Asset#

Add the .env file to your pubspec.yaml:

yaml
flutter:
  assets:
    - .env

3. Access in Configuration#

Use the env() helper or Env.get() in your configuration files:

dart
Map<String, dynamic> get appConfig => {
  'app': {
    'name': env('APP_NAME', 'Magic App'),
    'debug': env('APP_DEBUG', false),
    'url': env('APP_URL', 'http://localhost'),
  }
};

Environment Variable Types#

All variables in your .env files are read as strings. The type argument T alone picks the conversion, whether or not you pass a default: env() and Env.get() convert only when T is exactly bool, int or double. When you omit T, it is inferred from the default, so env('APP_DEBUG', false) reads a bool and env('PORT', 3000) reads an int, while env('APP_URL') with no default reads dynamic and returns the raw string:

T .env value Result
bool true, 1 or yes (any case) true
bool any other value, including false, (true) and on false
int a string int.tryParse accepts the parsed int
int anything else the default
double a string double.tryParse accepts the parsed double
double anything else the default
String, dynamic, Object any value, including null and empty the raw string ('null', 'empty')
any T key absent, or .env not loaded the default

"The default" is cast to T, so a call that can fall back needs a default of that type: env<int>('PORT') with PORT absent or unparsable throws a TypeError, since null is not an int; write env<int>('PORT', 3000). Any other T gets the raw string cast to it, so it has to accept a String: env<num>('PORT', 3000) and env<int?>('PORT') throw when PORT is present (neither is exactly int), and so does env<bool?>('DEBUG').

Env.getBool(key) and Env.getInt(key) default to false and 0; Env.getString(key) defaults to ''.

KEY="" and KEY='' resolve to '', not to the env() default: an explicitly present but empty value is not the same as an absent key, matching Laravel's Env::get. A blank value reaching a browser tab title or a link as Monitor | "" or as a path with no origin is the failure mode this distinguishes; see Guarding Against a Blank Value for the helper that treats blank as absent instead.

Determining the Current Environment#

The current application environment is determined via the APP_ENV variable. You may access this value via the Config facade:

dart
final environment = Config.get('app.env');

if (environment == 'local') {
  // Enable debug features
}

Accessing Configuration Values#

You may easily access configuration values using the Config facade from anywhere in your application. Configuration values may be accessed using "dot" notation, which includes the name of the file and option you wish to access:

dart
// Get a value with a default fallback
final appName = Config.get('app.name', 'Magic App');

// Get a typed value
final timeout = Config.get<int>('network.drivers.api.timeout', 5000);

// Get a nested configuration
final guard = Config.get('auth.defaults.guard');

Checking If Configuration Exists#

dart
if (Config.has('services.stripe')) {
  initializeStripe();
}

Getting Required Values#

If a configuration value is required and must exist, use getOrFail:

dart
final apiKey = Config.getOrFail<String>('services.stripe.secret');
// Throws if not found

Runtime Configuration#

You may set configuration values at runtime:

dart
Config.set('app.locale', 'fr');
Config.set('app.timezone', 'Europe/Istanbul');

Merging Configuration#

You can deep-merge additional configuration at runtime:

dart
Config.merge({
  'app': {
    'name': 'My App (staging)',
    'debug': false,
  }
});

Retrieving All Values#

Use Config.all() to retrieve the entire configuration as a Map<String, dynamic>. This is useful for debugging or passing the full config snapshot to another service:

dart
final everything = Config.all();
print(everything['app']['name']); // 'My App'

Array Configuration#

When a configuration value holds a list, you can prepend or append items at runtime without reading and rewriting the whole list.

Prepending a Value#

Config.prepend() inserts a value at the beginning of an array key:

dart
// services.middleware is currently ['AuthMiddleware', 'LogMiddleware']
Config.prepend('services.middleware', 'TraceMiddleware');
// now: ['TraceMiddleware', 'AuthMiddleware', 'LogMiddleware']

Pushing a Value#

Config.push() appends a value to the end of an array key:

dart
Config.push('app.providers', (app) => AnalyticsServiceProvider(app));

Removing Configuration#

Forgetting a Key#

Config.forget() removes a single configuration key (and its nested values) at runtime:

dart
Config.forget('services.stripe');
// Config.has('services.stripe') is now false

Flushing All Configuration#

Config.flush() clears the entire in-memory configuration store. Use with care: any subsequent Config.get() call will return null (or the provided default) until configuration is reloaded:

dart
Config.flush();

Repository Access#

For advanced use cases you can obtain the underlying ConfigRepository instance directly via the repository getter. This is useful when passing the repository to a service that needs direct access without going through the facade:

dart
final repo = Config.repository;
final value = repo.get<String>('app.name');

Configuration Files#

Magic recommends organising your configuration into logical files:

File Purpose Config Key
app.dart General settings, service providers app
auth.dart Authentication guards and behaviour auth
network.dart API endpoints, timeouts, headers network
cache.dart Cache stores and TTL settings cache
logging.dart Log levels and channels logging
localization.dart Supported locales and defaults localization

Example: app.dart#

dart
import 'package:magic/magic.dart';

Map<String, dynamic> get appConfig => {
  'app': {
    'name': env('APP_NAME', 'My App'),
    'env': env('APP_ENV', 'production'),
    'debug': env('APP_DEBUG', false),
    'url': env('APP_URL', 'http://localhost'),
    'key': env('APP_KEY'),
    'providers': [
      (app) => CacheServiceProvider(app),
      (app) => RouteServiceProvider(app),
      (app) => NetworkServiceProvider(app),
      (app) => AuthServiceProvider(app),
    ],
  },
};

Example: network.dart#

dart
Map<String, dynamic> get networkConfig => {
  'network': {
    'default': 'api',
    'drivers': {
      'api': {
        'base_url': env('API_BASE_URL', 'http://localhost:8000/api/v1'),
        'timeout': 10000,
        'headers': {
          'Accept': 'application/json',
          'Content-Type': 'application/json',
        },
      },
    },
  },
};

Theme Persistence#

MagicApplication automatically persists the user's theme preference (dark or light) to secure storage via the Vault. This allows the application to remember the user's theme choice across restarts.

How It Works#

When the application starts, MagicApplication:

  1. Loads the saved theme preference from Vault (stored under the theme_mode key)
  2. Restores the saved preference ('dark' or 'light') if available
  3. Falls back to the system brightness if no preference was previously saved
  4. Gracefully defaults to system theme if VaultServiceProvider is not registered

When the user manually toggles the theme, the preference is automatically persisted to Vault. The onThemeChanged callback fires during theme toggles, allowing you to add extra side-effects like analytics tracking.

Example Usage#

dart
void main() async {
  await Magic.init(
    configFactories: [
      () => appConfig,
      () => authConfig,
    ],
  );

  runApp(
    MagicApplication(
      themeMode: ThemeMode.system,  // Follows system by default, or restores saved preference
      onThemeChanged: (brightness) {
        // Called when user manually toggles theme
        // Preference is auto-saved to Vault; use this for extra side-effects
        analytics.track('theme_changed', {
          'mode': brightness.name,
        });
      },
    ),
  );
}

Storage Details#

  • Storage Key: theme_mode
  • Storage Values: 'dark' or 'light'
  • Provider: Requires VaultServiceProvider to be registered in your app config
  • Fallback: If Vault is unavailable, the application gracefully uses system brightness

Configuration Caching#

Configuration is loaded during Magic.init() and cached in memory for the duration of the application lifecycle. To update configuration:

  1. Modify your config files
  2. Perform a hot restart (or restart the application) so Magic.init() runs again

Guarding Against a Blank Value#

Env.get/env() only fall back to the given default when the key is entirely missing; a key that is present but blank resolves to ''. Env.filled(key, fallback) treats an absent, blank, or quote-only value the same way, all resolving to fallback, and strips one wrapping pair of quotes plus surrounding whitespace from a present value (an inner apostrophe survives; an unbalanced quote is left alone):

dart
Env.filled('WEB_URL', 'https://app.example.com'); // falls back on '', '""', "''", or a missing key
Env.filled('APP_NAME', 'My App');

Use it for any value that becomes a URL, a title, or anything else a blank string silently corrupts rather than visibly breaks.

Env.getOrFail(key) mirrors Laravel's Env::getOrFail: it throws a StateError when key is absent entirely, and still returns '' for a key that is present but empty, matching Env.get's own Laravel-parity handling of an explicit empty value.

dart
final apiKey = Env.getOrFail('API_KEY'); // throws StateError when API_KEY is not set at all
esc

Search index did not load.

No match for "".

Try a class like rounded-lg or a widget like WButton.