Relevant official examples: asset_loading.

Bevy has a flexible system for loading and managing your game assets asynchronously (in the background, without causing lag spikes in your game).

The data of your loaded assets is stored in Assets<T> resources.

Assets are tracked using handles. Handles are just lightweight IDs for specific assets.

Loading using AssetServer

To load assets from files, use the AssetServer resource.

struct UiFont(Handle<Font>);

fn load_ui_font(
    mut commands: Commands,
    server: Res<AssetServer>
) {
    let handle: Handle<Font> = server.load("font.ttf");

    // we can store the handle in a resource:
    //  - to prevent the asset from being unloaded
    //  - if we want to use it to access the asset later

This queues the asset loading to happen in the background. The asset will take some time to become available. You cannot access the actual data immediately in the same system, but you can use the handle.

You can spawn your 2D sprites, 3D models, and UI, using the handle, even before the asset has loaded. They will just "pop in" later, when the asset becomes ready.

Note that it is OK to call asset_server.load as many times as you want, even if the asset is currently loading, or already loaded. It will just provide you with the same handle. If the asset is unavailable, it will begin loading.

Creating your own assets

You can also add assets to Assets<T> manually.

This is useful if you want to create them using code (such as for procedural generation), or if you have gotten the data in some other way.

fn add_material(
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    let new_mat = StandardMaterial {
        base_color: Color::rgba(0.25, 0.50, 0.75, 1.0),
        unlit: true,



Bevy can detect when you modify your asset files and reload them live while the game is running. See this page for more info.


Handles are the typical way to refer to a particular asset. When you spawn things into your game, such as 2D sprites, 3D models, or UI, their respective components will need handles for the assets they use.

You could store your handles somewhere that is convenient for you (such as in resources).

If you don't have your handle stored anywhere, you can always generate one from a path by calling asset_server.load. You could simply do that whenever you need, and not bother storing handles.

Accessing the Assets

To access the actual asset data from systems, use the Assets<T> resource.

You can identify your desired asset, using either the handle or the asset path:

struct SpriteSheets {
    map_tiles: Handle<TextureAtlas>,

fn use_sprites(
    handles: Res<SpriteSheets>,
    atlases: Res<Assets<TextureAtlas>>,
    textures: Res<Assets<Texture>>,
) {
    // Could be `None` if the asset isn't loaded yet
    if let Some(atlas) = atlases.get(&handles.map_tiles) {
        // do something with the texture atlas

    // Can use a path instead of a handle
    if let Some(map_tex) = textures.get("map.png") {
        // if "map.png" was loaded, we can use it!

AssetPath and Labels

Assets from the filesystem can be identified by an AssetPath, which consists of the file path + a label. Labels are used in situations where multiple assets are contained in the same file. An example of this are GLTF files, which can contain meshes, scenes, textures, materials, etc.

Asset paths can be created from a string, with the label (if any) attached after a # symbol.

fn load_gltf_things(
    mut commands: Commands,
    server: Res<AssetServer>
) {
    // get a specific mesh
    let my_mesh: Handle<Mesh> = server.load("my_scene.gltf#Mesh0/Primitive0");

    // spawn a whole scene
    let my_scene: Handle<Scene> = server.load("my_scene.gltf#Scene0");

See the GLTF page for more info about working with 3D models.

Handles and Asset Lifetime (Garbage Collection)

Handles have built-in reference counting (similar to Rc/Arc in Rust). This allows Bevy to track if an asset is still needed, and automatically unload it when it no longer is.

You can use .clone() to create multiple handles to the same asset. The clone is a cheap operation, but it is explicit, to ensure that you are aware of the places in your code that create additional handles and may affect the lifetime of assets.

Weak Handles

Handles can be "strong" (the default) or "weak". Only strong handles are counted and cause the asset to remain loaded. Weak handles let you refer to an asset, while allowing it to be garbage-collected when no more strong handles remain.

You can create weak handles using .clone_weak() instead of .clone().

Untyped Handles

Bevy also has a HandleUntyped type. Use this type of handle if you need to be able to refer to any asset, regardless of the asset type.

This allows you to store a collection (such as Vec or HashMap) containing assets of mixed types.

You can create an untyped handle using .clone_untyped().

Untyped Loading

Conveniently, the AssetServer supports untyped loading, if you don't know what asset type the files are. You can also load an entire folder:

struct ExtraAssets(Vec<HandleUntyped>);

fn load_extra_assets(
    mut commands: Commands,
    server: Res<AssetServer>,
) {
    if let Ok(handles) = server.load_folder("extra") {

It will try to detect the format of each asset based on the file extension.


If you need to perform specific actions when an asset is created, modified, or removed, you can react to AssetEvents.

struct MyMapTexture {
    handle: Handle<Texture>,

fn fixup_textures(
    mut ev_asset: EventReader<AssetEvent<Texture>>,
    mut assets: ResMut<Assets<Texture>>,
    map_tex: Res<MyMapTexture>,
) {
    for ev in ev_asset.iter() {
        match ev {
            AssetEvent::Created { handle } |
            AssetEvent::Modified { handle } => {
                // a texture was just loaded or changed!

                let texture = assets.get_mut(handle).unwrap();
                // ^ unwrap is OK, because we know it is loaded now

                if *handle == map_tex.handle {
                    // it is our special map texture!
                } else {
                    // it is some other texture
            AssetEvent::Removed { handle } => {
                // a texture was unloaded