diff --git a/wp-admin/includes/plugin.php b/wp-admin/includes/plugin.php
index 5873a33466..2668cf5beb 100644
--- a/wp-admin/includes/plugin.php
+++ b/wp-admin/includes/plugin.php
@@ -31,6 +31,8 @@
  *     Network: Optional. Specify "Network: true" to require that a plugin is activated
  *          across all sites in an installation. This will prevent a plugin from being
  *          activated on a single site when Multisite is enabled.
+ *     Requires WP: Optional. Specify the minimum required WordPress version.
+ *     Requires PHP: Optional. Specify the minimum required PHP version.
  *      * / # Remove the space to close comment
  *
  * Some users have issues with opening large files and manipulating the contents
@@ -46,6 +48,7 @@
  * reading.
  *
  * @since 1.5.0
+ * @since 5.2.0 Added `RequiresWP` and `RequiresPHP`.
  *
  * @param string $plugin_file Absolute path to the main plugin file.
  * @param bool   $markup      Optional. If the returned data should have HTML markup applied.
@@ -63,6 +66,8 @@
  *     @type string $TextDomain  Plugin textdomain.
  *     @type string $DomainPath  Plugins relative directory path to .mo files.
  *     @type bool   $Network     Whether the plugin can only be activated network-wide.
+ *     @type string $RequiresWP  Minimum required version of WordPress.
+ *     @type string $RequiresPHP Minimum required version of PHP.
  * }
  */
 function get_plugin_data( $plugin_file, $markup = true, $translate = true ) {
@@ -77,6 +82,8 @@ function get_plugin_data( $plugin_file, $markup = true, $translate = true ) {
 		'TextDomain'  => 'Text Domain',
 		'DomainPath'  => 'Domain Path',
 		'Network'     => 'Network',
+		'RequiresWP'  => 'Requires WP',
+		'RequiresPHP' => 'Requires PHP',
 		// Site Wide Only is deprecated in favor of Network.
 		'_sitewide'   => 'Site Wide Only',
 	);
@@ -213,6 +220,44 @@ function _get_plugin_data_markup_translate( $plugin_file, $plugin_data, $markup
 	return $plugin_data;
 }
 
+/**
+ * Return plugin data for validation with WP version and PHP version.
+ *
+ * Uses get_file_data() to parse local readme.txt.
+ * Valid data in readme.txt takes priority.
+ * If valid data is not in readme.txt check if plugin header `Requires WP` or `Requires PHP` exists and use that.
+ *
+ * @since 5.2.0
+ * @see validate_plugin_requirements()
+ *
+ * @param string $plugin_file Path to the plugin file relative to the plugins directory.
+ *
+ * @return array $plugin_data Array of plugin data for validation.
+ */
+function get_plugin_validation_data( $plugin_file ) {
+	$validation_headers = array(
+		'requires'     => 'requires at least',
+		'requires_php' => 'requires php',
+	);
+	$plugin_data        = null;
+	$readme_file        = WP_PLUGIN_DIR . '/' . dirname( $plugin_file ) . '/readme.txt';
+	if ( file_exists( $readme_file ) ) {
+		$plugin_data = get_file_data( $readme_file, $validation_headers );
+	}
+
+	// Plugin might have `Requires WP` and/or `Requires PHP` headers we can use.
+	$plugin_headers = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_file, false, false );
+
+	$plugin_data['file']           = $plugin_file;
+	$plugin_data['name']           = $plugin_headers['Name'];
+	$plugin_data['requires']       = empty( $plugin_data['requires'] ) ? $plugin_headers['RequiresWP'] : $plugin_data['requires'];
+	$plugin_data['requires_php']   = empty( $plugin_data['requires_php'] ) ? $plugin_headers['RequiresPHP'] : $plugin_data['requires_php'];
+	$plugin_data['wp_compatible']  = wp_is_wp_compatible( $plugin_data['requires'] );
+	$plugin_data['php_compatible'] = wp_is_php_compatible( $plugin_data['requires_php'] );
+
+	return $plugin_data;
+}
+
 /**
  * Get a list of a plugin's files.
  *
@@ -595,6 +640,7 @@ function is_network_only_plugin( $plugin ) {
  * ensure that the success redirection will update the error redirection.
  *
  * @since 2.5.0
+ * @since 5.2.0 Test for WordPress version and PHP version compatibility.
  *
  * @param string $plugin       Path to the plugin file relative to the plugins directory.
  * @param string $redirect     Optional. URL to redirect to.
@@ -619,6 +665,10 @@ function activate_plugin( $plugin, $redirect = '', $network_wide = false, $silen
 		return $valid;
 	}
 
+	if ( ! validate_plugin_requirements( $plugin ) ) {
+		return plugin_validation_error_message( $plugin );
+	}
+
 	if ( ( $network_wide && ! isset( $current[ $plugin ] ) ) || ( ! $network_wide && ! in_array( $plugin, $current ) ) ) {
 		if ( ! empty( $redirect ) ) {
 			wp_redirect( add_query_arg( '_error_nonce', wp_create_nonce( 'plugin-activation-error_' . $plugin ), $redirect ) ); // we'll override this later if the plugin can be included without fatal error
@@ -1058,6 +1108,52 @@ function validate_plugin( $plugin ) {
 	return 0;
 }
 
+/**
+ * Validate the plugin requirements for WP version and PHP version.
+ *
+ * @since 5.2.0
+ * @see activate_plugin()
+ *
+ * @param string $plugin Path to the plugin file relative to the plugins directory.
+ *
+ * @return bool Default to true and if requirements met, false if not.
+ */
+function validate_plugin_requirements( $plugin ) {
+	$plugin_data = get_plugin_validation_data( $plugin );
+
+	return $plugin_data['wp_compatible'] && $plugin_data['php_compatible'];
+}
+
+/**
+ * Return appropriate plugin validation error message.
+ *
+ * @since 5.2.0
+ *
+ * string $plugin_file Path to the plugin file relative to the plugins directory.
+ *
+ * @return WP_Error
+ */
+function plugin_validation_error_message( $plugin_file ) {
+	$plugin_data = get_plugin_validation_data( $plugin_file );
+
+	switch ( $plugin_data ) {
+		case ( ! $plugin_data['wp_compatible'] && ! $plugin_data['php_compatible'] ):
+			$code    = __( 'plugin_wp_php_incompatible' );
+			$message = sprintf( __( 'Current WordPress and PHP versions do not meet minimum requirements for %s.' ), $plugin_data['name'] );
+			break;
+		case ! $plugin_data['php_compatible']:
+			$code    = __( 'plugin_php_incompatible' );
+			$message = sprintf( __( 'Current PHP version does not meet minimum requirements for %s.' ), $plugin_data['name'] );
+			break;
+		case ! $plugin_data['wp_compatible']:
+			$code    = __( 'plugin_wp_incompatible' );
+			$message = sprintf( __( 'Current WordPress version does not meet minimum requirements for %s.' ), $plugin_data['name'] );
+			break;
+	}
+
+	return new WP_Error( $code, $message );
+}
+
 /**
  * Whether the plugin can be uninstalled.
  *
diff --git a/wp-includes/functions.php b/wp-includes/functions.php
index 86cdabe7e0..09e18be372 100644
--- a/wp-includes/functions.php
+++ b/wp-includes/functions.php
@@ -6897,3 +6897,31 @@ function wp_direct_php_update_button() {
 	);
 	echo '</p>';
 }
+
+/**
+ * Check compatibility with current WordPress version.
+ *
+ * @since 5.2.0
+ *
+ * @param string $requires Minimum WordPress version from API.
+ *
+ * @return bool True if is compatible or empty, false if not.
+ */
+function wp_is_wp_compatible( $requires ) {
+	$wp_version = get_bloginfo( 'version' );
+
+	return empty( $requires ) || version_compare( $wp_version, $requires, '>=' );
+}
+
+/**
+ * Check compatibility with current PHP version.
+ *
+ * @since 5.2.0
+ *
+ * @param string $requires Minimum PHP version from API.
+ *
+ * @return bool True if is compatible or empty, false if not.
+ */
+function wp_is_php_compatible( $requires ) {
+	return empty( $requires ) || version_compare( phpversion(), $requires, '>=' );
+}
