diff --git a/wp-includes/class.wp-scripts.php b/wp-includes/class.wp-scripts.php
index cc88455bc1..f4277c8586 100644
--- a/wp-includes/class.wp-scripts.php
+++ b/wp-includes/class.wp-scripts.php
@@ -351,6 +351,19 @@ class WP_Scripts extends WP_Dependencies {
 			echo $cond_after;
 		}
 
+        $async_attr = '';
+        $defer_attr = '';
+
+		if ( false === in_array( $handle, $this->in_footer ) ) {
+            if( true === apply_filters( 'script_attr_async', $obj->extra['is_async'] ?? false ) ) {
+                $async_attr = ' async';
+            }
+
+            if( true === apply_filters( 'script_attr_defer', $obj->extra['is_defer'] ?? false ) ) {
+                $defer_attr = ' defer';
+            }
+        }
+
 		// A single item may alias a set of items, by having dependencies, but no source.
 		if ( ! $src ) {
 			if ( $inline_script_tag ) {
@@ -385,7 +398,7 @@ class WP_Scripts extends WP_Dependencies {
 		}
 
 		$tag  = $translations . $cond_before . $before_handle;
-		$tag .= sprintf( "<script%s src='%s'></script>\n", $this->type_attr, $src );
+        $tag .= sprintf( "<script%s src='%s'%s%s></script>\n", $this->type_attr, $src, $async_attr, $defer_attr );
 		$tag .= $after_handle . $cond_after;
 
 		/**
diff --git a/wp-includes/functions.wp-scripts.php b/wp-includes/functions.wp-scripts.php
index 3eefe4f903..b900fa2b2a 100644
--- a/wp-includes/functions.wp-scripts.php
+++ b/wp-includes/functions.wp-scripts.php
@@ -389,4 +389,46 @@ function wp_script_is( $handle, $list = 'enqueued' ) {
  */
 function wp_script_add_data( $handle, $key, $value ) {
 	return wp_scripts()->add_data( $handle, $key, $value );
+ }
+
+/**
+ * Add 'is_async' metadata to a script.
+ *
+ * @see WP_Dependencies::add_data()
+ *
+ * @param string|array $handles Names of the scripts.
+ */
+function wp_async_script($handles) {
+    if('string' === gettype($handles)) {
+        $handles = [$handles];
+    }
+
+    if('array' !== gettype($handles)) {
+        return;
+    }
+
+    foreach($handles as $handle ) {
+        wp_scripts()->add_data( $handle, 'is_async', true );
+    }
+}
+
+/**
+ * Add 'is_defer' metadata to a script.
+ *
+ * @see WP_Dependencies::add_data()
+ *
+ * @param string|array $handles Names of the scripts.
+ */
+function wp_defer_script($handles) {
+    if('string' === gettype($handles)) {
+        $handles = [$handles];
+    }
+
+    if('array' !== gettype($handles)) {
+        return;
+    }
+
+    foreach($handles as $handle ) {
+        wp_scripts()->add_data( $handle, 'is_defer', true );
+    }
 }
