diff --git a/Cargo.lock b/Cargo.lock
index 71b562b66b7f692c404827ce0d0122f2da8390a7..8c4ca699098826f1ac6f1285850b8dbb689e4782 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1567,7 +1567,7 @@ dependencies = [
 
 [[package]]
 name = "micro_ldtk"
-version = "0.6.0"
+version = "0.6.1"
 dependencies = [
  "anyhow",
  "bevy",
diff --git a/src/assets/asset_events.rs b/src/assets/asset_events.rs
index 03cde4d1f565c6259da52e90960235af515e8671..c7acc8ae2a88c5a5baa2a2b54c17083e6165a3dc 100644
--- a/src/assets/asset_events.rs
+++ b/src/assets/asset_events.rs
@@ -3,7 +3,7 @@ use std::collections::HashMap;
 use bevy::prelude::*;
 
 use crate::assets::{LevelIndex, TileMetadata, TilesetIndex};
-use crate::ldtk::Project;
+use crate::ldtk::{Level, Project};
 use crate::{LdtkLevel, LevelDataUpdated};
 
 pub fn handle_ldtk_project_events(
@@ -18,9 +18,11 @@ pub fn handle_ldtk_project_events(
 			AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
 				if let Some(project) = assets.get(handle) {
 					for level in project.get_all_levels() {
-						level_index
-							.insert(level.identifier.clone(), LdtkLevel::from(level.clone()));
-						update_events.send(LevelDataUpdated(level.identifier.clone()));
+						if level.external_rel_path.is_none() {
+							level_index
+								.insert(level.identifier.clone(), LdtkLevel::from(level.clone()));
+							update_events.send(LevelDataUpdated(level.identifier.clone()));
+						}
 					}
 
 					for tileset in &project.defs.tilesets {
@@ -39,3 +41,22 @@ pub fn handle_ldtk_project_events(
 		}
 	}
 }
+
+pub fn handle_ldtk_level_events(
+	mut events: EventReader<AssetEvent<Level>>,
+	assets: Res<Assets<Level>>,
+	mut level_index: ResMut<LevelIndex>,
+	mut update_events: EventWriter<LevelDataUpdated>,
+) {
+	for event in events.iter() {
+		match event {
+			AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
+				if let Some(level) = assets.get(handle) {
+					level_index.insert(level.identifier.clone(), LdtkLevel::from(level.clone()));
+					update_events.send(LevelDataUpdated(level.identifier.clone()));
+				}
+			}
+			_ => {}
+		}
+	}
+}
diff --git a/src/ldtk/mod.rs b/src/ldtk/mod.rs
index 51cc19d870b13ba80a3bd011b64b329777bb23c4..8d0cbafa8ae9dccdf82c7eade64f1181b9694528 100644
--- a/src/ldtk/mod.rs
+++ b/src/ldtk/mod.rs
@@ -15,7 +15,7 @@ mod data_1_2_5;
 #[cfg(feature = "ldtk_1_3_0")]
 mod data_1_3_0;
 
-use bevy::asset::{AssetLoader, BoxedFuture, LoadContext, LoadedAsset};
+use bevy::asset::{AssetLoader, AssetPath, BoxedFuture, LoadContext, LoadedAsset};
 use bevy::reflect::{TypePath, TypeUuid, Uuid};
 #[cfg(feature = "ldtk_1_0_0")]
 pub use data_1_0_0::*;
@@ -33,6 +33,7 @@ pub use data_1_2_4::*;
 pub use data_1_2_5::*;
 #[cfg(feature = "ldtk_1_3_0")]
 pub use data_1_3_0::*;
+use serde::Deserialize;
 
 #[derive(thiserror::Error, Debug)]
 pub enum ParseError {
@@ -40,6 +41,42 @@ pub enum ParseError {
 	SerdeError(String),
 }
 
+pub trait LdtkFromBytes<'a>: Deserialize<'a> {
+	fn from_bytes(bytes: &'a [u8]) -> Result<Self, ParseError> {
+		serde_json::from_slice(bytes).map_err(|e| ParseError::SerdeError(format!("{}", e)))
+	}
+}
+
+macro_rules! impl_from_bytes {
+	($type: tt) => {
+		impl<'a> From<&'a [u8]> for $type {
+			fn from(value: &'a [u8]) -> Self {
+				#[cfg(feature = "no_panic")]
+				{
+					match $type::from_bytes(value) {
+						Ok(val) => val,
+						Err(e) => {
+							log::error!("{}", e);
+							std::process::abort();
+						}
+					}
+				}
+
+				#[cfg(not(feature = "no_panic"))]
+				{
+					$type::from_bytes(value).expect("Failed to parse ldtk file")
+				}
+			}
+		}
+	};
+}
+
+impl<'a> LdtkFromBytes<'a> for Level {}
+impl<'a> LdtkFromBytes<'a> for Project {}
+
+impl_from_bytes!(Level);
+impl_from_bytes!(Project);
+
 impl TypeUuid for Project {
 	const TYPE_UUID: Uuid = Uuid::from_u128(87988914102923589138720617793417023455);
 }
@@ -53,11 +90,21 @@ impl TypePath for Project {
 	}
 }
 
-impl Project {
-	pub fn from_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
-		serde_json::from_slice(bytes).map_err(|e| ParseError::SerdeError(format!("{}", e)))
+impl TypeUuid for Level {
+	const TYPE_UUID: Uuid = Uuid::from_u128(18486863672600588966868281477384349187);
+}
+
+impl TypePath for Level {
+	fn type_path() -> &'static str {
+		"micro_ld0tk::ldtk::Level"
 	}
 
+	fn short_type_path() -> &'static str {
+		"Level"
+	}
+}
+
+impl Project {
 	pub fn get_all_levels(&self) -> Vec<&Level> {
 		if !self.worlds.is_empty() {
 			self.worlds
@@ -99,26 +146,6 @@ impl Project {
 
 pub type LdtkProject = Project;
 
-impl<'a> From<&'a [u8]> for Project {
-	fn from(value: &'a [u8]) -> Self {
-		#[cfg(feature = "no_panic")]
-		{
-			match Project::from_bytes(value) {
-				Ok(val) => val,
-				Err(e) => {
-					log::error!("{}", e);
-					std::process::abort();
-				}
-			}
-		}
-
-		#[cfg(not(feature = "no_panic"))]
-		{
-			Project::from_bytes(value).expect("Failed to parse ldtk project file")
-		}
-	}
-}
-
 #[derive(Default)]
 pub struct LdtkLoader;
 impl AssetLoader for LdtkLoader {
@@ -128,7 +155,27 @@ impl AssetLoader for LdtkLoader {
 		load_context: &'a mut LoadContext,
 	) -> BoxedFuture<'a, anyhow::Result<(), anyhow::Error>> {
 		Box::pin(async move {
-			load_context.set_default_asset(LoadedAsset::new(Project::from_bytes(bytes)?));
+			let project = Project::from_bytes(bytes)?;
+
+			let sub_levels = project
+				.levels
+				.iter()
+				.flat_map(|level| {
+					level
+						.external_rel_path
+						.as_ref()
+						.map(|rel_path| (level.identifier.clone(), rel_path.clone()))
+				})
+				.collect::<Vec<(String, String)>>();
+
+			let asset = LoadedAsset::new(project).with_dependencies(
+				sub_levels
+					.into_iter()
+					.map(|(id, path)| AssetPath::new(path.into(), Some(id)))
+					.collect(),
+			);
+
+			load_context.set_default_asset(asset);
 			Ok(())
 		})
 	}
@@ -137,6 +184,24 @@ impl AssetLoader for LdtkLoader {
 		&["ldtk"]
 	}
 }
+#[derive(Default)]
+pub struct LdtkLevelLoader;
+impl AssetLoader for LdtkLevelLoader {
+	fn load<'a>(
+		&'a self,
+		bytes: &'a [u8],
+		load_context: &'a mut LoadContext,
+	) -> BoxedFuture<'a, anyhow::Result<(), anyhow::Error>> {
+		Box::pin(async move {
+			load_context.set_default_asset(LoadedAsset::new(Level::from_bytes(bytes)?));
+			Ok(())
+		})
+	}
+
+	fn extensions(&self) -> &[&str] {
+		&["ldtkl"]
+	}
+}
 
 #[cfg(feature = "autotile")]
 mod autotile_support {
@@ -215,7 +280,7 @@ mod autotile_support {
 
 #[cfg(test)]
 mod test {
-	use crate::ldtk::Project;
+	use crate::ldtk::{LdtkFromBytes, Project};
 
 	#[cfg_attr(feature = "ldtk_1_2_5", test)]
 	pub fn load_project() {
diff --git a/src/lib.rs b/src/lib.rs
index 6acb9e7173f8dc667a89e838a82e9bd3b59d7cc3..fb70b85650b46937e8237c264c147516a10240ae 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -75,10 +75,13 @@ mod __plugin {
 			{
 				app.add_event::<super::system::LevelDataUpdated>()
 					.add_asset::<super::ldtk::Project>()
+					.add_asset::<super::ldtk::Level>()
 					.add_asset_loader(super::ldtk::LdtkLoader)
+					.add_asset_loader(super::ldtk::LdtkLevelLoader)
 					.init_resource::<super::assets::TilesetIndex>()
 					.init_resource::<super::assets::LevelIndex>()
-					.add_systems(Update, super::assets::handle_ldtk_project_events);
+					.add_systems(Update, super::assets::handle_ldtk_project_events)
+					.add_systems(Update, super::assets::handle_ldtk_level_events);
 			}
 		}
 	}