# Bevy Skill Research: Plugin Architecture > **Bevy version**: 0.18, with 0.19 notes > **Date**: 2026-07-07 ## Plugin Trait (`bevy::app::Plugin`) **Required**: - `fn build(&self, app: &mut App)` — runs immediately on `add_plugins` **Optional (provided defaults)**: - `fn ready(&self, _app: &App) -> bool` — default `true`; override for async init - `fn finish(&self, _app: &mut App)` — runs after all plugins report `ready()`, before first schedule tick; use for setup that depends on other plugins' async work - `fn cleanup(&self, _app: &mut App)` — runs after `finish()`, before schedule; remove temp resources - `fn name(&self) -> &str` — defaults to type name; used for uniqueness check - `fn is_unique(&self) -> bool` — default `true`; set `false` for multi-instance plugins Trait bounds: `Downcast + Any + Send + Sync`. Any `fn(&mut App)` auto-implements `Plugin`. ## Plugin Dependencies No formal "depends-on" declaration. Patterns: - `app.is_plugin_added::() -> bool` to guard or panic - Sub-plugins: call `app.add_plugins(ChildPlugin)` inside `build()`. Bevy deduplicates by name (unique plugins panic on double-add) - `finish()` is the idiomatic place for cross-plugin setup that needs another plugin's resources to exist ## Plugin Groups `DefaultPlugins` / `MinimalPlugins` implement `PluginGroup`. Customization via `PluginGroupBuilder` fluent API: - `.disable::()` — skip a plugin - `.set(plugin)` — replace a plugin's config - `.add_before::(plugin)` / `.add_after::(plugin)` — ordering - `.enable::()` — re-enable disabled ```rust DefaultPlugins.disable::().set(WindowPlugin { ... }) ``` ## App Builder Key Methods | Method | Purpose | |--------|---------| | `add_plugins(P)` | Accepts plugin, plugin group, or tuple | | `insert_resource(R)` | Insert (overwrites existing) | | `init_resource::()` | Insert via `Default`/`FromWorld` if absent | | `add_systems(schedule, systems)` | Add to a named schedule | | `configure_sets(schedule, sets)` | Configure system set ordering | | `register_type::()` | Register in `AppTypeRegistry` (reflection) | | `is_plugin_added::()` | Check if plugin was already added | | `register_required_components::()` | Component requirements (0.15+) | ## States (`bevy::state`) Three flavors: - **FreelyMutableState**: Changed via `NextState` resource - **SubStates**: Removed from world when parent state isn't in required variant - **ComputedStates**: Derived automatically via `fn compute(sources) -> Option` Registration: - `app.init_state::()` — register with default initial - `app.insert_state(value)` — register with explicit initial Transition schedules: `OnEnter(S)`, `OnExit(S)`, `OnTransition(S)` Entity scoping: `DespawnOnEnter(S)`, `DespawnOnExit(S)` (replaced `StateScoped` in 0.16+) Run condition: `in_state(S) -> impl Condition` (in `bevy::state::condition`) **0.18 change**: `State::set()` now always triggers `OnEnter`/`OnExit` even for same-state transitions. Use `set_if_neq()` for old behavior. ## Schedules (Built-in Labels) | Phase | Schedules | |-------|-----------| | Startup | `PreStartup`, `Startup`, `PostStartup` (run once) | | Per-frame | `First`, `PreUpdate`, `Update`, `PostUpdate`, `Last` | | Fixed timestep | `FixedFirst`, `FixedPreUpdate`, `FixedUpdate`, `FixedPostUpdate`, `FixedLast` (inside `FixedMain`, driven by `RunFixedMainLoop`) | | Other | `Main`, `SpawnScene` (new) | ## Run Conditions (`bevy::ecs::schedule::common_conditions`) Key functions: - `in_state(s)` — state matching - `resource_exists::()`, `resource_added::()`, `resource_changed::()`, `resource_equals(val)` - `any_with_component::()`, `any_component_removed::()` - `run_once()`, `not(cond)` - `on_message::()` (renamed from `on_event` in 0.18) - `condition_changed(cond)`, `condition_changed_to(cond)` Combinators: `.and(cond)`, `.or(cond)` on any condition.