diff --git src/wp-includes/class-wp-network-query.php src/wp-includes/class-wp-network-query.php
index d381e60117..13c6cd094a 100644
--- src/wp-includes/class-wp-network-query.php
+++ src/wp-includes/class-wp-network-query.php
@@ -197,32 +197,51 @@ class WP_Network_Query {
 		 */
 		do_action_ref_array( 'pre_get_networks', array( &$this ) );
 
-		// $args can include anything. Only use the args defined in the query_var_defaults to compute the key.
-		$_args = wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) );
+		$network_ids = null;
 
-		// Ignore the $fields argument as the queried result will be the same regardless.
-		unset( $_args['fields'] );
+		/**
+		 * Filter the sites array before the query takes place.
+		 *
+		 * Return a non-null value to bypass WordPress's default site queries.
+		 *
+		 *
+		 * @since 5.2.0
+		 *
+		 * @param array|null       $site_ids Return an array of site data to short-circuit WP's site query,
+		 *                                   or null to allow WP to run its normal queries.
+		 * @param WP_Network_Query $this     The WP_Network_Query instance, passed by reference.
+		 */
+		$network_ids = apply_filters_ref_array( 'networks_pre_query', array( $network_ids, &$this ) );
 
-		$key          = md5( serialize( $_args ) );
-		$last_changed = wp_cache_get_last_changed( 'networks' );
+		if ( null === $network_ids ) {
 
-		$cache_key   = "get_network_ids:$key:$last_changed";
-		$cache_value = wp_cache_get( $cache_key, 'networks' );
+			// $args can include anything. Only use the args defined in the query_var_defaults to compute the key.
+			$_args = wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) );
 
-		if ( false === $cache_value ) {
-			$network_ids = $this->get_network_ids();
-			if ( $network_ids ) {
-				$this->set_found_networks();
-			}
+			// Ignore the $fields argument as the queried result will be the same regardless.
+			unset( $_args['fields'] );
 
-			$cache_value = array(
-				'network_ids'    => $network_ids,
-				'found_networks' => $this->found_networks,
-			);
-			wp_cache_add( $cache_key, $cache_value, 'networks' );
-		} else {
-			$network_ids          = $cache_value['network_ids'];
-			$this->found_networks = $cache_value['found_networks'];
+			$key          = md5( serialize( $_args ) );
+			$last_changed = wp_cache_get_last_changed( 'networks' );
+
+			$cache_key   = "get_network_ids:$key:$last_changed";
+			$cache_value = wp_cache_get( $cache_key, 'networks' );
+
+			if ( false === $cache_value ) {
+				$network_ids = $this->get_network_ids();
+				if ( $network_ids ) {
+					$this->set_found_networks();
+				}
+
+				$cache_value = array(
+					'network_ids'    => $network_ids,
+					'found_networks' => $this->found_networks,
+				);
+				wp_cache_add( $cache_key, $cache_value, 'networks' );
+			} else {
+				$network_ids          = $cache_value['network_ids'];
+				$this->found_networks = $cache_value['found_networks'];
+			}
 		}
 
 		if ( $this->found_networks && $this->query_vars['number'] ) {
diff --git src/wp-includes/class-wp-site-query.php src/wp-includes/class-wp-site-query.php
index bc3e3519a3..507c611e24 100644
--- src/wp-includes/class-wp-site-query.php
+++ src/wp-includes/class-wp-site-query.php
@@ -288,32 +288,51 @@ class WP_Site_Query {
 			$this->meta_query_clauses = $this->meta_query->get_sql( 'blog', $wpdb->blogs, 'blog_id', $this );
 		}
 
-		// $args can include anything. Only use the args defined in the query_var_defaults to compute the key.
-		$_args = wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) );
+		$site_ids = null;
 
-		// Ignore the $fields argument as the queried result will be the same regardless.
-		unset( $_args['fields'] );
+		/**
+		 * Filter the sites array before the query takes place.
+		 *
+		 * Return a non-null value to bypass WordPress's default site queries.
+		 *
+		 *
+		 * @since 5.2.0
+		 *
+		 * @param array|null    $site_ids Return an array of site data to short-circuit WP's site query,
+		 *                                or null to allow WP to run its normal queries.
+		 * @param WP_Site_Query $this The WP_Site_Query instance, passed by reference.
+		 */
+		$site_ids = apply_filters_ref_array( 'sites_pre_query', array( $site_ids, &$this ) );
 
-		$key          = md5( serialize( $_args ) );
-		$last_changed = wp_cache_get_last_changed( 'sites' );
+		if ( null === $site_ids ) {
 
-		$cache_key   = "get_sites:$key:$last_changed";
-		$cache_value = wp_cache_get( $cache_key, 'sites' );
+			// $args can include anything. Only use the args defined in the query_var_defaults to compute the key.
+			$_args = wp_array_slice_assoc( $this->query_vars, array_keys( $this->query_var_defaults ) );
 
-		if ( false === $cache_value ) {
-			$site_ids = $this->get_site_ids();
-			if ( $site_ids ) {
-				$this->set_found_sites();
-			}
+			// Ignore the $fields argument as the queried result will be the same regardless.
+			unset( $_args['fields'] );
 
-			$cache_value = array(
-				'site_ids'    => $site_ids,
-				'found_sites' => $this->found_sites,
-			);
-			wp_cache_add( $cache_key, $cache_value, 'sites' );
-		} else {
-			$site_ids          = $cache_value['site_ids'];
-			$this->found_sites = $cache_value['found_sites'];
+			$key          = md5( serialize( $_args ) );
+			$last_changed = wp_cache_get_last_changed( 'sites' );
+
+			$cache_key   = "get_sites:$key:$last_changed";
+			$cache_value = wp_cache_get( $cache_key, 'sites' );
+
+			if ( false === $cache_value ) {
+				$site_ids = $this->get_site_ids();
+				if ( $site_ids ) {
+					$this->set_found_sites();
+				}
+
+				$cache_value = array(
+					'site_ids'    => $site_ids,
+					'found_sites' => $this->found_sites,
+				);
+				wp_cache_add( $cache_key, $cache_value, 'sites' );
+			} else {
+				$site_ids          = $cache_value['site_ids'];
+				$this->found_sites = $cache_value['found_sites'];
+			}
 		}
 
 		if ( $this->found_sites && $this->query_vars['number'] ) {
diff --git tests/phpunit/tests/multisite/networkQuery.php tests/phpunit/tests/multisite/networkQuery.php
index 6ec7f16c42..0b48fdc8d5 100644
--- tests/phpunit/tests/multisite/networkQuery.php
+++ tests/phpunit/tests/multisite/networkQuery.php
@@ -522,6 +522,41 @@ if ( is_multisite() ) :
 			);
 			$this->assertEquals( $number_of_queries + 1, $wpdb->num_queries );
 		}
+
+		/**
+		 * @ticket 45749
+		 */
+		public function test_networks_pre_query_filter_should_bypass_database_query() {
+			global $wpdb;
+
+			add_filter( 'networks_pre_query', array( __CLASS__, 'filter_networks_pre_query' ), 10, 2 );
+
+			$num_queries = $wpdb->num_queries;
+
+			$q       = new WP_Network_Query();
+			$results = $q->query(
+				array(
+					'fields' => 'ids',
+				)
+			);
+
+			remove_filter( 'networks_pre_query', array( __CLASS__, 'filter_networks_pre_query' ), 10, 2 );
+
+			// Make sure no queries were executed.
+			$this->assertSame( $num_queries, $wpdb->num_queries );
+
+			// We manually inserted a non-existing site and overrode the results with it.
+			$this->assertSame( array( 555 ), $q->networks );
+
+			// Make sure manually setting total_users doesn't get overwritten.
+			$this->assertEquals( 1, $q->found_networks );
+		}
+
+		public static function filter_networks_pre_query( $networks, $query ) {
+			$query->found_networks = 1;
+
+			return array( 555 );
+		}
 	}
 
 endif;
diff --git tests/phpunit/tests/multisite/siteQuery.php tests/phpunit/tests/multisite/siteQuery.php
index bac9269ff4..c17f977932 100644
--- tests/phpunit/tests/multisite/siteQuery.php
+++ tests/phpunit/tests/multisite/siteQuery.php
@@ -911,6 +911,42 @@ if ( is_multisite() ) :
 			}
 		}
 
+
+		/**
+		 * @ticket 45749
+		 */
+		public function test_sites_pre_query_filter_should_bypass_database_query() {
+			global $wpdb;
+
+			add_filter( 'sites_pre_query', array( __CLASS__, 'filter_sites_pre_query' ), 10, 2 );
+
+			$num_queries = $wpdb->num_queries;
+
+			$q       = new WP_Site_Query();
+			$results = $q->query(
+				array(
+					'fields' => 'ids',
+				)
+			);
+
+			remove_filter( 'sites_pre_query', array( __CLASS__, 'filter_sites_pre_query' ), 10, 2 );
+
+			// Make sure no queries were executed.
+			$this->assertSame( $num_queries, $wpdb->num_queries );
+
+			// We manually inserted a non-existing site and overrode the results with it.
+			$this->assertSame( array( 555 ), $q->sites );
+
+			// Make sure manually setting total_users doesn't get overwritten.
+			$this->assertEquals( 1, $q->found_sites );
+		}
+
+		public static function filter_sites_pre_query( $sites, $query ) {
+			$query->found_sites = 1;
+
+			return array( 555 );
+		}
+
 		public function data_wp_site_query_meta_query() {
 			return array(
 				array(
