diff --git a/patches/net/minecraft/server/level/ServerLevel.java.patch b/patches/net/minecraft/server/level/ServerLevel.java.patch
index 82396345cf..a6094dac99 100644
--- a/patches/net/minecraft/server/level/ServerLevel.java.patch
+++ b/patches/net/minecraft/server/level/ServerLevel.java.patch
@@ -9,6 +9,15 @@
      private final StructureManager structureManager;
      private final StructureCheck structureCheck;
      private final boolean tickTime;
+@@ -281,6 +_,8 @@
+         this.randomSequences = Objects.requireNonNullElseGet(
+             p_288977_, () -> this.getDataStorage().computeIfAbsent(RandomSequences.factory(i), "random_sequences")
+         );
++
++        net.neoforged.neoforge.attachment.LevelAttachmentsSavedData.init(this);
+     }
+ 
+     @Deprecated
 @@ -325,7 +_,7 @@
          if (this.sleepStatus.areEnoughSleeping(i) && this.sleepStatus.areEnoughDeepSleeping(i, this.players)) {
              if (this.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {
diff --git a/patches/net/minecraft/world/level/Level.java.patch b/patches/net/minecraft/world/level/Level.java.patch
index 063b4aae2c..0ec1792350 100644
--- a/patches/net/minecraft/world/level/Level.java.patch
+++ b/patches/net/minecraft/world/level/Level.java.patch
@@ -5,7 +5,7 @@
  import net.minecraft.world.scores.Scoreboard;
  
 -public abstract class Level implements LevelAccessor, AutoCloseable {
-+public abstract class Level implements LevelAccessor, AutoCloseable, net.neoforged.neoforge.common.extensions.ILevelExtension {
++public abstract class Level extends net.neoforged.neoforge.attachment.AttachmentHolder implements LevelAccessor, AutoCloseable, net.neoforged.neoforge.common.extensions.ILevelExtension {
      public static final Codec<ResourceKey<Level>> RESOURCE_KEY_CODEC = ResourceKey.codec(Registries.DIMENSION);
      public static final ResourceKey<Level> OVERWORLD = ResourceKey.create(Registries.DIMENSION, new ResourceLocation("overworld"));
      public static final ResourceKey<Level> NETHER = ResourceKey.create(Registries.DIMENSION, new ResourceLocation("the_nether"));
diff --git a/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java b/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java
index 02669a0928..9590be6d42 100644
--- a/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java
+++ b/src/main/java/net/neoforged/neoforge/attachment/AttachmentType.java
@@ -12,6 +12,7 @@
 import net.minecraft.nbt.Tag;
 import net.minecraft.world.entity.Entity;
 import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.Level;
 import net.minecraft.world.level.block.entity.BlockEntity;
 import net.minecraft.world.level.chunk.LevelChunk;
 import net.neoforged.neoforge.common.util.INBTSerializable;
@@ -38,13 +39,16 @@
  * <li>Serializable item stack attachments are copied when an item stack is copied.</li>
  * <li>Serializable item stack attachments must match for item stack comparison to succeed.</li>
  * </ul>
+ * <h3>{@link Level}-exclusive behavior:</h3>
+ * <ul>
+ * <li>(nothing)</li>
+ * </ul>
  * <h3>{@link LevelChunk}-exclusive behavior:</h3>
  * <ul>
  * <li>Modifications to attachments should be followed by a call to {@link LevelChunk#setUnsaved(boolean)}.</li>
  * </ul>
  */
 // TODO Future work: maybe add copy handlers for stacks and entities, to customize copying behavior (instead of serializing, copying the NBT, deserializing).
-// TODO Future work: maybe add custom comparison handlers for item stacks.
 public final class AttachmentType<T> {
     final Supplier<T> defaultValueSupplier;
     @Nullable
diff --git a/src/main/java/net/neoforged/neoforge/attachment/LevelAttachmentsSavedData.java b/src/main/java/net/neoforged/neoforge/attachment/LevelAttachmentsSavedData.java
new file mode 100644
index 0000000000..bba9a56595
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/attachment/LevelAttachmentsSavedData.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.attachment;
+
+import java.util.Objects;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.level.saveddata.SavedData;
+import org.jetbrains.annotations.ApiStatus;
+
+@ApiStatus.Internal
+public class LevelAttachmentsSavedData extends SavedData {
+    private static final String NAME = "neoforge_data_attachments";
+
+    public static void init(ServerLevel level) {
+        var factory = new SavedData.Factory<>(
+                () -> new LevelAttachmentsSavedData(level),
+                tag -> new LevelAttachmentsSavedData(level, tag));
+        // Querying the attachment a single time is enough to initialize it,
+        // and make sure it gets saved when the level is saved.
+        level.getDataStorage().computeIfAbsent(factory, NAME);
+    }
+
+    private final ServerLevel level;
+
+    public LevelAttachmentsSavedData(ServerLevel level) {
+        this.level = level;
+    }
+
+    public LevelAttachmentsSavedData(ServerLevel level, CompoundTag tag) {
+        this.level = level;
+        level.deserializeAttachments(tag);
+    }
+
+    @Override
+    public CompoundTag save(CompoundTag tag) {
+        // Make sure we don't return null
+        return Objects.requireNonNullElseGet(level.serializeAttachments(), CompoundTag::new);
+    }
+
+    @Override
+    public boolean isDirty() {
+        // Always re-save
+        return true;
+    }
+}