diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php
index 3769b936cdee9..0a34af25f6168 100644
--- a/src/wp-includes/blocks.php
+++ b/src/wp-includes/blocks.php
@@ -40,6 +40,217 @@ function unregister_block_type( $name ) {
 	return WP_Block_Type_Registry::get_instance()->unregister( $name );
 }
 
+/**
+ * Removes the block asset's path prefix if provided.
+ *
+ * @since 5.5.0
+ *
+ * @param string $asset_handle_or_path Asset handle or prefixed path.
+ * @return string Path without the prefix or the original value.
+ */
+function remove_block_asset_path_prefix( $asset_handle_or_path ) {
+	$path_prefix = 'file:';
+	if ( 0 !== strpos( $asset_handle_or_path, $path_prefix ) ) {
+		return $asset_handle_or_path;
+	}
+	return substr(
+		$asset_handle_or_path,
+		strlen( $path_prefix )
+	);
+}
+
+/**
+ * Generates the name for an asset based on the name of the block
+ * and the field name provided.
+ *
+ * @since 5.5.0
+ *
+ * @param string $block_name Name of the block.
+ * @param string $field_name Name of the metadata field.
+ * @return string Generated asset name for the block's field.
+ */
+function generate_block_asset_handle( $block_name, $field_name ) {
+	$field_mappings = array(
+		'editorScript' => 'editor-script',
+		'script'       => 'script',
+		'editorStyle'  => 'editor-style',
+		'style'        => 'style',
+	);
+	return str_replace( '/', '-', $block_name ) .
+		'-' . $field_mappings[ $field_name ];
+}
+
+/**
+ * Finds a script handle for the selected block metadata field. It detects
+ * when a path to file was provided and finds a corresponding
+ * asset file with details necessary to register the script under
+ * automatically generated handle name. It returns unprocessed script handle
+ * otherwise.
+ *
+ * @since 5.5.0
+ *
+ * @param array  $metadata   Block metadata.
+ * @param string $field_name Field name to pick from metadata.
+ * @return string|bool Script handle provided directly or created through
+ *                     script's registration, or false on failure.
+ */
+function register_block_script_handle( $metadata, $field_name ) {
+	if ( empty( $metadata[ $field_name ] ) ) {
+		return false;
+	}
+	$script_handle = $metadata[ $field_name ];
+	$script_path   = remove_block_asset_path_prefix( $metadata[ $field_name ] );
+	if ( $script_handle === $script_path ) {
+		return $script_handle;
+	}
+
+	$script_handle     = generate_block_asset_handle( $metadata['name'], $field_name );
+	$script_asset_path = realpath(
+		dirname( $metadata['file'] ) . '/' .
+		substr_replace( $script_path, '.asset.php', - strlen( '.js' ) )
+	);
+	if ( ! file_exists( $script_asset_path ) ) {
+		$message = sprintf(
+			/* translators: %1: field name. %2: block name */
+			__( 'The asset file for the "%1$s" defined in "%2$s" block definition is missing.', 'default' ),
+			$field_name,
+			$metadata['name']
+		);
+		_doing_it_wrong( __FUNCTION__, $message, '5.5.0' );
+		return false;
+	}
+	$script_asset = require $script_asset_path;
+	$result       = wp_register_script(
+		$script_handle,
+		plugins_url( $script_path, $metadata['file'] ),
+		$script_asset['dependencies'],
+		$script_asset['version']
+	);
+	return $result ? $script_handle : false;
+}
+
+/**
+ * Finds a style handle for the block metadata field. It detects when a path
+ * to file was provided and registers the style under automatically
+ * generated handle name. It returns unprocessed style handle otherwise.
+ *
+ * @since 5.5.0
+ *
+ * @param array  $metadata Block metadata.
+ * @param string $field_name Field name to pick from metadata.
+ * @return string|boolean Style handle provided directly or created through
+ *                        style's registration, or false on failure.
+ */
+function register_block_style_handle( $metadata, $field_name ) {
+	if ( empty( $metadata[ $field_name ] ) ) {
+		return false;
+	}
+	$style_handle = $metadata[ $field_name ];
+	$style_path   = remove_block_asset_path_prefix( $metadata[ $field_name ] );
+	if ( $style_handle === $style_path ) {
+		return $style_handle;
+	}
+
+	$style_handle = generate_block_asset_handle( $metadata['name'], $field_name );
+	$block_dir    = dirname( $metadata['file'] );
+	$result       = wp_register_style(
+		$style_handle,
+		plugins_url( $style_path, $metadata['file'] ),
+		array(),
+		filemtime( realpath( "$block_dir/$style_path" ) )
+	);
+	return $result ? $style_handle : false;
+}
+
+/**
+ * Registers a block type from metadata stored in the `block.json` file.
+ *
+ * @since 5.5.0
+ *
+ * @param string $file_or_folder Path to the JSON file with metadata definition for
+ *                               the block or path to the folder where the `block.json` file is located.
+ * @param array  $args {
+ *     Optional. Array of block type arguments. Any arguments may be defined, however the
+ *     ones described below are supported by default. Default empty array.
+ *
+ *     @type callable $render_callback Callback used to render blocks of this block type.
+ * }
+ * @return WP_Block_Type|false The registered block type on success, or false on failure.
+ */
+function register_block_type_from_metadata( $file_or_folder, $args = array() ) {
+	$filename      = 'block.json';
+	$metadata_file = ( substr( $file_or_folder, -strlen( $filename ) ) !== $filename ) ?
+		trailingslashit( $file_or_folder ) . $filename :
+		$file_or_folder;
+	if ( ! file_exists( $metadata_file ) ) {
+		return false;
+	}
+
+	$metadata = json_decode( file_get_contents( $metadata_file ), true );
+	if ( ! is_array( $metadata ) || empty( $metadata['name'] ) ) {
+		return false;
+	}
+	$metadata['file'] = $metadata_file;
+
+	$settings          = array();
+	$property_mappings = array(
+		'title'           => 'title',
+		'category'        => 'category',
+		'parent'          => 'parent',
+		'icon'            => 'icon',
+		'description'     => 'description',
+		'keywords'        => 'keywords',
+		'attributes'      => 'attributes',
+		'providesContext' => 'provides_context',
+		'usesContext'     => 'uses_context',
+		'supports'        => 'supports',
+		'styles'          => 'styles',
+		'example'         => 'example',
+	);
+
+	foreach ( $property_mappings as $key => $mapped_key ) {
+		if ( isset( $metadata[ $key ] ) ) {
+			$settings[ $mapped_key ] = $metadata[ $key ];
+		}
+	}
+
+	if ( ! empty( $metadata['editorScript'] ) ) {
+		$settings['editor_script'] = register_block_script_handle(
+			$metadata,
+			'editorScript'
+		);
+	}
+
+	if ( ! empty( $metadata['script'] ) ) {
+		$settings['script'] = register_block_script_handle(
+			$metadata,
+			'script'
+		);
+	}
+
+	if ( ! empty( $metadata['editorStyle'] ) ) {
+		$settings['editor_style'] = register_block_style_handle(
+			$metadata,
+			'editorStyle'
+		);
+	}
+
+	if ( ! empty( $metadata['style'] ) ) {
+		$settings['style'] = register_block_style_handle(
+			$metadata,
+			'style'
+		);
+	}
+
+	return register_block_type(
+		$metadata['name'],
+		array_merge(
+			$settings,
+			$args
+		)
+	);
+}
+
 /**
  * Determine whether a post or content string has blocks.
  *
diff --git a/tests/phpunit/tests/blocks/fixtures/block.asset.php b/tests/phpunit/tests/blocks/fixtures/block.asset.php
new file mode 100644
index 0000000000000..792bbb6c84cd3
--- /dev/null
+++ b/tests/phpunit/tests/blocks/fixtures/block.asset.php
@@ -0,0 +1,6 @@
+<?php
+
+return array(
+	'dependencies' => array(),
+	'version'      => 'test',
+);
diff --git a/tests/phpunit/tests/blocks/fixtures/block.css b/tests/phpunit/tests/blocks/fixtures/block.css
new file mode 100644
index 0000000000000..5bbe1134f7048
--- /dev/null
+++ b/tests/phpunit/tests/blocks/fixtures/block.css
@@ -0,0 +1 @@
+/* Test CSS file */
diff --git a/tests/phpunit/tests/blocks/fixtures/block.js b/tests/phpunit/tests/blocks/fixtures/block.js
new file mode 100644
index 0000000000000..0bdf0f5ad91f7
--- /dev/null
+++ b/tests/phpunit/tests/blocks/fixtures/block.js
@@ -0,0 +1 @@
+/* Test JavaScript file. */
diff --git a/tests/phpunit/tests/blocks/fixtures/block.json b/tests/phpunit/tests/blocks/fixtures/block.json
new file mode 100644
index 0000000000000..be4205ce767af
--- /dev/null
+++ b/tests/phpunit/tests/blocks/fixtures/block.json
@@ -0,0 +1,52 @@
+{
+	"name": "my-plugin/notice",
+	"title": "Notice",
+	"category": "common",
+	"parent": [
+		"core/group"
+	],
+	"providesContext": {
+		"my-plugin/message": "message"
+	},
+	"usesContext": [
+		"groupId"
+	],
+	"icon": "star",
+	"description": "Shows warning, error or success notices…",
+	"keywords": [
+		"alert",
+		"message"
+	],
+	"textDomain": "my-plugin",
+	"attributes": {
+		"message": {
+			"type": "string",
+			"source": "html",
+			"selector": ".message"
+		}
+	},
+	"supports": {
+		"align": true,
+		"lightBlockWrapper": true
+	},
+	"styles": [
+		{
+			"name": "default",
+			"label": "Default",
+			"isDefault": true
+		},
+		{
+			"name": "other",
+			"label": "Other"
+		}
+	],
+	"example": {
+		"attributes": {
+			"message": "This is a notice!"
+		}
+	},
+	"editorScript": "my-plugin-notice-editor-script",
+	"script": "my-plugin-notice-script",
+	"editorStyle": "my-plugin-notice-editor-style",
+	"style": "my-plugin-notice-style"
+}
diff --git a/tests/phpunit/tests/blocks/register.php b/tests/phpunit/tests/blocks/register.php
index 8271c9babdf67..514fa807505fa 100644
--- a/tests/phpunit/tests/blocks/register.php
+++ b/tests/phpunit/tests/blocks/register.php
@@ -102,6 +102,248 @@ function test_unregister_affects_main_registry() {
 		$this->assertFalse( $registry->is_registered( $name ) );
 	}
 
+	/**
+	 * @ticket 50263
+	 */
+	function test_does_not_remove_block_asset_path_prefix() {
+		$result = remove_block_asset_path_prefix( 'script-handle' );
+
+		$this->assertSame( 'script-handle', $result );
+	}
+
+	/**
+	 * @ticket 50263
+	 */
+	function test_removes_block_asset_path_prefix() {
+		$result = remove_block_asset_path_prefix( 'file:./block.js' );
+
+		$this->assertSame( './block.js', $result );
+	}
+
+	/**
+	 * @ticket 50263
+	 */
+	function test_generate_block_asset_handle() {
+		$block_name = 'unit-tests/my-block';
+
+		$this->assertSame(
+			'unit-tests-my-block-editor-script',
+			generate_block_asset_handle( $block_name, 'editorScript' )
+		);
+		$this->assertSame(
+			'unit-tests-my-block-script',
+			generate_block_asset_handle( $block_name, 'script' )
+		);
+		$this->assertSame(
+			'unit-tests-my-block-editor-style',
+			generate_block_asset_handle( $block_name, 'editorStyle' )
+		);
+		$this->assertSame(
+			'unit-tests-my-block-style',
+			generate_block_asset_handle( $block_name, 'style' )
+		);
+	}
+
+	/**
+	 * @ticket 50263
+	 */
+	function test_field_not_found_register_block_script_handle() {
+		$result = register_block_script_handle( array(), 'script' );
+
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @ticket 50263
+	 */
+	function test_empty_value_register_block_script_handle() {
+		$metadata = array( 'script' => '' );
+		$result   = register_block_script_handle( $metadata, 'script' );
+
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @expectedIncorrectUsage register_block_script_handle
+	 * @ticket 50263
+	 */
+	function test_missing_asset_file_register_block_script_handle() {
+		$metadata = array(
+			'file'   => __FILE__,
+			'name'   => 'unit-tests/test-block',
+			'script' => 'file:./fixtures/missing-asset.js',
+		);
+		$result   = register_block_script_handle( $metadata, 'script' );
+
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @ticket 50263
+	 */
+	function test_handle_passed_register_block_script_handle() {
+		$metadata = array(
+			'editorScript' => 'test-script-handle',
+		);
+		$result   = register_block_script_handle( $metadata, 'editorScript' );
+
+		$this->assertSame( 'test-script-handle', $result );
+	}
+
+	/**
+	 * @ticket 50263
+	 */
+	function test_success_register_block_script_handle() {
+		$metadata = array(
+			'file'   => __FILE__,
+			'name'   => 'unit-tests/test-block',
+			'script' => 'file:./fixtures/block.js',
+		);
+		$result   = register_block_script_handle( $metadata, 'script' );
+
+		$this->assertSame( 'unit-tests-test-block-script', $result );
+	}
+
+	/**
+	 * @ticket 50263
+	 */
+	function test_field_not_found_register_block_style_handle() {
+		$result = register_block_style_handle( array(), 'style' );
+
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @ticket 50263
+	 */
+	function test_empty_value_found_register_block_style_handle() {
+		$metadata = array( 'style' => '' );
+		$result   = register_block_style_handle( $metadata, 'style' );
+
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * @ticket 50263
+	 */
+	function test_handle_passed_register_block_style_handle() {
+		$metadata = array(
+			'style' => 'test-style-handle',
+		);
+		$result   = register_block_style_handle( $metadata, 'style' );
+
+		$this->assertSame( 'test-style-handle', $result );
+	}
+
+	/**
+	 * @ticket 50263
+	 */
+	function test_success_register_block_style_handle() {
+		$metadata = array(
+			'file'  => __FILE__,
+			'name'  => 'unit-tests/test-block',
+			'style' => 'file:./fixtures/block.css',
+		);
+		$result   = register_block_style_handle( $metadata, 'style' );
+
+		$this->assertSame( 'unit-tests-test-block-style', $result );
+	}
+
+	/**
+	 * Tests that the function returns false when the `block.json` is not found
+	 * in the WordPress core.
+	 *
+	 * @ticket 50263
+	 */
+	function test_metadata_not_found_in_wordpress_core() {
+		$result = register_block_type_from_metadata( 'unknown' );
+
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * Tests that the function returns false when the `block.json` is not found
+	 * in the current directory.
+	 *
+	 * @ticket 50263
+	 */
+	function test_metadata_not_found_in_the_current_directory() {
+		$result = register_block_type_from_metadata( __DIR__ );
+
+		$this->assertFalse( $result );
+	}
+
+	/**
+	 * Tests that the function returns the registered block when the `block.json`
+	 * is found in the fixtures directory.
+	 *
+	 * @ticket 50263
+	 */
+	function test_block_registers_with_metadata_fixture() {
+		$result = register_block_type_from_metadata(
+			__DIR__ . '/fixtures'
+		);
+
+		$this->assertInstanceOf( 'WP_Block_Type', $result );
+		$this->assertSame( 'my-plugin/notice', $result->name );
+		$this->assertSame( 'Notice', $result->title );
+		$this->assertSame( 'common', $result->category );
+		$this->assertEqualSets( array( 'core/group' ), $result->parent );
+		$this->assertSame( 'star', $result->icon );
+		$this->assertSame( 'Shows warning, error or success notices…', $result->description );
+		$this->assertEqualSets( array( 'alert', 'message' ), $result->keywords );
+		$this->assertEquals(
+			array(
+				'message' => array(
+					'type'     => 'string',
+					'source'   => 'html',
+					'selector' => '.message',
+				),
+			),
+			$result->attributes
+		);
+		$this->assertEquals(
+			array(
+				'my-plugin/message' => 'message',
+			),
+			$result->provides_context
+		);
+		$this->assertEqualSets( array( 'groupId' ), $result->uses_context );
+		$this->assertEquals(
+			array(
+				'align'             => true,
+				'lightBlockWrapper' => true,
+			),
+			$result->supports
+		);
+		$this->assertEquals(
+			array(
+				array(
+					'name'      => 'default',
+					'label'     => 'Default',
+					'isDefault' => true,
+				),
+				array(
+					'name'  => 'other',
+					'label' => 'Other',
+				),
+			),
+			$result->styles
+		);
+		$this->assertEquals(
+			array(
+				'attributes' => array(
+					'message' => 'This is a notice!',
+				),
+			),
+			$result->example
+		);
+		$this->assertSame( 'my-plugin-notice-editor-script', $result->editor_script );
+		$this->assertSame( 'my-plugin-notice-script', $result->script );
+		$this->assertSame( 'my-plugin-notice-editor-style', $result->editor_style );
+		$this->assertSame( 'my-plugin-notice-style', $result->style );
+	}
+
 	/**
 	 * @ticket 45109
 	 */