diff --git a/src/wp-includes/class-wp-block-metadata-registry.php b/src/wp-includes/class-wp-block-metadata-registry.php
index 17bbf320d5..4b567d7d6f 100644
--- a/src/wp-includes/class-wp-block-metadata-registry.php
+++ b/src/wp-includes/class-wp-block-metadata-registry.php
@@ -38,20 +38,12 @@ class WP_Block_Metadata_Registry {
 	private static $last_matched_collection = null;
 
 	/**
-	 * Stores the WordPress 'wp-includes' directory path.
+	 * Stores the default allowed collection root paths.
 	 *
-	 * @since 6.7.0
-	 * @var string|null
+	 * @since 6.7.2
+	 * @var string[]|null
 	 */
-	private static $wpinc_dir = null;
-
-	/**
-	 * Stores the normalized WordPress plugin directory path.
-	 *
-	 * @since 6.7.0
-	 * @var string|null
-	 */
-	private static $plugin_dir = null;
+	private static $default_collection_roots = null;
 
 	/**
 	 * Registers a block metadata collection.
@@ -92,29 +84,50 @@ class WP_Block_Metadata_Registry {
 	public static function register_collection( $path, $manifest ) {
 		$path = wp_normalize_path( rtrim( $path, '/' ) );
 
-		$wpinc_dir  = self::get_wpinc_dir();
-		$plugin_dir = self::get_plugin_dir();
+		$collection_roots = self::get_default_collection_roots();
+
+		/**
+		 * Filters the root directory paths for block metadata collections.
+		 *
+		 * Any block metadata collection that is registered must not use any of these paths, or any parent directory
+		 * path of them. Most commonly, block metadata collections should reside within one of these paths, though in
+		 * some scenarios they may also reside in entirely different directories (e.g. in case of symlinked plugins).
+		 *
+		 * Example:
+		 * * It is allowed to register a collection with path `WP_PLUGIN_DIR . '/my-plugin'`.
+		 * * It is not allowed to register a collection with path `WP_PLUGIN_DIR`.
+		 * * It is not allowed to register a collection with path `dirname( WP_PLUGIN_DIR )`.
+		 *
+		 * The default list encompasses the `wp-includes` directory, as well as the root directories for plugins,
+		 * must-use plugins, and themes. This filter can be used to expand the list, e.g. to custom directories that
+		 * contain symlinked plugins, so that these root directories cannot be used themselves for a block metadata
+		 * collection either.
+		 *
+		 * @since 6.7.2
+		 *
+		 * @param string[] $collection_roots List of allowed metadata collection root paths.
+		 */
+		$collection_roots = apply_filters( 'wp_allowed_block_metadata_collection_roots', $collection_roots );
+
+		$collection_roots = array_unique(
+			array_map(
+				static function ( $allowed_root ) {
+					return rtrim( $allowed_root, '/' );
+				},
+				$collection_roots
+			)
+		);
 
 		// Check if the path is valid:
-		if ( str_starts_with( $path, $plugin_dir ) ) {
-			// For plugins, ensure the path is within a specific plugin directory and not the base plugin directory.
-			$relative_path = substr( $path, strlen( $plugin_dir ) + 1 );
-			$plugin_name   = strtok( $relative_path, '/' );
-
-			if ( empty( $plugin_name ) || $plugin_name === $relative_path ) {
-				_doing_it_wrong(
-					__METHOD__,
-					__( 'Block metadata collections can only be registered for a specific plugin. The provided path is neither a core path nor a valid plugin path.' ),
-					'6.7.0'
-				);
-				return false;
-			}
-		} elseif ( ! str_starts_with( $path, $wpinc_dir ) ) {
-			// If it's neither a plugin directory path nor within 'wp-includes', the path is invalid.
+		if ( ! self::is_valid_collection_path( $path, $collection_roots ) ) {
 			_doing_it_wrong(
 				__METHOD__,
-				__( 'Block metadata collections can only be registered for a specific plugin. The provided path is neither a core path nor a valid plugin path.' ),
-				'6.7.0'
+				sprintf(
+					/* translators: %s: list of allowed collection roots */
+					__( 'Block metadata collections cannot be registered as one of the following directories or their parent directories: %s' ),
+					esc_html( implode( wp_get_list_item_separator(), $collection_roots ) )
+				),
+				'6.7.2'
 			);
 			return false;
 		}
@@ -244,30 +257,58 @@ class WP_Block_Metadata_Registry {
 	}
 
 	/**
-	 * Gets the WordPress 'wp-includes' directory path.
+	 * Checks whether the given block metadata collection path is valid against the list of collection roots.
 	 *
-	 * @since 6.7.0
+	 * @since 6.7.2
 	 *
-	 * @return string The WordPress 'wp-includes' directory path.
+	 * @param string   $path             Block metadata collection path, without trailing slash.
+	 * @param string[] $collection_roots List of collection root paths, without trailing slashes.
+	 * @return bool True if the path is allowed, false otherwise.
 	 */
-	private static function get_wpinc_dir() {
-		if ( ! isset( self::$wpinc_dir ) ) {
-			self::$wpinc_dir = wp_normalize_path( ABSPATH . WPINC );
+	private static function is_valid_collection_path( $path, $collection_roots ) {
+		foreach ( $collection_roots as $allowed_root ) {
+			// If the path matches any root exactly, it is invalid.
+			if ( $allowed_root === $path ) {
+				return false;
+			}
+
+			// If the path is a parent path of any of the roots, it is invalid.
+			if ( str_starts_with( $allowed_root, $path ) ) {
+				return false;
+			}
 		}
-		return self::$wpinc_dir;
+
+		return true;
 	}
 
 	/**
-	 * Gets the normalized WordPress plugin directory path.
+	 * Gets the default collection root directory paths.
 	 *
-	 * @since 6.7.0
+	 * @since 6.7.2
 	 *
-	 * @return string The normalized WordPress plugin directory path.
+	 * @return string[] List of directory paths within which metadata collections are allowed.
 	 */
-	private static function get_plugin_dir() {
-		if ( ! isset( self::$plugin_dir ) ) {
-			self::$plugin_dir = wp_normalize_path( WP_PLUGIN_DIR );
+	private static function get_default_collection_roots() {
+		if ( isset( self::$default_collection_roots ) ) {
+			return self::$default_collection_roots;
 		}
-		return self::$plugin_dir;
+
+		$collection_roots = array(
+			wp_normalize_path( ABSPATH . WPINC ),
+			wp_normalize_path( WP_CONTENT_DIR ),
+			wp_normalize_path( WPMU_PLUGIN_DIR ),
+			wp_normalize_path( WP_PLUGIN_DIR ),
+		);
+
+		$theme_roots = get_theme_roots();
+		if ( ! is_array( $theme_roots ) ) {
+			$theme_roots = array( $theme_roots );
+		}
+		foreach ( $theme_roots as $theme_root ) {
+			$collection_roots[] = trailingslashit( wp_normalize_path( WP_CONTENT_DIR ) ) . ltrim( wp_normalize_path( $theme_root ), '/' );
+		}
+
+		self::$default_collection_roots = array_unique( $collection_roots );
+		return self::$default_collection_roots;
 	}
 }
diff --git a/tests/phpunit/tests/blocks/wpBlockMetadataRegistry.php b/tests/phpunit/tests/blocks/wpBlockMetadataRegistry.php
index 3f0ec006b0..bc62131c6a 100644
--- a/tests/phpunit/tests/blocks/wpBlockMetadataRegistry.php
+++ b/tests/phpunit/tests/blocks/wpBlockMetadataRegistry.php
@@ -80,12 +80,112 @@ class Tests_Blocks_WpBlockMetadataRegistry extends WP_UnitTestCase {
 		$this->assertFalse( $result, 'Invalid plugin path should not be registered' );
 	}
 
-	public function test_register_collection_with_non_existent_path() {
-		$non_existent_path = '/path/that/does/not/exist';
+	/**
+	 * @ticket 62140
+	 */
+	public function test_register_collection_with_valid_muplugin_path() {
+		$plugin_path = WPMU_PLUGIN_DIR . '/my-plugin/blocks';
+		$result      = WP_Block_Metadata_Registry::register_collection( $plugin_path, $this->temp_manifest_file );
+		$this->assertTrue( $result, 'Valid must-use plugin path should be registered successfully' );
+	}
+
+	/**
+	 * @ticket 62140
+	 */
+	public function test_register_collection_with_invalid_muplugin_path() {
+		$invalid_plugin_path = WPMU_PLUGIN_DIR;
 
 		$this->setExpectedIncorrectUsage( 'WP_Block_Metadata_Registry::register_collection' );
 
-		$result = WP_Block_Metadata_Registry::register_collection( $non_existent_path, $this->temp_manifest_file );
-		$this->assertFalse( $result, 'Non-existent path should not be registered' );
+		$result = WP_Block_Metadata_Registry::register_collection( $invalid_plugin_path, $this->temp_manifest_file );
+		$this->assertFalse( $result, 'Invalid must-use plugin path should not be registered' );
+	}
+
+	/**
+	 * @ticket 62140
+	 */
+	public function test_register_collection_with_valid_theme_path() {
+		$theme_path = WP_CONTENT_DIR . '/themes/my-theme/blocks';
+		$result     = WP_Block_Metadata_Registry::register_collection( $theme_path, $this->temp_manifest_file );
+		$this->assertTrue( $result, 'Valid theme path should be registered successfully' );
+	}
+
+	/**
+	 * @ticket 62140
+	 */
+	public function test_register_collection_with_invalid_theme_path() {
+		$invalid_theme_path = WP_CONTENT_DIR . '/themes';
+
+		$this->setExpectedIncorrectUsage( 'WP_Block_Metadata_Registry::register_collection' );
+
+		$result = WP_Block_Metadata_Registry::register_collection( $invalid_theme_path, $this->temp_manifest_file );
+		$this->assertFalse( $result, 'Invalid theme path should not be registered' );
+	}
+
+	/**
+	 * @ticket 62140
+	 */
+	public function test_register_collection_with_arbitrary_path() {
+		$arbitrary_path = '/var/arbitrary/path';
+		$result         = WP_Block_Metadata_Registry::register_collection( $arbitrary_path, $this->temp_manifest_file );
+		$this->assertTrue( $result, 'Arbitrary path should be registered successfully' );
+	}
+
+	/**
+	 * @ticket 62140
+	 */
+	public function test_register_collection_with_arbitrary_path_and_collection_roots_filter() {
+		$arbitrary_path = '/var/arbitrary/path';
+		add_filter(
+			'wp_allowed_block_metadata_collection_roots',
+			static function ( $paths ) use ( $arbitrary_path ) {
+				$paths[] = $arbitrary_path;
+				return $paths;
+			}
+		);
+
+		$this->setExpectedIncorrectUsage( 'WP_Block_Metadata_Registry::register_collection' );
+
+		$result = WP_Block_Metadata_Registry::register_collection( $arbitrary_path, $this->temp_manifest_file );
+		$this->assertFalse( $result, 'Arbitrary path should not be registered if it matches a collection root' );
+
+		$result = WP_Block_Metadata_Registry::register_collection( dirname( $arbitrary_path ), $this->temp_manifest_file );
+		$this->assertFalse( $result, 'Arbitrary path should not be registered if it is a parent directory of a collection root' );
+
+		$result = WP_Block_Metadata_Registry::register_collection( $arbitrary_path . '/my-plugin/blocks', $this->temp_manifest_file );
+		$this->assertTrue( $result, 'Arbitrary path should be registered successfully if it is within a collection root' );
+	}
+
+	/**
+	 * @ticket 62140
+	 */
+	public function test_register_collection_with_wp_content_parent_directory_path() {
+		$invalid_path = dirname( WP_CONTENT_DIR );
+
+		$this->setExpectedIncorrectUsage( 'WP_Block_Metadata_Registry::register_collection' );
+
+		$result = WP_Block_Metadata_Registry::register_collection( $invalid_path, $this->temp_manifest_file );
+		$this->assertFalse( $result, 'Invalid path (parent directory of "wp-content") should not be registered' );
+	}
+
+	/**
+	 * @ticket 62140
+	 */
+	public function test_register_collection_with_wp_includes_parent_directory_path() {
+		$invalid_path = ABSPATH;
+
+		$this->setExpectedIncorrectUsage( 'WP_Block_Metadata_Registry::register_collection' );
+
+		$result = WP_Block_Metadata_Registry::register_collection( $invalid_path, $this->temp_manifest_file );
+		$this->assertFalse( $result, 'Invalid path (parent directory of "wp-includes") should not be registered' );
+	}
+
+	public function test_register_collection_with_non_existent_manifest() {
+		$non_existent_manifest = '/path/that/does/not/exist/block-manifest.php';
+
+		$this->setExpectedIncorrectUsage( 'WP_Block_Metadata_Registry::register_collection' );
+
+		$result = WP_Block_Metadata_Registry::register_collection( '/var/arbitrary/path', $non_existent_manifest );
+		$this->assertFalse( $result, 'Non-existent manifest should not be registered' );
 	}
 }