diff --git a/paper-api/src/main/java/org/bukkit/block/BlockState.java b/paper-api/src/main/java/org/bukkit/block/BlockState.java
index 631cbf2be5..7c5438fa92 100644
--- a/paper-api/src/main/java/org/bukkit/block/BlockState.java
+++ b/paper-api/src/main/java/org/bukkit/block/BlockState.java
@@ -7,6 +7,7 @@ import org.bukkit.World;
 import org.bukkit.block.data.BlockData;
 import org.bukkit.material.MaterialData;
 import org.bukkit.metadata.Metadatable;
+import org.jetbrains.annotations.ApiStatus;
 import org.jetbrains.annotations.Contract;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -47,6 +48,15 @@ public interface BlockState extends Metadatable {
     @NotNull
     BlockData getBlockData();
 
+    /**
+     * Returns a copy of this BlockState as an unplaced BlockState.
+     *
+     * @return a copy of the block state
+     */
+    @NotNull
+    @ApiStatus.Experimental
+    BlockState copy();
+
     /**
      * Gets the type of this block state.
      *
diff --git a/paper-api/src/main/java/org/bukkit/event/world/AsyncStructureGenerateEvent.java b/paper-api/src/main/java/org/bukkit/event/world/AsyncStructureGenerateEvent.java
new file mode 100644
index 0000000000..91a137379f
--- /dev/null
+++ b/paper-api/src/main/java/org/bukkit/event/world/AsyncStructureGenerateEvent.java
@@ -0,0 +1,229 @@
+package org.bukkit.event.world;
+
+import com.google.common.base.Preconditions;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.bukkit.NamespacedKey;
+import org.bukkit.World;
+import org.bukkit.event.HandlerList;
+import org.bukkit.generator.structure.Structure;
+import org.bukkit.util.BlockTransformer;
+import org.bukkit.util.BoundingBox;
+import org.bukkit.util.EntityTransformer;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * This event will sometimes fire synchronously, depending on how it was
+ * triggered.
+ * <p>
+ * The constructor provides a boolean to indicate if the event was fired
+ * synchronously or asynchronously. When asynchronous, this event can be called
+ * from any thread, sans the main thread, and has limited access to the API.
+ * <p>
+ * If a {@link Structure} is naturally placed in a chunk of the world, this
+ * event will be asynchronous. If a player executes the '/place structure'
+ * command, this event will be synchronous.
+ *
+ * Allows to register transformers that can modify the blocks placed and
+ * entities spawned by the structure.
+ * <p>
+ * Care should be taken to check {@link #isAsynchronous()} and treat the event
+ * appropriately.
+ * <p>
+ */
+@ApiStatus.Experimental
+public class AsyncStructureGenerateEvent extends WorldEvent {
+
+    public static enum Cause {
+        COMMAND,
+        WORLD_GENERATION,
+        CUSTOM;
+    }
+
+    private static final HandlerList handlers = new HandlerList();
+
+    private final Cause cause;
+
+    private final Structure structure;
+    private final BoundingBox boundingBox;
+
+    private final int chunkX, chunkZ;
+
+    private final Map<NamespacedKey, BlockTransformer> blockTransformers = new LinkedHashMap<>();
+    private final Map<NamespacedKey, EntityTransformer> entityTransformers = new LinkedHashMap<>();
+
+    public AsyncStructureGenerateEvent(@NotNull World world, boolean async, @NotNull Cause cause, @NotNull Structure structure, @NotNull BoundingBox boundingBox, int chunkX, int chunkZ) {
+        super(world, async);
+        this.structure = structure;
+        this.boundingBox = boundingBox;
+        this.chunkX = chunkX;
+        this.chunkZ = chunkZ;
+        this.cause = cause;
+    }
+
+    /**
+     * Gets the event cause.
+     *
+     * @return the event cause
+     */
+    @NotNull
+    public Cause getCause() {
+        return cause;
+    }
+
+    /**
+     * Gets a block transformer by key.
+     *
+     * @param key the key of the block transformer
+     *
+     * @return the block transformer or null
+     */
+    @Nullable
+    public BlockTransformer getBlockTransformer(@NotNull NamespacedKey key) {
+        Preconditions.checkNotNull(key, "NamespacedKey cannot be null");
+        return blockTransformers.get(key);
+    }
+
+    /**
+     * Sets a block transformer to a key.
+     *
+     * @param key the key
+     * @param transformer the block transformer
+     */
+    public void setBlockTransformer(@NotNull NamespacedKey key, @NotNull BlockTransformer transformer) {
+        Preconditions.checkNotNull(key, "NamespacedKey cannot be null");
+        Preconditions.checkNotNull(transformer, "BlockTransformer cannot be null");
+        blockTransformers.put(key, transformer);
+    }
+
+    /**
+     * Removes a block transformer.
+     *
+     * @param key the key of the block transformer
+     */
+    public void removeBlockTransformer(@NotNull NamespacedKey key) {
+        Preconditions.checkNotNull(key, "NamespacedKey cannot be null");
+        blockTransformers.remove(key);
+    }
+
+    /**
+     * Removes all block transformers.
+     */
+    public void clearBlockTransformers() {
+        blockTransformers.clear();
+    }
+
+    /**
+     * Gets all block transformers in a unmodifiable map.
+     *
+     * @return the block transformers in a map
+     */
+    @NotNull
+    public Map<NamespacedKey, BlockTransformer> getBlockTransformers() {
+        return Collections.unmodifiableMap(blockTransformers);
+    }
+
+    /**
+     * Gets a entity transformer by key.
+     *
+     * @param key the key of the entity transformer
+     *
+     * @return the entity transformer or null
+     */
+    @Nullable
+    public EntityTransformer getEntityTransformer(@NotNull NamespacedKey key) {
+        Preconditions.checkNotNull(key, "NamespacedKey cannot be null");
+        return entityTransformers.get(key);
+    }
+
+    /**
+     * Sets a entity transformer to a key.
+     *
+     * @param key the key
+     * @param transformer the entity transformer
+     */
+    public void setEntityTransformer(@NotNull NamespacedKey key, @NotNull EntityTransformer transformer) {
+        Preconditions.checkNotNull(key, "NamespacedKey cannot be null");
+        Preconditions.checkNotNull(transformer, "EntityTransformer cannot be null");
+        entityTransformers.put(key, transformer);
+    }
+
+    /**
+     * Removes a entity transformer.
+     *
+     * @param key the key of the entity transformer
+     */
+    public void removeEntityTransformer(@NotNull NamespacedKey key) {
+        Preconditions.checkNotNull(key, "NamespacedKey cannot be null");
+        entityTransformers.remove(key);
+    }
+
+    /**
+     * Removes all entity transformers.
+     */
+    public void clearEntityTransformers() {
+        entityTransformers.clear();
+    }
+
+    /**
+     * Gets all entity transformers in a unmodifiable map.
+     *
+     * @return the entity transformers in a map
+     */
+    @NotNull
+    public Map<NamespacedKey, EntityTransformer> getEntityTransformers() {
+        return Collections.unmodifiableMap(entityTransformers);
+    }
+
+    /**
+     * Get the structure reference that is generated.
+     *
+     * @return the structure
+     */
+    @NotNull
+    public Structure getStructure() {
+        return structure;
+    }
+
+    /**
+     * Get the bounding box of the structure.
+     *
+     * @return the bounding box
+     */
+    @NotNull
+    public BoundingBox getBoundingBox() {
+        return boundingBox.clone();
+    }
+
+    /**
+     * Get the x coordinate of the origin chunk of the structure.
+     *
+     * @return the chunk x coordinate
+     */
+    public int getChunkX() {
+        return chunkX;
+    }
+
+    /**
+     * Get the z coordinate of the origin chunk of the structure.
+     *
+     * @return the chunk z coordinate
+     */
+    public int getChunkZ() {
+        return chunkZ;
+    }
+
+    @NotNull
+    @Override
+    public HandlerList getHandlers() {
+        return handlers;
+    }
+
+    @NotNull
+    public static HandlerList getHandlerList() {
+        return handlers;
+    }
+}
diff --git a/paper-api/src/main/java/org/bukkit/util/BlockTransformer.java b/paper-api/src/main/java/org/bukkit/util/BlockTransformer.java
new file mode 100644
index 0000000000..7f430519c0
--- /dev/null
+++ b/paper-api/src/main/java/org/bukkit/util/BlockTransformer.java
@@ -0,0 +1,62 @@
+package org.bukkit.util;
+
+import org.bukkit.block.BlockState;
+import org.bukkit.block.data.BlockData;
+import org.bukkit.generator.LimitedRegion;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A BlockTransformer is used to modify blocks that are placed by structure.
+ */
+@FunctionalInterface
+@ApiStatus.Experimental
+public interface BlockTransformer {
+
+    /**
+     * The TransformationState allows access to the original block state and the
+     * block state of the block that was at the location of the transformation
+     * in the world before the transformation started.
+     */
+    public static interface TransformationState {
+
+        /**
+         * Creates a clone of the original block state that a structure wanted
+         * to place and caches it for the current transformer.
+         *
+         * @return a clone of the original block state.
+         */
+        @NotNull
+        BlockState getOriginal();
+
+        /**
+         * Creates a clone of the block state that was at the location of the
+         * currently modified block at the start of the transformation process
+         * and caches it for the current transformer.
+         *
+         * @return a clone of the world block state.
+         */
+        @NotNull
+        BlockState getWorld();
+
+    }
+
+    /**
+     * Transforms a block in a structure.
+     *
+     * NOTE: The usage of {@link BlockData#createBlockState()} can provide even
+     * more flexibility to return the exact block state you might want to
+     * return.
+     *
+     * @param region the accessible region
+     * @param x the x position of the block
+     * @param y the y position of the block
+     * @param z the z position of the block
+     * @param current the state of the block that should be placed
+     * @param state the state of this transformation.
+     *
+     * @return the new block state
+     */
+    @NotNull
+    BlockState transform(@NotNull LimitedRegion region, int x, int y, int z, @NotNull BlockState current, @NotNull TransformationState state);
+}
diff --git a/paper-api/src/main/java/org/bukkit/util/EntityTransformer.java b/paper-api/src/main/java/org/bukkit/util/EntityTransformer.java
new file mode 100644
index 0000000000..d1f44fd90d
--- /dev/null
+++ b/paper-api/src/main/java/org/bukkit/util/EntityTransformer.java
@@ -0,0 +1,29 @@
+package org.bukkit.util;
+
+import org.bukkit.entity.Entity;
+import org.bukkit.generator.LimitedRegion;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A EntityTransformer is used to modify entities that are spawned by structure.
+ */
+@FunctionalInterface
+@ApiStatus.Experimental
+public interface EntityTransformer {
+
+    /**
+     * Transforms a entity in a structure.
+     *
+     * @param region the accessible region
+     * @param x the x position of the entity
+     * @param y the y position of the entity
+     * @param z the z position of the entity
+     * @param entity the entity
+     * @param allowedToSpawn if the entity is allowed to spawn
+     *
+     * @return {@code true} if the entity should be spawned otherwise
+     * {@code false}
+     */
+    boolean transform(@NotNull LimitedRegion region, int x, int y, int z, @NotNull Entity entity, boolean allowedToSpawn);
+}