diff --git src/wp-includes/class-wp-customize-manager.php src/wp-includes/class-wp-customize-manager.php
index 54c3f0e..6902ae3 100644
--- src/wp-includes/class-wp-customize-manager.php
+++ src/wp-includes/class-wp-customize-manager.php
@@ -523,8 +523,12 @@ final class WP_Customize_Manager {
 			}
 		}
 
-		// Import theme starter content for fresh installs when landing in the customizer and no existing changeset loaded.
-		if ( get_option( 'fresh_site' ) && 'customize.php' === $pagenow && ! $this->changeset_post_id() ) {
+		/*
+		 * Import theme starter content for fresh installs when landing in the customizer.
+		 * Import starter content at after_setup_theme:100 so that any
+		 * add_theme_support( 'starter-content' ) calls will have been made.
+		 */
+		if ( get_option( 'fresh_site' ) && 'customize.php' === $pagenow ) {
 			add_action( 'after_setup_theme', array( $this, 'import_theme_starter_content' ), 100 );
 		}
 
@@ -885,7 +889,16 @@ final class WP_Customize_Manager {
 	}
 
 	/**
-	 * Import theme starter content into post values.
+	 * Starter content setting IDs.
+	 *
+	 * @since 4.7.0
+	 * @access private
+	 * @var array
+	 */
+	protected $starter_content_settings_ids = array();
+
+	/**
+	 * Import theme starter content into the customized state.
 	 *
 	 * @since 4.7.0
 	 * @access public
@@ -897,6 +910,11 @@ final class WP_Customize_Manager {
 			$starter_content = get_theme_starter_content();
 		}
 
+		$changeset_data = array();
+		if ( $this->changeset_post_id() ) {
+			$changeset_data = $this->get_changeset_post_data( $this->changeset_post_id() );
+		}
+
 		$sidebars_widgets = isset( $starter_content['widgets'] ) && ! empty( $this->widgets ) ? $starter_content['widgets'] : array();
 		$posts = isset( $starter_content['posts'] ) && ! empty( $this->nav_menus ) ? $starter_content['posts'] : array();
 		$options = isset( $starter_content['options'] ) ? $starter_content['options'] : array();
@@ -932,35 +950,73 @@ final class WP_Customize_Manager {
 				$widget_id = sprintf( '%s-%d', $id_base, $max_widget_numbers[ $id_base ] );
 				$setting_id = sprintf( 'widget_%s[%d]', $id_base, $max_widget_numbers[ $id_base ] );
 
-				$class = 'WP_Customize_Setting';
-
-				/** This filter is documented in wp-includes/class-wp-customize-manager.php */
-				$args = apply_filters( 'customize_dynamic_setting_args', false, $setting_id );
-
-				if ( false !== $args ) {
-
-					/** This filter is documented in wp-includes/class-wp-customize-manager.php */
-					$class = apply_filters( 'customize_dynamic_setting_class', $class, $setting_id, $args );
-
-					$setting = new $class( $this, $setting_id, $args );
-					$setting_value = call_user_func( $setting->sanitize_js_callback, $instance, $setting );
+				$setting_value = $this->widgets->sanitize_widget_js_instance( $instance );
+				if ( empty( $changeset_data[ $setting_id ] ) || ! empty( $changeset_data[ $setting_id ]['starter_content'] ) ) {
 					$this->set_post_value( $setting_id, $setting_value );
-					$sidebar_widget_ids[] = $widget_id;
+					$this->starter_content_settings_ids[] = $setting_id;
 				}
+				$sidebar_widget_ids[] = $widget_id;
 			}
 
-			$this->set_post_value( sprintf( 'sidebars_widgets[%s]', $sidebar_id ), $sidebar_widget_ids );
+			$setting_id = sprintf( 'sidebars_widgets[%s]', $sidebar_id );
+			if ( empty( $changeset_data[ $setting_id ] ) || ! empty( $changeset_data[ $setting_id ]['starter_content'] ) ) {
+				$this->set_post_value( $setting_id, $sidebar_widget_ids );
+				$this->starter_content_settings_ids[] = $setting_id;
+			}
 		}
 
 		// Posts & pages.
 		if ( ! empty( $posts ) ) {
+			$nav_menus_created_posts = array();
+			if ( ! empty( $changeset_data['nav_menus_created_posts']['value'] ) ) {
+				$nav_menus_created_posts = $changeset_data['nav_menus_created_posts']['value'];
+			}
+
+			$existing_posts = array();
+			if ( ! empty( $nav_menus_created_posts ) ) {
+				$existing_posts_query = new WP_Query( array(
+					'post__in' => $nav_menus_created_posts,
+					'post_status' => 'auto-draft',
+					'post_type' => 'any',
+					'number' => -1,
+				) );
+				foreach ( $existing_posts_query->posts as $existing_post ) {
+					$existing_posts[ $existing_post->post_type . ':' . $existing_post->post_name ] = $existing_post;
+				}
+			}
+
 			foreach ( array_keys( $posts ) as $post_symbol ) {
+				if ( empty( $posts[ $post_symbol ]['post_type'] ) ) {
+					continue;
+				}
+				$post_type = $posts[ $post_symbol ]['post_type'];
+				if ( ! empty( $posts[ $post_symbol ]['post_name'] ) ) {
+					$post_name = $posts[ $post_symbol ]['post_name'];
+				} elseif ( ! empty( $posts[ $post_symbol ]['post_title'] ) ) {
+					$post_name = sanitize_title( $posts[ $post_symbol ]['post_title'] );
+				} else {
+					continue;
+				}
+
+				// Use existing auto-draft post if one already exists with the same type and name.
+				if ( isset( $existing_posts[ $post_type . ':' . $post_name ] ) ) {
+					$posts[ $post_symbol ] = $existing_posts[ $post_type . ':' . $post_name ]->ID;
+					continue;
+				}
+
 				$r = $this->nav_menus->insert_auto_draft_post( $posts[ $post_symbol ] );
 				if ( $r instanceof WP_Post ) {
 					$posts[ $post_symbol ]['ID'] = $r->ID;
 				}
 			}
-			$this->set_post_value( 'nav_menus_created_posts', wp_list_pluck( $posts, 'ID' ) ); // This is why nav_menus component is dependency for adding posts.
+
+			// The nav_menus_created_posts setting is why nav_menus component is dependency for adding posts.
+			$setting_id = 'nav_menus_created_posts';
+			if ( empty( $changeset_data[ $setting_id ] ) || ! empty( $changeset_data[ $setting_id ]['starter_content'] ) ) {
+				$nav_menus_created_posts = array_unique( array_merge( $nav_menus_created_posts, wp_list_pluck( $posts, 'ID' ) ) );
+				$this->set_post_value( $setting_id, $nav_menus_created_posts );
+				$this->starter_content_settings_ids[] = $setting_id;
+			}
 		}
 
 		// Nav menus.
@@ -968,9 +1024,14 @@ final class WP_Customize_Manager {
 		foreach ( $nav_menus as $nav_menu_location => $nav_menu ) {
 			$nav_menu_term_id = $placeholder_id--;
 			$nav_menu_setting_id = sprintf( 'nav_menu[%d]', $nav_menu_term_id );
-			$this->set_post_value( $nav_menu_setting_id, array(
-				'name' => isset( $nav_menu['name'] ) ? $nav_menu['name'] : $nav_menu_location,
-			) );
+
+
+			if ( empty( $changeset_data[ $nav_menu_setting_id ] ) || ! empty( $changeset_data[ $nav_menu_setting_id ]['starter_content'] ) ) {
+				$this->set_post_value( $nav_menu_setting_id, array(
+					'name' => isset( $nav_menu['name'] ) ? $nav_menu['name'] : $nav_menu_location,
+				) );
+				$this->starter_content_settings_ids[] = $nav_menu_setting_id;
+			}
 
 			// @todo Add support for menu_item_parent.
 			$position = 0;
@@ -994,10 +1055,18 @@ final class WP_Customize_Manager {
 				} else {
 					$nav_menu_item['object_id'] = 0;
 				}
-				$this->set_post_value( $nav_menu_item_setting_id, $nav_menu_item );
+
+				if ( empty( $changeset_data[ $nav_menu_item_setting_id ] ) || ! empty( $changeset_data[ $nav_menu_item_setting_id ]['starter_content'] ) ) {
+					$this->set_post_value( $nav_menu_item_setting_id, $nav_menu_item );
+					$this->starter_content_settings_ids[] = $nav_menu_item_setting_id;
+				}
 			}
 
-			$this->set_post_value( sprintf( 'nav_menu_locations[%s]', $nav_menu_location ), $nav_menu_term_id );
+			$setting_id = sprintf( 'nav_menu_locations[%s]', $nav_menu_location );
+			if ( empty( $changeset_data[ $setting_id ] ) || ! empty( $changeset_data[ $setting_id ]['starter_content'] ) ) {
+				$this->set_post_value( $setting_id, $nav_menu_term_id );
+				$this->starter_content_settings_ids[] = $setting_id;
+			}
 		}
 
 		// Options.
@@ -1005,7 +1074,11 @@ final class WP_Customize_Manager {
 			if ( preg_match( '/^{{(?P<symbol>.+)}}$/', $value, $matches ) && isset( $posts[ $matches['symbol'] ] ) ) {
 				$value = $posts[ $matches['symbol'] ]['ID'];
 			}
-			$this->set_post_value( $name, $value );
+
+			if ( empty( $changeset_data[ $name ] ) || ! empty( $changeset_data[ $name ]['starter_content'] ) ) {
+				$this->set_post_value( $name, $value );
+				$this->starter_content_settings_ids[] = $name;
+			}
 		}
 
 		// Theme mods.
@@ -1013,11 +1086,41 @@ final class WP_Customize_Manager {
 			if ( preg_match( '/^{{(?P<symbol>.+)}}$/', $value, $matches ) && isset( $posts[ $matches['symbol'] ] ) ) {
 				$value = $posts[ $matches['symbol'] ]['ID'];
 			}
-			$this->set_post_value( $name, $value );
+
+			if ( empty( $changeset_data[ $name ] ) || ! empty( $changeset_data[ $name ]['starter_content'] ) ) {
+				$this->set_post_value( $name, $value );
+				$this->starter_content_settings_ids[] = $name;
+			}
+		}
+
+		if ( ! empty( $this->starter_content_settings_ids ) ) {
+			if ( did_action( 'customize_register' ) ) {
+				$this->_save_starter_content_changeset();
+			} else {
+				add_action( 'customize_register', array( $this, '_save_starter_content_changeset' ), 1000 );
+			}
 		}
 	}
 
 	/**
+	 * Save starter content changeset.
+	 *
+	 * @since 4.7.0
+	 * @access private
+	 */
+	public function _save_starter_content_changeset() {
+
+		if ( empty( $this->starter_content_settings_ids ) ) {
+			return;
+		}
+
+		$this->save_changeset_post( array(
+			'data' => array_fill_keys( $this->starter_content_settings_ids, array( 'starter_content' => true ) ),
+			'starter_content' => true,
+		) );
+	}
+
+	/**
 	 * Get dirty pre-sanitized setting values in the current customized state.
 	 *
 	 * The returned array consists of a merge of three sources:
@@ -1823,11 +1926,12 @@ final class WP_Customize_Manager {
 	 * @param array $args {
 	 *     Args for changeset post.
 	 *
-	 *     @type array  $data     Optional additional changeset data. Values will be merged on top of any existing post values.
-	 *     @type string $status   Post status. Optional. If supplied, the save will be transactional and a post revision will be allowed.
-	 *     @type string $title    Post title. Optional.
-	 *     @type string $date_gmt Date in GMT. Optional.
-	 *     @type int    $user_id  ID for user who is saving the changeset. Optional, defaults to the current user ID.
+	 *     @type array  $data            Optional additional changeset data. Values will be merged on top of any existing post values.
+	 *     @type string $status          Post status. Optional. If supplied, the save will be transactional and a post revision will be allowed.
+	 *     @type string $title           Post title. Optional.
+	 *     @type string $date_gmt        Date in GMT. Optional.
+	 *     @type int    $user_id         ID for user who is saving the changeset. Optional, defaults to the current user ID.
+	 *     @type bool   $starter_content Whether the data is starter content. If false (default), then $starter_content will be cleared for any $data being saved.
 	 * }
 	 *
 	 * @return array|WP_Error Returns array on success and WP_Error with array data on error.
@@ -1841,6 +1945,7 @@ final class WP_Customize_Manager {
 				'data' => array(),
 				'date_gmt' => null,
 				'user_id' => get_current_user_id(),
+				'starter_content' => false,
 			),
 			$args
 		);
@@ -1977,6 +2082,7 @@ final class WP_Customize_Manager {
 				if ( ! isset( $data[ $changeset_setting_id ] ) ) {
 					$data[ $changeset_setting_id ] = array();
 				}
+
 				$data[ $changeset_setting_id ] = array_merge(
 					$data[ $changeset_setting_id ],
 					$setting_params,
@@ -1985,6 +2091,11 @@ final class WP_Customize_Manager {
 						'user_id' => $args['user_id'],
 					)
 				);
+
+				// Clear starter_content flag in data if changeset is not explicitly being updated for starter content.
+				if ( empty( $args['starter_content'] ) ) {
+					unset( $data[ $changeset_setting_id ]['starter_content'] );
+				}
 			}
 		}
 
diff --git src/wp-includes/theme.php src/wp-includes/theme.php
index 66e10b2..606c0db 100644
--- src/wp-includes/theme.php
+++ src/wp-includes/theme.php
@@ -1792,26 +1792,26 @@ function get_theme_starter_content() {
 		$config = array();
 	}
 
-	$core_content = array (
+	$core_content = array(
 		'widgets' => array(
-			'text_business_info' => array ( 'text', array (
+			'text_business_info' => array( 'text', array(
 				'title' => _x( 'Find Us', 'Theme starter content' ),
-				'text' => join( '', array (
+				'text' => join( '', array(
 					'<p><strong>' . _x( 'Address', 'Theme starter content' ) . '</strong><br />',
 					_x( '123 Main Street', 'Theme starter content' ) . '<br />' . _x( 'New York, NY 10001', 'Theme starter content' ) . '</p>',
 					'<p><strong>' . _x( 'Hours', 'Theme starter content' ) . '</strong><br />',
 					_x( 'Monday&mdash;Friday: 9:00AM&ndash;5:00PM', 'Theme starter content' ) . '<br />' . _x( 'Saturday &amp; Sunday: 11:00AM&ndash;3:00PM', 'Theme starter content' ) . '</p>'
 				) ),
 			) ),
-			'search' => array ( 'search', array (
+			'search' => array( 'search', array(
 				'title' => _x( 'Site Search', 'Theme starter content' ),
 			) ),
-			'text_credits' => array ( 'text', array (
+			'text_credits' => array( 'text', array(
 				'title' => _x( 'Site Credits', 'Theme starter content' ),
 				'text' => sprintf( _x( 'This site was created on %s', 'Theme starter content' ), get_date_from_gmt( current_time( 'mysql', 1 ), 'c' ) ),
 			) ),
 		),
-		'nav_menus' => array (
+		'nav_menus' => array(
 			'page_home' => array(
 				'type' => 'post_type',
 				'object' => 'page',
@@ -1887,48 +1887,53 @@ function get_theme_starter_content() {
 
 	foreach ( $config as $type => $args ) {
 		switch( $type ) {
-			// Use options and theme_mods as-is
+			// Use options and theme_mods as-is.
 			case 'options' :
 			case 'theme_mods' :
 				$content[ $type ] = $config[ $type ];
 				break;
 
-			// Widgets are an extra level down due to groupings
+			// Widgets are grouped into sidebars.
 			case 'widgets' :
-				foreach ( $config[ $type ] as $group => $items ) {
-					foreach ( $items as $id ) {
-						if ( ! empty( $core_content[ $type ] ) && ! empty( $core_content[ $type ][ $id ] ) ) {
-							$content[ $type ][ $group ][ $id ] = $core_content[ $type ][ $id ];
+				foreach ( $config[ $type ] as $sidebar_id => $widgets ) {
+					foreach ( $widgets as $widget ) {
+						if ( is_array( $widget ) ) {
+							$content[ $type ][ $sidebar_id ][] = $widget;
+						} elseif ( is_string( $widget ) && ! empty( $core_content[ $type ] ) && ! empty( $core_content[ $type ][ $widget ] ) ) {
+							$content[ $type ][ $sidebar_id ][] = $core_content[ $type ][ $widget ];
 						}
 					}
 				}
 				break;
 
-			// And nav menus are yet another level down
+			// And nav menu items are grouped into nav menus.
 			case 'nav_menus' :
-				foreach ( $config[ $type ] as $group => $args2 ) {
-					// Menu groups need a name
-					if ( empty( $args['name'] ) ) {
-						$args2['name'] = $group;
+				foreach ( $config[ $type ] as $nav_menu_location => $nav_menu ) {
+
+					// Ensure nav menus get a name.
+					if ( empty( $nav_menu['name'] ) ) {
+						$nav_menu['name'] = $nav_menu_location;
 					}
 
-					$content[ $type ][ $group ]['name'] = $args2['name'];
+					$content[ $type ][ $nav_menu_location ]['name'] = $nav_menu['name'];
 
-					// Do we need to check if this is empty?
-					foreach ( $args2['items'] as $id ) {
-						if ( ! empty( $core_content[ $type ] ) && ! empty( $core_content[ $type ][ $id ] ) ) {
-							$content[ $type ][ $group ]['items'][ $id ] = $core_content[ $type ][ $id ];
+					foreach ( $nav_menu['items'] as $nav_menu_item ) {
+						if ( is_array( $nav_menu_item ) ) {
+							$content[ $type ][ $nav_menu_location ]['items'][] = $nav_menu_item;
+						} elseif ( is_string( $nav_menu_item ) && ! empty( $core_content[ $type ] ) && ! empty( $core_content[ $type ][ $nav_menu_item ] ) ) {
+							$content[ $type ][ $nav_menu_location ]['items'][] = $core_content[ $type ][ $nav_menu_item ];
 						}
 					}
 				}
 				break;
 
-
-			// Everything else should map at the next level
+			// Everything else should map at the next level.
 			default :
-				foreach( $config[ $type ] as $id ) {
-					if ( ! empty( $core_content[ $type ] ) && ! empty( $core_content[ $type ][ $id ] ) ) {
-						$content[ $type ][ $id ] = $core_content[ $type ][ $id ];
+				foreach( $config[ $type ] as $i => $item ) {
+					if ( is_array( $item ) ) {
+						$content[ $type ][ $i ] = $item;
+					} elseif ( is_string( $item ) && ! empty( $core_content[ $type ] ) && ! empty( $core_content[ $type ][ $item ] ) ) {
+						$content[ $type ][ $item ] = $core_content[ $type ][ $item ];
 					}
 				}
 				break;
