Index: src/wp-admin/includes/schema.php
--- src/wp-admin/includes/schema.php
+++ src/wp-admin/includes/schema.php
@@ -1003,6 +1003,20 @@
 
 	$network_id = (int) $network_id;
 
+	/**
+	 * Fires before a network is populated.
+	 *
+	 * @since 6.9.0
+	 *
+	 * @param int    $network_id        ID of network to populate.
+	 * @param string $domain            The domain name for the network.
+	 * @param string $email             Email address for the network administrator.
+	 * @param string $site_name         The name of the network.
+	 * @param string $path              The path to append to the network's domain name.
+	 * @param bool   $subdomain_install Whether the network is a subdomain installation or a subdirectory installation.
+	 */
+	do_action( 'before_populate_network', $network_id, $domain, $email, $site_name, $path, $subdomain_install );
+
 	$errors = new WP_Error();
 	if ( '' === $domain ) {
 		$errors->add( 'empty_domain', __( 'You must provide a domain name.' ) );
@@ -1122,6 +1136,20 @@
 
 		flush_rewrite_rules();
 
+		/**
+		 * Fires after a network is created when converting a single site to multisite.
+		 *
+		 * @since 6.9.0
+		 *
+		 * @param int    $network_id        ID of network created.
+		 * @param string $domain            The domain name for the network.
+		 * @param string $email             Email address for the network administrator.
+		 * @param string $site_name         The name of the network.
+		 * @param string $path              The path to append to the network's domain name.
+		 * @param bool   $subdomain_install Whether the network is a subdomain installation or a subdirectory installation.
+		 */
+		do_action( 'after_upgrade_to_multisite', $network_id, $domain, $email, $site_name, $path, $subdomain_install );
+
 		if ( ! $subdomain_install ) {
 			return true;
 		}
@@ -1168,6 +1196,20 @@
 		}
 	}
 
+	/**
+	 * Fires after a network is fully populated.
+	 *
+	 * @since 6.9.0
+	 *
+	 * @param int    $network_id        ID of network created.
+	 * @param string $domain            The domain name for the network.
+	 * @param string $email             Email address for the network administrator.
+	 * @param string $site_name         The name of the network.
+	 * @param string $path              The path to append to the network's domain name.
+	 * @param bool   $subdomain_install Whether the network is a subdomain installation or a subdirectory installation.
+	 */
+	do_action( 'after_populate_network', $network_id, $domain, $email, $site_name, $path, $subdomain_install );
+
 	return true;
 }
 
Index: tests/phpunit/tests/multisite/populateNetworkHooks.php
--- tests/phpunit/tests/multisite/populateNetworkHooks.php
+++ tests/phpunit/tests/multisite/populateNetworkHooks.php
@@ -0,0 +1,110 @@
+<?php
+
+if ( is_multisite() ) :
+
+	/**
+	 * Tests for the populate_network hooks.
+	 *
+	 * @group ms-network
+	 * @group ms-populate-network
+	 * @group multisite
+	 */
+	class Tests_Multisite_PopulateNetworkHooks extends WP_UnitTestCase {
+		protected $action_counts = array(
+			'before_populate_network'    => 0,
+			'after_upgrade_to_multisite' => 0,
+			'after_populate_network'     => 0,
+		);
+
+		protected $action_args = array();
+
+		/**
+		 * Flag to track if hook was called.
+		 */
+		public $hook_called = false;
+
+		public function hook_action_counter( $network_id, $domain, $email, $site_name, $path, $subdomain_install ) {
+			$action = current_filter();
+			++$this->action_counts[ $action ];
+			$this->action_args[ $action ] = array(
+				'network_id'        => $network_id,
+				'domain'            => $domain,
+				'email'             => $email,
+				'site_name'         => $site_name,
+				'path'              => $path,
+				'subdomain_install' => $subdomain_install,
+			);
+		}
+
+		/**
+		 * Test that the before_populate_network hook fires.
+		 *
+		 * @ticket 27289
+		 */
+		public function test_before_populate_network_hook() {
+			$this->action_counts = array_fill_keys( array_keys( $this->action_counts ), 0 );
+			$this->action_args   = array();
+
+			add_action( 'before_populate_network', array( $this, 'hook_action_counter' ), 10, 6 );
+			add_action( 'after_populate_network', array( $this, 'hook_action_counter' ), 10, 6 );
+
+			$domain     = 'example' . time() . '.org';
+			$network_id = self::factory()->network->create(
+				array(
+					'domain' => $domain,
+					'path'   => '/',
+				)
+			);
+
+			$this->assertSame( 1, $this->action_counts['before_populate_network'], 'before_populate_network action should fire once' );
+			$this->assertSame( 1, $this->action_counts['after_populate_network'], 'after_populate_network action should fire once' );
+
+			$this->assertEquals( $network_id, $this->action_args['before_populate_network']['network_id'], 'Network ID should match in before_populate_network hook' );
+			$this->assertEquals( $domain, $this->action_args['before_populate_network']['domain'], 'Domain should match in before_populate_network hook' );
+			$this->assertEquals( $network_id, $this->action_args['after_populate_network']['network_id'], 'Network ID should match in after_populate_network hook' );
+			$this->assertEquals( $domain, $this->action_args['after_populate_network']['domain'], 'Domain should match in after_populate_network hook' );
+
+			remove_action( 'before_populate_network', array( $this, 'hook_action_counter' ), 10 );
+			remove_action( 'after_populate_network', array( $this, 'hook_action_counter' ), 10 );
+
+			global $wpdb;
+			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->sitemeta} WHERE site_id = %d", $network_id ) );
+			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->site} WHERE id = %d", $network_id ) );
+		}
+
+		/**
+		 * Test that the hooks can modify parameters.
+		 *
+		 * @ticket 27289
+		 */
+		public function test_populate_network_hook_filter() {
+			$this->hook_called = false;
+
+			add_action( 'before_populate_network', array( $this, 'modify_domain_hook' ), 10, 6 );
+
+			$domain     = 'example' . time() . '.org';
+			$network_id = self::factory()->network->create(
+				array(
+					'domain' => $domain,
+					'path'   => '/',
+				)
+			);
+
+			$this->assertTrue( $this->hook_called, 'The modify_domain_hook action should have been called' );
+
+			remove_action( 'before_populate_network', array( $this, 'modify_domain_hook' ), 10 );
+
+			global $wpdb;
+			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->sitemeta} WHERE site_id = %d", $network_id ) );
+			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->site} WHERE id = %d", $network_id ) );
+		}
+
+		/**
+		 * Action to track if hooks are being executed.
+		 */
+		public function modify_domain_hook( $network_id, $domain, $email, $site_name, $path, $subdomain_install ) {
+			$this->hook_called = true;
+		}
+	}
+
+endif;
