Configuration
On this page
Introduction#
The magic_starter plugin is configured via a single Dart file at lib/config/magic_starter.dart in your consumer project. This file is generated automatically when you run the install command, and contains all feature toggles, route prefixes, auth identity settings, and locale defaults your app needs.
dart run magic_starter:install
The install command scaffolds lib/config/magic_starter.dart with all features defaulting to false. Enable only the features your app requires — each toggle controls route registration, UI visibility, and controller behaviour.
The Config File#
The generated lib/config/magic_starter.dart exports a single top-level getter that returns a nested Map<String, dynamic>:
Map<String, dynamic> get magicStarterConfig => {
'magic_starter': {
'features': {
'teams': false,
'registration': false,
'extended_profile': false,
'profile_photos': false,
'social_login': false,
'two_factor': false,
'sessions': false,
'phone_otp': false,
'newsletter': false,
'notifications': false,
'email_verification': false,
'guest_auth': false,
'timezones': false,
'billing': false,
'support': false,
'releases': false,
'app_update': false,
'handoff': false,
},
'auth': {
'email': true,
'phone': false,
},
'defaults': {
'locale': 'en',
'timezone': 'UTC',
},
'localization': {
'apply_user_locale': true,
},
'supported_locales': [
'en',
'tr',
],
'routes': {
'home': '/',
'login': '/auth/login',
'auth_prefix': '/auth',
'teams_prefix': '/teams',
'profile_prefix': '/settings',
'notifications_prefix': '/notifications',
'billing': '/teams/billing',
},
'billing': {
'web_origin': null,
},
'notifications': {
'external_id_prefix': 'user_',
},
'handoff': {
'confirm_without_session': true,
'signed_out_route': null,
},
'legal': {
'terms_url': null,
'privacy_url': null,
},
},
};
Register it with Magic.init() alongside your other config factories:
await Magic.init(
configFactories: [
() => appConfig,
() => magicStarterConfig,
],
);
Feature Toggles#
Every feature defaults to false and must be explicitly enabled. Each toggle controls whether routes are registered, UI sections are rendered, and controller logic is activated.
| Key | Default | Description |
|---|---|---|
teams |
false |
Team creation, management, member invitations, and team switching. |
registration |
false |
User registration screen and route. |
extended_profile |
false |
Additional profile fields: phone, timezone, and language preferences. |
profile_photos |
false |
Profile photo upload and removal in the profile settings. |
social_login |
false |
Social login: provider buttons on the login and register screens, the Connected accounts settings page, and the provider option of the identity-confirmation dialog. Needs a bridge registered with MagicStarter.useSocialAuth(...) (see Social Login); the flag alone renders nothing. |
two_factor |
false |
Two-factor authentication setup, challenge screen, and recovery codes. |
sessions |
false |
Browser sessions list with logout/revoke capability. |
phone_otp |
false |
Phone-based OTP authentication flow (send code, verify). |
newsletter |
false |
Newsletter subscribe/unsubscribe toggle in the profile. |
notifications |
false |
In-app notification list, unread badge, and preferences screen. |
email_verification |
false |
Email verification banner and resend verification flow. |
guest_auth |
false |
The login screen's "Continue as guest" button, plus the listeners that record a guest sign-in and claim that guest's data when an account signs in. It adds no routes or screens, and MagicStarterGuestAuthController works without it. |
timezones |
false |
Timezone selection with debounced async API search. |
support |
false |
Support desk screens (/support, /support/new, /support/:id), the Support profile-menu item and the Contact sales conversation. The host registers the routes with registerMagicStarterSupportRoutes(); see Support. |
releases |
false |
The "What's new" changelog page (/settings/changelog), its settings hub row and the MagicStarterAppVersion caption, read from the backend's GET releases. Needs Features::releases() on magic-starter-laravel; see Releases. |
app_update |
false |
MagicStarterAppUpdateBanner in the app layout: Reload when a new web deploy appears in version.json, Update when the store has a newer build than a store-installed release build. The store path reads GET releases and needs Features::releases() on the backend; see Releases. |
handoff |
false |
The sign-in handoff page /auth/handoff?code=&next=: trades a one-time code for a session, after asking "Sign in as ...?", and opens next. handoff.confirm_without_session (default true) keeps that question when nobody is signed in, which is what stops a link carrying an attacker's own code from signing a visitor in to the attacker's account; false only saves the tap. handoff.signed_out_route (default null, meaning routes.login) is where a signed-out visit without a code goes, carrying ?next=. See Sign-in handoff. |
To enable features, set their values to true in lib/config/magic_starter.dart:
'features': {
'teams': true,
'registration': true,
'notifications': true,
// ... remaining features stay false
},
Route Customization#
The routes block controls all URL paths registered by Magic Starter. Change these values to match your app's URL structure:
'routes': {
'home': '/',
'login': '/auth/login',
'auth_prefix': '/auth',
'teams_prefix': '/teams',
'profile_prefix': '/settings',
'notifications_prefix': '/notifications',
},
| Key | Default | Description |
|---|---|---|
home |
'/' |
Redirect target after successful login. |
login |
'/auth/login' |
Login screen path; used by RedirectIfAuthenticated middleware. |
auth_prefix |
'/auth' |
Prefix for all auth routes (login, register, forgot, reset, two-factor challenge). |
teams_prefix |
'/teams' |
Prefix for team routes (create, settings). |
profile_prefix |
'/settings' |
Prefix for profile routes (profile, notifications preferences). |
notifications_prefix |
'/notifications' |
Prefix for the notifications list page. |
The config class also provides computed route paths built from these prefixes:
MagicStarterConfig.teamCreateRoute(); // '/teams/create'
MagicStarterConfig.teamSettingsRoute(); // '/teams/settings'
MagicStarterConfig.profileRoute(); // '/settings/profile'
MagicStarterConfig.notificationsRoute(); // '/notifications'
MagicStarterConfig.notificationPreferencesRoute(); // '/settings/notifications'
MagicStarterConfig.twoFactorChallengeRoute(); // '/auth/two-factor-challenge'
Billing#
The billing feature gates the teams.billing view, which sells subscriptions through
magic_payments. It is off by default and it is gated on
its OWN toggle rather than on teams: a subscription is bought by whoever holds the account, so an
app with no team features can still sell one.
'features': {
'billing': true,
},
'routes': {
'billing': '/teams/billing',
},
'billing': {
'web_origin': 'https://app.example.com',
},
The origin is the host app's own web origin, with no trailing slash. It is only read on the web rail; a store purchase on iOS or Android never touches it.
The push external id#
With the notifications feature on, this package declares who the device is subscribed as every
time a session begins, and releases it on every sign-out, including the ones no call site performs
(account deletion, and a session that simply expires). You wire nothing.
What it declares is the prefix below followed by the authenticated user's id:
'notifications': {
'external_id_prefix': 'user_',
},
Change it when one OneSignal app serves more than one kind of subject, and change the backend with it.
dart run magic:dowel starter:doctor prints the prefix this config resolves to, so the two sides
can be read against each other:
Push external id prefix: user_ (default, key not set)
Push external id prefix: user_ (blank, using default)
Push external id prefix: operator-
The first two resolve to the same prefix and are not the same situation: the second means the value you wrote is being discarded, which "key not set" would hide.
It is printed rather than checked, and never fails: any prefix is valid as long as the backend uses
the same one, and nothing inside this repository can see the other side to compare. --verbose adds
the key name and the file to compare it against. The value comes from this config FILE, so an app
that writes the key at runtime through Config.set is not covered; the command boots nothing and
has no container to ask. Whole-line // comments are skipped, so a commented-out old key above the
live one does not win; a block comment around it, or a key written outside the notifications
block, is not covered.
Auth Identity Modes#
The auth block controls which identity fields are shown on login and registration forms:
'auth': {
'email': true,
'phone': false,
},
There are three supported modes:
Email only (default) — users authenticate with email and password:
'auth': {
'email': true,
'phone': false,
},
Phone only — users authenticate with phone number:
'auth': {
'email': false,
'phone': true,
},
Both — both fields are shown, but phone takes precedence in the login payload when both are provided:
'auth': {
'email': true,
'phone': true,
},
Access identity mode at runtime:
final emailEnabled = MagicStarterConfig.emailIdentity(); // true
final phoneEnabled = MagicStarterConfig.phoneIdentity(); // false
Locale & Timezone Defaults#
The defaults and supported_locales sections configure localisation behaviour:
'defaults': {
'locale': 'en',
'timezone': 'UTC',
},
'supported_locales': [
'en',
'tr',
],
| Key | Default | Description |
|---|---|---|
defaults.locale |
'en' |
Default locale for new users. |
defaults.timezone |
'UTC' |
Default timezone for new users. |
localization.apply_user_locale |
true |
Whether a signed-in user's saved locale attribute is applied to the running app automatically, on sign-in and on restore. Set false to own locale switching yourself. See Locale Application. |
supported_locales |
['en', 'tr'] |
Locales available in the language selector (when extended_profile is enabled). |
Access these values at runtime:
final locale = MagicStarterConfig.defaultLocale(); // 'en'
final timezone = MagicStarterConfig.defaultTimezone(); // 'UTC'
final locales = MagicStarterConfig.supportedLocales(); // ['en', 'tr']
final applies = MagicStarterConfig.applyUserLocale(); // true
The legal block provides optional links displayed on auth screens:
'legal': {
'terms_url': 'https://example.com/terms',
'privacy_url': 'https://example.com/privacy',
},
Accessing Values#
At runtime, all values are accessible in two ways.
Via MagicStarterConfig static methods — the recommended approach with type safety and defaults:
// Feature checks
final teamsEnabled = MagicStarterConfig.hasTeamFeatures();
final registrationEnabled = MagicStarterConfig.hasRegistrationFeatures();
final twoFactorEnabled = MagicStarterConfig.hasTwoFactorFeatures();
// Route paths
final loginPath = MagicStarterConfig.loginRoute();
final profilePath = MagicStarterConfig.profileRoute();
// Identity
final emailAuth = MagicStarterConfig.emailIdentity();
final phoneAuth = MagicStarterConfig.phoneIdentity();
// Defaults
final locale = MagicStarterConfig.defaultLocale();
final timezone = MagicStarterConfig.defaultTimezone();
// Legal
final termsUrl = MagicStarterConfig.termsUrl();
final hasLegal = MagicStarterConfig.hasLegalLinks();
Via Config.get() dot notation — for dynamic access or when you need the raw value:
final teamsEnabled = Config.get<bool>('magic_starter.features.teams', false);
final authPrefix = Config.get<String>('magic_starter.routes.auth_prefix', '/auth');
final termsUrl = Config.get<String?>('magic_starter.legal.terms_url', null);