Installation
On this page
Introduction#
magic_starter is a starter kit for the Magic Framework that provides auth screens, profile management, team support, notifications, and more — all behind 14 opt-in feature toggles. It follows the same ServiceProvider + config pattern used throughout the framework, so it wires up in exactly the same way as every other Magic plugin.
Under the hood the package provides 7 controllers, overridable views via a string-keyed view registry, 9 Gate abilities for section visibility, and Wind UI-based layouts. Every feature defaults to false — you enable only what your app needs.
Requirements#
- Dart SDK: 3.13.0 or higher
- Flutter: 3.47.0 or higher
- Magic Framework installed and bootstrapped (
lib/config/app.dartpresent)
Installing the Package#
Add magic_starter to your Flutter project:
flutter pub add magic_starter
Or add it manually to pubspec.yaml:
dependencies:
magic_starter: ^1.0.0
Then fetch dependencies:
flutter pub get
Running the Install Command#
Magic Starter ships its own CLI command. Run it from your project root to scaffold all required files and inject the provider automatically:
dart run magic_starter:install
The command performs the following steps:
- Validates that
lib/config/app.dartexists (Magic must be installed first). - Prompts for which features to enable (interactive mode).
- Creates
lib/config/magic_starter.dartwith all 14 feature toggles set to your selections. - Injects
MagicStarterServiceProviderinto theproviderslist inlib/config/app.dart. - Injects
() => magicStarterConfiginto theconfigFactorieslist inlib/main.dart. - Creates middleware files (
EnsureAuthenticated,RedirectIfAuthenticated). - Injects middleware aliases into
lib/config/kernel.dart. - Injects starter route registrations into
RouteServiceProvider. - Scaffolds
AppServiceProvider,Usermodel,Dashboardview, app routes, and translation files. - Scaffolds
Teammodel (only when the teams feature is enabled). - Runs
dart format .on the host project.
dart run magic_starter:install --force
For CI pipelines or scripted installs, use non-interactive mode with a comma-separated list of features:
dart run magic_starter:install --non-interactive --features teams,registration,notifications
Registering the Service Provider#
If you ran dart run magic_starter:install, the provider was already injected. The relevant section of lib/config/app.dart will look like this:
import 'package:magic/magic.dart';
import 'package:magic_starter/magic_starter.dart'; // injected by install
final appConfig = {
'app': {
'name': Env.get('APP_NAME', 'My App'),
'providers': [
(app) => RouteServiceProvider(app),
(app) => AppServiceProvider(app),
(app) => MagicStarterServiceProvider(app), // injected by install
],
},
};
Injecting the Config Factory#
The install command also adds the config factory to Magic.init() in lib/main.dart:
import 'package:material_ui/material_ui.dart';
import 'package:magic/magic.dart';
import 'config/app.dart';
import 'config/magic_starter.dart'; // injected by install
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Magic.init(
configFactories: [
() => appConfig,
() => magicStarterConfig, // injected by install
],
);
runApp(MagicApplication(title: 'My App'));
}
Bootstrapping the Starter#
Registering the provider is not enough. The starter also needs to know four host-owned things: how to build your user model, how to log out, which locales to offer, and (when the teams feature is enabled) how to read and switch teams. Register all of them with a single required call, MagicStarter.bootstrap(), from your AppServiceProvider.boot():
class AppServiceProvider extends ServiceProvider {
AppServiceProvider(super.app);
@override
Future<void> boot() async {
MagicStarter.bootstrap(
userFactory: (data) => User.fromMap(data),
onLogout: () async {
await Auth.logout();
MagicRoute.to('/auth/login');
},
locales: {
'en': 'English',
'tr': 'Türkçe',
},
);
}
}
That is the complete setup for a teamless app. When magic_starter.features.teams is true, pass the three team callbacks as well:
MagicStarter.bootstrap(
userFactory: (data) => User.fromMap(data),
onLogout: () async => Auth.logout(),
locales: {
'en': 'English',
'tr': 'Türkçe',
},
currentTeam: () => User.current.currentTeam?.toMagicStarterTeam(),
allTeams: () =>
User.current.allTeams.map((t) => t.toMagicStarterTeam()).toList(),
onSwitch: (id) => MagicStarterTeamController.instance.switchTeam(id),
);
The team callbacks are a cohesive group: pass all three or none.
| Situation | Result |
|---|---|
| Teams disabled, team callbacks omitted | Valid. No team resolver is registered and no team UI renders. |
| Teams enabled, all three callbacks passed | Valid. The team selector, team switching, and team settings work. |
| Teams enabled, team callbacks omitted | Throws a StateError naming the missing call and the config flag. |
| Only some of the three callbacks passed | Throws an ArgumentError; the manager is left untouched. |
The individual setters remain available for partial or advanced setup, and any of them can be called after bootstrap() to override what it registered:
// Register the team resolver later, once your own team store is warm.
MagicStarter.useTeamResolver(
currentTeam: () => TeamStore.instance.current?.toMagicStarterTeam(),
allTeams: () => TeamStore.instance.all,
onSwitch: (id) => TeamStore.instance.switchTo(id),
);
| Setter | Covered by bootstrap() |
|---|---|
MagicStarter.useUserModel() |
Yes, as the required userFactory argument. |
MagicStarter.useLogout() |
Yes, as the required onLogout argument. |
MagicStarter.useLocaleOptions() |
Yes, as the required locales argument. |
MagicStarter.useTeamResolver() |
Yes, as the optional currentTeam / allTeams / onSwitch group. |
Configuration Reference#
The generated lib/config/magic_starter.dart looks like this out of the box (with all features defaulting to false):
// Magic Starter Configuration.
//
// This file is auto-generated by the magic_starter install command.
// Customize feature toggles and route prefixes as needed.
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,
},
'auth': {
'email': true,
'phone': false,
},
'defaults': {
'locale': 'en',
'timezone': 'UTC',
},
'supported_locales': [
'en',
'tr',
],
'routes': {
'home': '/',
'login': '/auth/login',
'auth_prefix': '/auth',
'teams_prefix': '/teams',
'profile_prefix': '/settings',
'notifications_prefix': '/notifications',
},
'legal': {
'terms_url': null,
'privacy_url': null,
},
},
};
Next Steps#
Now that the plugin is installed and wired up:
- Configuration — Learn how to customise feature toggles, route prefixes, and auth identity modes.
- Views — Override any screen using the string-keyed view registry.
- Controllers — Understand the auth, profile, team, and notification controllers.