diff --git a/docs/_templates/changelog/v3.0.0.md b/docs/_templates/changelog/v3.0.0.md
index d23cd63f3..4b2b4a1b9 100644
--- a/docs/_templates/changelog/v3.0.0.md
+++ b/docs/_templates/changelog/v3.0.0.md
@@ -24,9 +24,9 @@
- 📥 **[#336](https://github.com/mcbookshelf/bookshelf/issues/336)** - Bookshelf is now available on PyPI! You can install it with pip or any other Python dependency manager for use with the Beet build toolchain.
### `🧱 bs.block`
-
-- ✨ **[#279](https://github.com/mcbookshelf/bookshelf/issues/279)** - Added a `#bs.block:play_block_sound` tag for playing block sounds.
-- 🐛 **[#320](https://github.com/mcbookshelf/bookshelf/issues/320)** - Fixed functions that were unusable outside the Overworld.
+- ✨ **[#349](https://github.com/mcbookshelf/bookshelf/issues/349)** - Added the `on_finished` command argument to block fill operations. If specified the provided string is run as a command once the block fill operation is finished.
+- ✨ **[#279](https://github.com/mcbookshelf/Bookshelf/issues/279)** - Added a `#bs.block:play_block_sound` tag for playing block sounds.
+- 🐛 **[#320](https://github.com/mcbookshelf/Bookshelf/issues/320)** - Fixed functions that were unusable outside the Overworld.
### `🧱 bs.environment`
diff --git a/docs/modules/block.md b/docs/modules/block.md
index 7913f605f..a67c33b8a 100644
--- a/docs/modules/block.md
+++ b/docs/modules/block.md
@@ -40,8 +40,9 @@ Fill all or part of a region with a specific block.
- {nbt}`string` **block**: Block to fill the region with.
- {nbt}`string` {nbt}`list` **from**: Starting position as a valid position string or a list of 3 elements (x, y, z).
- {nbt}`string` {nbt}`list` **to**: Ending position as a valid position string or a list of 3 elements (x, y, z).
- - {nbt}`string` **mode**: Mode used to set blocks [destroy|keep|replace] (default: replace).
- {nbt}`int` **limit**: Limit how many blocks can be set in a single tick (default: 4096).
+ - {nbt}`string` **mode**: Mode used to set blocks [destroy|keep|replace] (default: replace).
+ - {nbt}`string` **on_finished**: Command executed at the end of the operation (at the location of the final block).
- {nbt}`list` **masks**: Determine which blocks will be replaced.
- {nbt}`compound` Block mask
- {nbt}`string` **block**: Block acting as a filter.
@@ -55,11 +56,11 @@ Fill all or part of a region with a specific block.
**State**: Blocks are placed in the world.
```
-*Replace the top layer of dirt by grass:*
+*Replace the top layer of dirt by grass and use a say command when finished:*
```mcfunction
# Setup the input
-data modify storage bs:in block.fill_block set value {block:"minecraft:grass_block",from:[-16,100,0],to:[-1,103,15],masks:[{block:"minecraft:dirt"},{block:"minecraft:air",y:1}]}
+data modify storage bs:in block.fill_block set value {block:"minecraft:grass_block",from:[-16,100,0],to:[-1,103,15],on_finished:"say added grass on top",masks:[{block:"minecraft:dirt"},{block:"minecraft:air",y:1}]}
# Run the process
function #bs.block:fill_block
@@ -88,8 +89,9 @@ Fill all or part of a region with a specific block type, preserving states and N
- {nbt}`string` **type**: Block id to fill the region with.
- {nbt}`string` {nbt}`list` **from**: Starting position as a valid position string or a list of 3 elements (x, y, z).
- {nbt}`string` {nbt}`list` **to**: Ending position as a valid position string or a list of 3 elements (x, y, z).
- - {nbt}`string` **mode**: Mode used to set blocks [destroy|keep|replace] (default: replace).
- {nbt}`int` **limit**: Limit how many blocks can be set in a single tick (default: 4096).
+ - {nbt}`string` **mode**: Mode used to set blocks [destroy|keep|replace] (default: replace).
+ - {nbt}`string` **on_finished**: Command executed at the end of the operation (at the location of the final block).
- {nbt}`list` **masks**: Determine which blocks will be replaced.
- {nbt}`compound` Block mask
- {nbt}`string` **block**: Block acting as a filter.
@@ -103,11 +105,11 @@ Fill all or part of a region with a specific block type, preserving states and N
**State**: Blocks are placed in the world.
```
-*Replace oak stairs with spruce stairs while preserving states:*
+*Replace oak stairs with spruce stairs while preserving states and use a say command when finished:*
```mcfunction
# Setup the input
-data modify storage bs:in block.fill_type set value {type:"minecraft:spruce_stairs",from:[-16,100,0],to:[-1,103,15],masks:[{block:"minecraft:oak_stairs"}]}
+data modify storage bs:in block.fill_type set value {type:"minecraft:spruce_stairs",from:[-16,100,0],to:[-1,103,15],on_finished:"say replaced the stairs",masks:[{block:"minecraft:oak_stairs"}]}
# Run the process
function #bs.block:fill_type
@@ -129,8 +131,9 @@ Fill all or part of a region with random blocks or types.
- {nbt}`int` **weight**: Determine the likelihood of selecting the entry (default: 1).
- {nbt}`string` {nbt}`list` **from**: Starting position as a valid position string or a list of 3 elements (x, y, z).
- {nbt}`string` {nbt}`list` **to**: Ending position as a valid position string or a list of 3 elements (x, y, z).
- - {nbt}`string` **mode**: Mode used to set blocks [destroy|keep|replace] (default: replace).
- {nbt}`int` **limit**: Limit how many blocks can be set in a single tick (default: 4096).
+ - {nbt}`string` **mode**: Mode used to set blocks [destroy|keep|replace] (default: replace).
+ - {nbt}`string` **on_finished**: Command executed at the end of the operation (at the location of the final block).
- {nbt}`list` **masks**: Determine which blocks will be replaced.
- {nbt}`compound` Block mask
- {nbt}`string` **block**: Block acting as a filter.
@@ -144,11 +147,11 @@ Fill all or part of a region with random blocks or types.
**State**: Blocks are placed in the world.
```
-*Randomly fill an area with stone or air:*
+*Randomly fill an area with stone or air and use a say command when finished:*
```mcfunction
# Setup the input
-data modify storage bs:in block.fill_random set value {entries:[{block:"minecraft:stone"},{block:"minecraft:air"}],from:[-16,100,0],to:[-1,103,15]}
+data modify storage bs:in block.fill_random set value {entries:[{block:"minecraft:stone"},{block:"minecraft:air"}],from:[-16,100,0],to:[-1,103,15],on_finished:"say randomly placed stone"}
# Run the process
function #bs.block:fill_random
diff --git a/modules/bs.block/data/bs.block/function/fill/events/on_finished.mcfunction b/modules/bs.block/data/bs.block/function/fill/events/on_finished.mcfunction
new file mode 100644
index 000000000..b6dabcbfe
--- /dev/null
+++ b/modules/bs.block/data/bs.block/function/fill/events/on_finished.mcfunction
@@ -0,0 +1,16 @@
+# ------------------------------------------------------------------------------------------------------------
+# Copyright (c) 2025 Gunivers
+#
+# This file is part of the Bookshelf project (https://github.com/mcbookshelf/Bookshelf).
+#
+# This source code is subject to the terms of the Mozilla Public License, v. 2.0.
+# If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# Conditions:
+# - You may use this file in compliance with the MPL v2.0
+# - Any modifications must be documented and disclosed under the same license
+#
+# For more details, refer to the MPL v2.0.
+# ------------------------------------------------------------------------------------------------------------
+
+$$(on_finished)
diff --git a/modules/bs.block/data/bs.block/function/fill/recurse/next.mcfunction b/modules/bs.block/data/bs.block/function/fill/recurse/next.mcfunction
index 23ea52ed9..9c865d7bc 100644
--- a/modules/bs.block/data/bs.block/function/fill/recurse/next.mcfunction
+++ b/modules/bs.block/data/bs.block/function/fill/recurse/next.mcfunction
@@ -27,3 +27,6 @@ execute if score #block.y bs.data > #block.max_y bs.data run scoreboard players
execute if score #block.z bs.data > #block.min_z bs.data positioned ~ ~ ~1 run return run function bs.block:fill/recurse/next with storage bs:data block._
$execute if score #block.y bs.data > #block.min_y bs.data positioned ~ ~1 $(min_z) run return run function bs.block:fill/recurse/next with storage bs:data block._
$execute if score #block.x bs.data <= #block.max_x bs.data positioned ~1 $(min_y) $(min_z) run return run function bs.block:fill/recurse/next with storage bs:data block._
+
+# if present, the on_finished command is only executed at the end of the recursion
+execute if data storage bs:data block._.on_finished run function bs.block:fill/events/on_finished with storage bs:data block._
diff --git a/modules/bs.block/data/bs.block/test/fill/on_finished.mcfunction b/modules/bs.block/data/bs.block/test/fill/on_finished.mcfunction
new file mode 100644
index 000000000..8b85c13bb
--- /dev/null
+++ b/modules/bs.block/data/bs.block/test/fill/on_finished.mcfunction
@@ -0,0 +1,21 @@
+# ------------------------------------------------------------------------------------------------------------
+# Copyright (c) 2025 Gunivers
+#
+# This file is part of the Bookshelf project (https://github.com/mcbookshelf/Bookshelf).
+#
+# This source code is subject to the terms of the Mozilla Public License, v. 2.0.
+# If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# Conditions:
+# - You may use this file in compliance with the MPL v2.0
+# - Any modifications must be documented and disclosed under the same license
+#
+# For more details, refer to the MPL v2.0.
+# ------------------------------------------------------------------------------------------------------------
+
+data modify storage bs:in block.fill_block set value {block:"minecraft:stone",from:"~ ~ ~",to:"~ ~1 ~",limit:1,on_finished:"setblock ~ ~ ~ minecraft:bookshelf"}
+function #bs.block:fill_block
+assert block ~ ~ ~ minecraft:stone
+assert not block ~ ~1 ~ minecraft:stone
+await delay 1t
+assert block ~ ~1 ~ minecraft:bookshelf