diff --git a/README.md b/README.md
index 1bee3c3..ed73763 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
 * more filters
 * despawn black characters
 * despawn character when too far
-* level design
+* more levels
 * (?) multiplayer
 * more audio
 * "jumpable" component to avoid jumping on sensors
@@ -56,6 +56,7 @@ Edit the level `N: u32` with the command `bevyjam <N> e`.
 * **Select**: left click to select, click in void to deselect, CTRL+click to select many, CTRL+A to select all
 * **Move selection**: arrows to move one step, Shift+arrows to move continuously
 * **Delete selection**: delete
+* **Add objects**: P=platform, C=character, A=absorbing filter, R=rotating filter
 * **Move camera**: CTRL+arrows
 * **Save**: CTRL+S
 
diff --git a/src/editor.rs b/src/editor.rs
index 8474c1d..28dd89d 100644
--- a/src/editor.rs
+++ b/src/editor.rs
@@ -23,7 +23,8 @@ impl Plugin for EditorPlugin {
 					.with_system(move_system)
 					.with_system(input_control_system)
 					.with_system(follow_ends_system)
-					.with_system(remove_system),
+					.with_system(remove_system)
+					.with_system(spawn_system),
 			);
 	}
 }
@@ -620,6 +621,68 @@ fn remove_system(
 	}
 }
 
+fn spawn_system(
+	mut commands: Commands,
+	mut meshes: ResMut<Assets<Mesh>>,
+	mut materials: ResMut<Assets<ColorMaterial>>,
+	asset_server: Res<AssetServer>,
+	camera_query: Query<&Transform, With<Camera>>,
+	keyboard_input: Res<Input<KeyCode>>,
+	mut character_list: ResMut<CharacterList>,
+) {
+	if keyboard_input.pressed(KeyCode::LControl)
+		|| keyboard_input.pressed(KeyCode::RControl)
+		|| keyboard_input.pressed(KeyCode::LAlt)
+		|| keyboard_input.pressed(KeyCode::RAlt)
+	{
+		return;
+	}
+
+	let camera_pos = camera_query.single().translation;
+
+	if keyboard_input.just_released(KeyCode::P) {
+		spawn_platform(
+			&mut commands,
+			&mut meshes,
+			&mut materials,
+			Transform::from_xyz(camera_pos.x, camera_pos.y, 0.),
+			Vec2 { x: 96., y: 16. },
+		);
+	}
+	if keyboard_input.just_released(KeyCode::C) {
+		let index = character_list.0.len();
+		character_list.0.push(spawn_character(
+			&mut commands,
+			&mut meshes,
+			&mut materials,
+			&asset_server,
+			Transform::from_xyz(camera_pos.x, camera_pos.y, 0.),
+			Color::RED,
+			index,
+		));
+	}
+	if keyboard_input.just_released(KeyCode::A) {
+		spawn_absorbing_filter(
+			&mut commands,
+			&mut meshes,
+			&mut materials,
+			Transform::from_xyz(camera_pos.x, camera_pos.y, 0.),
+			Vec2 { x: 16., y: 96. },
+			Color::RED,
+		);
+	}
+	if keyboard_input.just_released(KeyCode::R) {
+		spawn_rotating_filter(
+			&mut commands,
+			&mut meshes,
+			&mut materials,
+			&asset_server,
+			Transform::from_xyz(camera_pos.x, camera_pos.y, 0.),
+			90.,
+		);
+	}
+}
+
 fn follow_ends_system(
 	mut meshes: ResMut<Assets<Mesh>>,
 	mut follower_query: Query<(&mut Transform, &mut Mesh2dHandle, &mut Size, &Ends)>,