Site Editor and Global Styles

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This sub-section’s WordPress bibliography lists the reference material consulted while preparing these pages.

The Site Editor (Appearance > Editor) is where the owner of a site that runs a block theme designs the whole site with blocks: the header and footer, the layout of every kind of page, the menus, and the site-wide look (colours, fonts, spacing) that the Styles panel controls. It replaces the Customizer, the Menus screen and the Widgets screen of classic themes. This page describes it from the site owner’s side: what each screen does, what changed in recent releases, and, because it explains most surprises, where every change is stored and how it is reset or carried to another site.

This page was written against WordPress 7.1.3 (released 2026-10-06; 7.1.4, a security release of 2026-10-28, is the version to run in production), PHP 7.4 minimum and 8.3+ recommended, theme.json version 3, the Twenty Twenty-Five theme 1.5 bundled with 7.1, and WP-CLI 2.12.0, and checked on 2026-10-28. The Gutenberg plugin (24.2.0) previews editor changes before they reach core; nothing on this page requires it. WordPress 7.2 is in beta and planned for 2026-12-08; it does not change the storage model described here so far. Every stored value and result below comes from a fresh 7.1.3 installation with Twenty Twenty-Five active, edited through the same REST endpoints the Site Editor calls.

Writing a theme’s theme.json is a theme developer’s task, covered in theme.json Settings and theme.json Styles; template files are covered in Templates and Template Parts. This page only shows the JSON that the Site Editor writes on the owner’s behalf.

Who can use it and when it appears

The Site Editor needs a block theme: a theme with a templates/index.html file. Block themes and the Site Editor arrived in WordPress 5.9 (January 2022); older books that place "full site editing" in 5.4 to 5.6 confuse it with the Gutenberg plugin’s experiments. The Appearance menu changes with the active theme:

Active theme Appearance menu

Block theme

Themes, Editor (site-editor.php), Fonts. Customize is hidden unless a plugin or the theme hooks customize_register; Menus and Widgets do not appear.

Classic theme that declares editor-styles or ships a theme.json (since 6.8)

Themes, Design (the Style Book and the Patterns screen), Fonts, Customize, Menus and Widgets when supported

Other classic theme

Themes, Patterns, Fonts, Customize, Menus and Widgets when supported

All of these entries require the edit_theme_options capability, which only Administrators have by default (and on Multisite, site Administrators too). Editing CSS additionally requires edit_css, which maps to unfiltered_html: it is denied to everyone when DISALLOW_UNFILTERED_HTML is true, and to everyone but Super Admins on Multisite. Users without it do not see the Additional CSS editors at all.

The editor itself is a single-page application over the REST API; every screen below saves through a /wp/v2/ endpoint, which is why the same changes can be scripted, as the examples on this page do.

The Site Editor screens

Opening Appearance > Editor shows a navigation sidebar and a preview of the home page (the page chosen in Settings > Reading). In 7.1 the sidebar lists six sections, and the WordPress admin bar now stays visible on every Site Editor screen (hidden only in Distraction Free mode).

Section What the site owner does there Since

Identity

Site title, tagline, logo and icon in one panel, without editing blocks. They are the ordinary blogname, blogdescription, site_logo and site_icon options.

documented with 7.1

Styles

Site-wide colours, typography, layout, background, shadows, block defaults, style variations, revisions, Additional CSS (all described below)

5.9

Pages

List, create and edit pages inside the Site Editor, with their template shown around the content

6.3

Navigation

List all menus, rename, duplicate or delete them, reorder items, open a menu in focus mode

6.3

Patterns

Synced and unsynced patterns, and the template parts (header, footer, general, navigation overlay)

6.3 (template parts moved here from their own entry)

Templates

The theme’s templates and any custom ones; add a template for a page type, a post type or a single page

5.9

The top bar of an open template or page has the block inserter, undo and redo, List View, the Command Palette (Ctrl+K or Cmd+K), Preview (device sizes, and in 7.1 the switch to edit responsive styles), the Settings and Styles toggles, Save, and the More options menu (top toolbar, spotlight mode, distraction free, visual and code editor, Tools > Export, keyboard shortcuts, Preferences). Save opens a list of every entity that changed (styles, templates, template parts, navigation menus, synced patterns), and each can be saved or left unsaved separately.

Zoom out (the toggle next to the Settings button, or Ctrl+Shift+0 / Shift+Cmd+0) shrinks the canvas to show whole sections. The editor switches into it when a pattern is inserted into a template, which makes it the natural view for rearranging a home page section by section. In the post editor it is available when "Show template" is on.

Two block-level helpers save time: Copy styles / Paste styles in a block’s options menu (6.2) copies one block’s design to another of the same type, and Advanced > Apply globally (6.2) turns one block’s local design into the site-wide default for its block type, that is, into Global Styles.

Styles

Styles (the half-filled circle icon, or the Styles section of the sidebar) sets the site-wide design. Every change is a value in the user layer of global styles, which is merged on top of WordPress’s defaults and the theme’s theme.json. Its panels are:

Panel Settings

Typography

Font family, size, appearance (weight and style), line height, letter spacing, decoration, case and orientation for Text, Links, Headings (all or H1 to H6), Captions and Buttons; the font size presets (S to XXL) can be edited, including fluid minimum and maximum values; Fonts opens the Font Library

Colors

The palette (theme colours, default colours if the theme allows them, and custom colours and gradients the owner adds), plus the colours of Background, Text, Links (with hover), Headings and Buttons; since 6.7 a set of preset palettes to start from

Layout

Content width and wide width, root padding (linked or per side, as values or spacing presets) and block spacing

Background

A site background image with its size, position and repeat

Shadows

The theme’s shadow presets (Natural, Deep, Sharp, Outlined, Crisp in Twenty Twenty-Five) and custom shadows applied to buttons and other blocks

Blocks

Defaults for every block type: colours, typography, spacing, border, radius, duotone, shadow, its registered block style variations, and Additional block CSS

What the theme does not allow does not appear: a theme can switch off custom colours, custom font sizes or the default palette in theme.json (settings.color.custom, settings.typography.customFontSize, settings.color.defaultPalette), and those controls disappear from Styles.

A block that has its own colour or size set in the block sidebar keeps it; Styles changes only blocks that have no local value. This is the most common "Styles does not work" report, and Apply globally or clearing the block’s local value is the fix.

Style variations and presets

A style variation is a JSON file in the theme’s styles/ folder that replaces the theme’s whole look. Styles > Browse styles lists them (since 6.0). Since 6.6 themes can also ship colour-only variations (styles/colors/*.json) and typography-only variations (styles/typography/*.json, typesets), which the Colors and Typography panels list as palettes and font pairings, so a site owner can combine one theme variation’s colours with another’s fonts.

Twenty Twenty-Five ships eight full variations (Evening, Noon, Dusk, Afternoon, Twilight, Morning, Sunrise, Midnight), eight matching colour palettes, seven typesets (for example "Literata & Ysabeau Office"), five section styles and four block styles. The list comes from the theme’s variations endpoint:

<?php
// wp eval-file list-variations.php --user=admin
$response = rest_do_request( new WP_REST_Request( 'GET', '/wp/v2/global-styles/themes/twentytwentyfive/variations' ) );
foreach ( $response->get_data() as $variation ) {
	WP_CLI::log( $variation['title'] );
}

Choosing a variation copies its values into the user layer; WordPress does not store "use variation Noon". Two consequences follow. Further changes are made on top of the copy, and a theme update that changes styles/02-noon.json does not reach a site that applied Noon before the update until the owner applies the variation again (or resets). Building variations and section styles is covered in Style Variations and Section Styles.

The Style Book

The Style Book (the eye icon in Styles, since 6.2) replaces the canvas with a catalogue of every block, grouped by category, rendered with the current styles, plus a Colors and Typography overview. It is the place to check what a change does to blocks that are not on the page being edited. Since 6.8 classic themes that declare editor-styles or ship a theme.json reach it through Appearance > Design, so a hybrid theme can be reviewed the same way.

Responsive and state styles (7.1)

Before 7.1 a site-wide style applied at every screen width; changing the site title size on phones required custom CSS. WordPress 7.1 adds viewport states:

  • In Styles > Blocks > a block, a States dropdown with a Viewport group (Default, Tablet, Mobile). Values set for Tablet or Mobile override the default inside that range only.

  • On a single block in the editor, the same states are stored with that block instance. The View menu has a Responsive styles toggle to show or hide this editing.

  • The default breakpoints are @mobile up to 480px and @tablet from 481px to 782px; there is no desktop state, the default value covers everything wider. A theme can move the breakpoints with settings.viewport (px, em or rem only), which applies to the whole site, not per block.

7.1 also adds pseudo states (:hover, :focus, :focus-visible, :active) to the same States dropdown, for the Button and Navigation Link blocks, and a theme-only -current state for the menu item of the current page. Both features can be hidden from editors without removing saved styles, with the responsiveEditingEnabled and blockStatesEditingEnabled editor settings. A site owner who wants a simpler editor for Lumen Press’s Editors can hide the viewport controls with a must-use plugin:

<?php
/**
 * Plugin Name: Lumen Site Editor guardrails
 * Description: Hides the responsive (Tablet/Mobile) style controls in the editors.
 */
add_filter(
	'block_editor_settings_all',
	static function ( array $settings ): array {
		$settings['responsiveEditingEnabled'] = false;
		return $settings;
	}
);

Saved as wp-content/mu-plugins/lumen-site-editor.php, it sets the setting to false for every editor; responsive values already saved keep rendering. Theme-side syntax (@mobile, @tablet and :hover keys in theme.json) is on theme.json Styles.

Custom CSS at three levels

Level Where Stored in Since

Site-wide

Styles > More (the ellipsis in the Styles header since 6.9) > Additional CSS

styles.css of the user global styles

6.2

Block type

Styles > Blocks > a block > Additional block CSS; declarations only, & for nested selectors

styles.blocks.<block>.css

6.2

One block instance

Block sidebar > Advanced > Custom CSS; declarations only

the block’s style.css attribute in the post or template content

7.0

The instance level is new in 7.0. WordPress adds a generated wp-custom-css-* class to that block and prints the rule in a stylesheet that loads after global styles, so it wins over the block-type default. It is skipped for blocks that opt out with "customCSS": false (Classic, Custom HTML, More, Synced pattern), and CSS that contains anything looking like an HTML tag is dropped, the same validation the global styles endpoint applies. Without edit_css, the field is hidden and the attribute is stripped when the content is saved.

On a block theme the Customizer’s old Additional CSS (a custom_css post per theme) is still printed, merged into the global styles stylesheet just before the Styles Additional CSS, so CSS added before a switch to a block theme keeps working. The Customizer itself is reachable only by URL (customize.php) once a block theme is active, so moving that CSS into Styles > Additional CSS is the cleaner path. Styles CSS survives theme updates but belongs to one theme: it is not applied after switching to another theme, and comes back when switching back.

Custom CSS should be the last resort. Every value that Styles can express (a colour, a size, a breakpoint since 7.1) is better set there, because it lands in the preset system, the Style Book shows it, and theme updates and variations interact with it predictably.

Revisions, reset and per-panel reset

Each save of Styles creates a revision of the user global styles (since 6.3). Styles > More > Revisions shows a timeline; selecting one previews it and Apply restores it as a new save. Reset to defaults (same menu) empties the user layer, so the theme’s theme.json and active variation values take over again. Most panels also have their own reset (Reset colors, Reset gradients, the reset of a Layout value), which removes only that key.

Templates and template parts have their own history: they are posts with revisions. A customized template shows Reset (or "Clear customizations") in its Actions menu, which deletes the stored copy so the theme file is used again.

The Fonts screen and the Font Library

The Font Library (6.5) manages font families separately from the theme. Since 7.0 it has its own admin page, Appearance > Fonts (font-library.php), available with every theme, classic ones included; in a block theme it is also reachable from Styles > Typography. It has three tabs:

  • Library: the fonts the theme provides (read-only) and the fonts installed by users, which can be activated, deactivated per variant or deleted.

  • Upload: install .woff2, .woff, .ttf or .otf files.

  • Install fonts: the Google Fonts collection (google-fonts, the only collection registered by default). After the site owner gives permission, WordPress downloads the chosen files once and serves them locally; the visitor’s browser never contacts Google, which is the privacy-friendly way to use those fonts.

Installed fonts are wp_font_family posts (the CSS family name and settings in JSON) with one wp_font_face child post per style and weight, and the files go to wp-content/uploads/fonts (filterable with font_dir). They do not depend on the theme and are not part of a theme export. A font that is installed but not activated in Styles > Typography does not load on the front end.

Templates and template parts

Templates lists every template the theme provides (Twenty Twenty-Five ships index, home, single, page, archive, search, 404 and page-no-title) and those added by the owner. Add new template offers the missing ones of the template hierarchy (Front Page, a single post type such as "Single item: Book" for the Lumen Press lp_book type, an archive for a taxonomy, an author, a date archive, or one specific page or post) and custom templates that a page can select in its Template panel. How WordPress picks a template is explained in Template Hierarchy.

Template parts (under Patterns) are the shared areas, assigned to an area: Header, Footer, General (uncategorized), and Navigation Overlay (the block-based mobile menu overlay of 7.0). Editing the Header changes it everywhere it is used; the Save list warns about it.

The list views show where a template or part comes from: from the theme, from a plugin, or customized by the owner. A customized item is a copy in the database and from then on wins over the file. That is the expected behaviour and the source of the most frequent support question: "I updated the theme, but my header did not change." The theme update changed parts/header.html; the site still uses the customized copy until it is reset.

Each template is the serialized block markup that Templates and Template Parts describes; the Code editor (More options > Code editor) shows it.

Navigation

A block theme’s menus are Navigation blocks, and a menu’s items live in a wp_navigation post that the block references by ID ({"ref":9}). Appearance > Editor > Navigation (6.3) lists those posts. Creating a menu there does not place it anywhere; a Navigation block in a template part (normally the Header) must select it. Classic menus (nav_menu terms) appear in the Navigation block’s menu selector and can be imported as a copy; the originals stay untouched.

The Navigation block also builds the mobile overlay (the "hamburger" menu); since 7.0 the overlay can be a block-based template part in the Navigation Overlay area, designed like any other part. Patterns, synced patterns and their overrides, which also appear in this sidebar, are covered in Patterns for Site Builders, and the developer side of menus in Navigation and Menus.

Where the edits are stored

Nothing the Site Editor saves goes into the theme folder. Every save is a post in wp_posts, tied to the active theme by the wp_theme taxonomy (a term named after the theme’s directory), and resolved against the theme files at request time:

flowchart LR subgraph editor["Site Editor screens"] styles["Styles"] templates["Templates"] parts["Patterns: template parts"] nav["Navigation"] fonts["Appearance: Fonts"] end subgraph db["Database: wp_posts rows"] gs["wp_global_styles: one per theme, JSON"] wt["wp_template: block markup"] wtp["wp_template_part: markup and area"] wn["wp_navigation: menu items"] wf["wp_font_family and wp_font_face"] end subgraph files["Theme folder"] tj["theme.json and styles/*.json"] tf["templates/*.html"] pf["parts/*.html"] end styles -- "POST /wp/v2/global-styles/ID" --> gs templates -- "POST /wp/v2/templates/theme//slug" --> wt parts -- "POST /wp/v2/template-parts/theme//slug" --> wtp nav -- "POST /wp/v2/navigation" --> wn fonts -- "POST /wp/v2/font-families" --> wf tj --> merge{{"merge: core, parent, child, then user"}} gs --> merge merge --> css["global-styles stylesheet"] tf --> pick{{"database copy wins over the file"}} wt --> pick pf --> pick wtp --> pick pick --> page["rendered page"] wtp -. "Navigation block ref" .-> wn
Edit Post type and identity Notes

Styles, Additional CSS, block-type CSS

wp_global_styles, post_name wp-global-styles-<theme>, title "Custom Styles"

JSON with "isGlobalStylesUserThemeJSON": true and "version": 3; created on first need; one per theme; revisions kept

Customized template

wp_template, post_name = template slug (home)

Wins over templates/home.html of the same theme; revisions kept

Customized or new template part

wp_template_part, post_name = slug (header)

Also has a wp_template_part_area term (header, footer, uncategorized, navigation-overlay)

Menu

wp_navigation

Not tied to a theme: survives a theme switch; referenced by post ID

Installed font

wp_font_family with wp_font_face children

Not tied to a theme; files under wp-content/uploads/fonts

Identity

options blogname, blogdescription, site_logo, site_icon

The same options Settings > General and the Customizer use

Instance custom CSS, responsive and state values of one block

inside the block’s attributes in the post, template or part

Travels with that content

Customizer Additional CSS

custom_css, post_name = theme

Legacy; still printed for block themes

The precedence for styles is core defaults, then the parent theme, then the child theme, then the user layer; a child theme is merged into its parent before the user values. Since 6.6 global styles are printed with selectors such as :root :where(.wp-block-site-title) capped at 0-1-0 specificity, so a theme’s or plugin’s ordinary class rules can still override them.

Seeing it on a real site

The Compose file below starts MariaDB and a WP-CLI container, installs WordPress 7.1.3 (Twenty Twenty-Five is the default theme) and mounts the scripts of this section. It needs no web server: the scripts call the REST API in process with rest_do_request(), as the Site Editor does over HTTP. The passwords are placeholders for a local machine.

# compose.yaml
name: lumen-site-editor
services:
  db:
    image: mariadb:11.8
    environment:
      MARIADB_DATABASE: wordpress
      MARIADB_USER: wordpress
      MARIADB_PASSWORD: ${WP_DB_PASSWORD:?set WP_DB_PASSWORD}
      MARIADB_RANDOM_ROOT_PASSWORD: "1"
    tmpfs: [/var/lib/mysql]
  cli:
    image: wordpress:cli-2.12.0-php8.4
    depends_on: [db]
    user: "33:33"
    environment:
      WP_DB_PASSWORD: ${WP_DB_PASSWORD}
      WP_ADMIN_PASSWORD: ${WP_ADMIN_PASSWORD:?set WP_ADMIN_PASSWORD}
    volumes:
      - wp:/var/www/html
      - ./scripts:/scripts:ro
    entrypoint: ["sleep", "infinity"]
volumes:
  wp: {}
export WP_DB_PASSWORD='<a-local-db-password>' WP_ADMIN_PASSWORD='<a-local-admin-password>'
docker compose up -d
docker compose exec cli sh -c 'php -d memory_limit=512M /usr/local/bin/wp core download --version=7.1.3 &&
  wp config create --dbname=wordpress --dbuser=wordpress --dbpass="$WP_DB_PASSWORD" --dbhost=db &&
  wp core install --url=http://lumenpress.test --title="Lumen Press" --admin_user=admin \
    --admin_email=admin@lumenpress.test --admin_password="$WP_ADMIN_PASSWORD" --skip-email'
docker compose exec cli wp post list --post_type=wp_global_styles --post_status=any --fields=ID,post_name

On a fresh site the last command prints only the header line: the user global styles post does not exist until something asks for it (opening Styles, or the first save).

Saving styles

scripts/save-styles.php sends the request the Styles panel sends on Save: a custom palette colour "Lumen ink", a background from the theme palette, the theme’s Fira Code family as body font, the link colour, a larger site title with a smaller value for phones, and a site-wide CSS rule for ISBNs.

<?php
/**
 * Saves user-layer global styles for the active theme through the REST API,
 * the same request the Styles panel sends when the site owner clicks Save.
 * Run with: wp eval-file /scripts/save-styles.php --user=admin
 */
$id      = WP_Theme_JSON_Resolver::get_user_global_styles_post_id();
$request = new WP_REST_Request( 'POST', "/wp/v2/global-styles/$id" );
$request->set_body_params(
	array(
		'settings' => array(
			'color' => array(
				'palette' => array(
					'custom' => array(
						array( 'slug' => 'lumen-ink', 'name' => 'Lumen ink', 'color' => '#1d2a44' ),
					),
				),
			),
		),
		'styles'   => array(
			'color'      => array( 'background' => 'var:preset|color|accent-5' ),
			'typography' => array( 'fontFamily' => 'var:preset|font-family|fira-code' ),
			'elements'   => array(
				'link' => array( 'color' => array( 'text' => 'var:preset|color|lumen-ink' ) ),
			),
			'blocks'     => array(
				'core/site-title' => array(
					'typography' => array( 'fontSize' => '2.5rem' ),
					'@mobile'    => array( 'typography' => array( 'fontSize' => '1.5rem' ) ),
				),
			),
			'css'        => '.lp-book-isbn { font-variant-numeric: tabular-nums; }',
		),
	)
);
$response = rest_do_request( $request );
WP_CLI::log( "HTTP {$response->get_status()}, wp_global_styles post $id" );

The --user=admin flag matters. Without a user, get_user_global_styles_post_id() still creates the post, but wp_insert_post() silently drops its wp_theme term (assigning terms needs a capability), the lookup by term then fails, and every later call creates another orphan "Custom Styles" post (wp-global-styles-twentytwentyfive-2, and so on). Scripts that touch global styles must run as an administrator.

docker compose exec cli wp eval-file /scripts/save-styles.php --user=admin
docker compose exec cli wp post list --post_type=wp_global_styles --post_status=any --fields=ID,post_name,post_title
docker compose exec cli wp post get 4 --field=post_content
docker compose exec cli wp post term list 4 wp_theme --fields=slug
HTTP 200, wp_global_styles post 4
ID  post_name                          post_title
4   wp-global-styles-twentytwentyfive  Custom Styles
{"styles":{"color":{"background":"var:preset|color|accent-5"},"typography":{"fontFamily":"var:preset|font-family|fira-code"},"elements":{"link":{"color":{"text":"var:preset|color|lumen-ink"}}},"blocks":{"core/site-title":{"typography":{"fontSize":"2.5rem"},"@mobile":{"typography":{"fontSize":"1.5rem"}}}},"css":".lp-book-isbn { font-variant-numeric: tabular-nums; }"},"settings":{"color":{"palette":{"custom":[{"slug":"lumen-ink","name":"Lumen ink","color":"#1d2a44"}]}}},"isGlobalStylesUserThemeJSON":true,"version":3}
slug
twentytwentyfive

The stored document is a theme.json fragment: only the keys the owner changed, with preset references (var:preset|color|accent-5) rather than hex values, so a palette change later propagates. The custom colour is under settings.color.palette.custom, separate from the theme’s palette. The generated stylesheet shows the merge, including the 7.1 media query for the @mobile value and the Additional CSS that is printed last:

docker compose exec cli sh -c 'wp eval "echo wp_get_global_stylesheet();" | grep -o "@media (width <= 480px){[^}]*}"'
docker compose exec cli wp eval 'echo wp_get_global_stylesheet( array( "custom-css" ) );'
@media (width <= 480px){:root :where(.wp-block-site-title){font-size: clamp(0.984rem, 0.984rem + ((1vw - 0.2rem) * 0.809), 1.5rem);}
.lp-book-isbn { font-variant-numeric: tabular-nums; }

The 1.5rem became a clamp() because Twenty Twenty-Five enables fluid typography: fixed sizes set in Styles are made fluid too, with the entered value as the maximum.

Saving a menu, a template part and a template

scripts/site-edits.php reproduces three Site Editor saves: a new "Lumen header" menu, the Header part pointed at it, and a customized home template.

<?php
/**
 * Creates a navigation menu, points the header template part at it and
 * customizes the home template, as saving in the Site Editor does.
 * Run with: wp eval-file /scripts/site-edits.php --user=admin
 */
$theme = get_stylesheet();

$request = new WP_REST_Request( 'POST', '/wp/v2/navigation' );
$request->set_body_params(
	array(
		'title'   => 'Lumen header',
		'status'  => 'publish',
		'content' => '<!-- wp:navigation-link {"label":"Catalogue","url":"/books/","kind":"custom"} /-->'
			. '<!-- wp:navigation-link {"label":"Submissions","url":"/submissions/","kind":"custom"} /-->',
	)
);
$nav_id = rest_do_request( $request )->get_data()['id'];
WP_CLI::log( "wp_navigation post $nav_id" );

$header  = rest_do_request( new WP_REST_Request( 'GET', "/wp/v2/template-parts/$theme//header" ) )->get_data();
$content = preg_replace_callback(
	'/<!-- wp:navigation (\{.*?\} )?\/-->/s',
	static function ( array $match ) use ( $nav_id ): string {
		$attrs        = ! empty( $match[1] ) ? json_decode( $match[1], true ) : array();
		$attrs['ref'] = $nav_id;
		return '<!-- wp:navigation ' . wp_json_encode( $attrs, JSON_UNESCAPED_SLASHES ) . ' /-->';
	},
	$header['content']['raw'],
	1
);
$request = new WP_REST_Request( 'POST', "/wp/v2/template-parts/$theme//header" );
$request->set_body_params( array( 'content' => $content ) );
$saved = rest_do_request( $request )->get_data();
WP_CLI::log( "header: source {$header['source']} -> {$saved['source']}, wp_template_part post {$saved['wp_id']}" );

$request = new WP_REST_Request( 'POST', "/wp/v2/templates/$theme//home" );
$request->set_body_params(
	array(
		'content' => '<!-- wp:template-part {"slug":"header"} /-->'
			. '<!-- wp:heading {"level":1} --><h1 class="wp-block-heading">New from Lumen Press</h1><!-- /wp:heading -->'
			. '<!-- wp:template-part {"slug":"footer"} /-->',
	)
);
$saved = rest_do_request( $request )->get_data();
WP_CLI::log( "home: source {$saved['source']}, wp_template post {$saved['wp_id']}" );

Template and part IDs in the REST API are <theme>//<slug>; the first save of a theme item turns its source from theme into custom and creates the post.

docker compose exec cli wp eval-file /scripts/site-edits.php --user=admin
docker compose exec cli wp post list --post_type=wp_global_styles,wp_template,wp_template_part,wp_navigation \
  --post_status=any --fields=ID,post_type,post_name,post_title
docker compose exec cli wp post term list 10 wp_theme wp_template_part_area --fields=taxonomy,slug
docker compose exec cli sh -c 'wp post get 10 --field=post_content | grep -o "<!-- wp:navigation [^>]*>"'
wp_navigation post 9
header: source theme -> custom, wp_template_part post 10
home: source custom, wp_template post 11
ID  post_type         post_name                          post_title
9   wp_navigation     lumen-header                       Lumen header
10  wp_template_part  header                             Header
11  wp_template       home                               Blog Home
4   wp_global_styles  wp-global-styles-twentytwentyfive  Custom Styles
taxonomy               slug
wp_template_part_area  header
wp_theme               twentytwentyfive
<!-- wp:navigation {"overlayBackgroundColor":"base","overlayTextColor":"contrast","layout":{"type":"flex","justifyContent":"right","flexWrap":"wrap"},"ref":9} /-->

The Header in the theme file is only a Pattern block (twentytwentyfive/header); the REST response and the saved copy contain the pattern’s blocks expanded, so a customized part no longer follows later changes to that pattern either.

Revisions and resets

<?php
/**
 * Lists the style revisions the Styles > Revisions panel shows.
 * Run with: wp eval-file /scripts/list-revisions.php --user=admin
 */
$id        = WP_Theme_JSON_Resolver::get_user_global_styles_post_id();
$revisions = rest_do_request( new WP_REST_Request( 'GET', "/wp/v2/global-styles/$id/revisions" ) )->get_data();
foreach ( $revisions as $revision ) {
	WP_CLI::log( sprintf( '%d  %s  %d top-level style keys', $revision['id'], $revision['date'], count( (array) ( $revision['styles'] ?? array() ) ) ) );
}
<?php
/**
 * Empties the user layer, as Styles > Reset to defaults does.
 * Run with: wp eval-file /scripts/reset-styles.php --user=admin
 */
$id      = WP_Theme_JSON_Resolver::get_user_global_styles_post_id();
$request = new WP_REST_Request( 'POST', "/wp/v2/global-styles/$id" );
$request->set_body_params( array( 'settings' => new stdClass(), 'styles' => new stdClass() ) );
WP_CLI::log( 'HTTP ' . rest_do_request( $request )->get_status() );

Resetting the home template means deleting its database copy, which is what the template’s Reset action does:

docker compose exec cli wp post delete 11 --force
docker compose exec cli wp eval 'echo get_block_template( get_stylesheet() . "//home" )->source, PHP_EOL;'
docker compose exec cli wp eval-file /scripts/reset-styles.php --user=admin
docker compose exec cli wp post get 4 --field=post_content
docker compose exec cli wp eval-file /scripts/list-revisions.php --user=admin
Success: Deleted post 11.
theme
HTTP 200
{"styles":[],"settings":[],"isGlobalStylesUserThemeJSON":true,"version":3}
12  2026-10-28T10:28:52  0 top-level style keys
5  2026-10-28T10:27:49  5 top-level style keys

A reset keeps the post and the history: the earlier state is still a revision, so a reset done by mistake is undone from Styles > Revisions. Deleting a template’s copy, in contrast, is final unless a backup exists.

Exporting and moving the design

Tools > Export in the Site Editor’s More options menu downloads a zip of the active theme with the owner’s changes written into the files: user global styles merged into theme.json, customized templates and parts in templates/ and parts/. The same zip is produced by wp_generate_block_templates_export_file(), which makes it easy to check:

docker compose exec cli sh -c 'zip=$(wp eval "echo wp_generate_block_templates_export_file();" --user=admin)
  unzip -l "$zip" | grep -E " (theme.json|templates/home.html|parts/header.html)$"
  unzip -p "$zip" parts/header.html | grep -o "<!-- wp:navigation [^>]*>"
  rm -f "$zip"'
     1193  10-28-2026 10:28   parts/header.html
      193  10-28-2026 10:28   templates/home.html
    55175  10-28-2026 10:28   theme.json
<!-- wp:navigation {"overlayBackgroundColor":"base","overlayTextColor":"contrast","layout":{"type":"flex","justifyContent":"right","flexWrap":"wrap"},"ref":9} /-->

The export has limits a site owner should know:

  • Menus are not exported. The header keeps "ref":9, an ID that means nothing on another site; the Navigation block there falls back to another menu or a page list. Menus move with the content export (the dashboard’s Tools > Export includes wp_navigation posts), and the menu is then selected again in the Navigation block.

  • Installed fonts are not exported, and neither are media referenced from styles (a background image URL keeps pointing at the old site’s uploads).

  • It is a copy of the theme with the same name and version. Installing it over the original on another site is fine; updating the original theme from WordPress.org later overwrites it. A design meant to last belongs in a child theme or the site’s own theme, which the Create Block Theme plugin produces from the Site Editor (save changes to the theme, create a child theme, or a new theme): see Create Block Theme and Version Control.

Once the changes are in the theme’s files, reset the database copies (styles and every customized template or part); otherwise the database still wins and later file edits seem to have no effect. For Lumen Press the intended workflow is: design in the Site Editor on staging, save to the lumen theme with Create Block Theme, commit, reset the customizations, deploy the theme. Content moves are covered in Importing, Exporting and Migrating.

What older books get wrong or leave out

Older advice Current state (7.1)

Full site editing is "still maturing" or experimental, and design is best done in the Customizer with a commercial theme and a page builder (UWH)

The Site Editor has been part of core since 5.9 (2022) and is the default design tool of every bundled theme since Twenty Twenty-Two. Responsive styles (7.1), per-instance CSS (7.0), the Fonts screen (7.0), colour and typography variations (6.6) and style revisions (6.3) fill the gaps that used to require a builder. Page builders remain an option, with the trade-offs in Page Builders and Commercial Themes.

Site editing arrived in WordPress 5.4 to 5.6 (PRO)

Block themes and the Site Editor arrived in 5.9; earlier versions only had Gutenberg plugin experiments.

Customizer Additional CSS, Menus and Widgets as the way to adjust any theme (PRO, UWH)

With a block theme they are replaced by Styles > Additional CSS, the Navigation screen and template parts; the Customizer is hidden, and its old CSS is still printed.

"Reusable blocks" (UWH)

Renamed synced patterns in 6.3, managed under Patterns.

Site Editor walkthroughs written for 6.3 and 6.4 (AIO)

The screens have moved since: template parts are listed under Patterns, Additional CSS is in the ellipsis menu of the Styles header (6.9), the Fonts screen is in Appearance (7.0), and 7.1 adds the Identity section, responsive styles and a persistent admin bar.

Google Fonts loaded through a plugin or the theme from Google’s servers

The Font Library downloads the files once and serves them locally, for every theme since 7.0.

Updating the theme updates the site’s header and footer

Only for parts and templates that were never customized; customized copies in the database win until reset.

Common errors and pitfalls

Symptom Cause and fix

Appearance > Editor is missing

The active theme is classic (no templates/index.html), or the user lacks edit_theme_options. Classic themes with theme.json or editor-styles show Design instead (6.8).

A theme update changed nothing visible

The header, footer or a template is customized in the database. Compare in the Templates or Patterns list (the "customized" source) and reset it, after exporting if the changes matter.

Styles change some blocks but not others

Those blocks have a local value set in the block sidebar. Clear it, or use Advanced > Apply globally.

Additional CSS editors are missing

DISALLOW_UNFILTERED_HTML is true, or the site is Multisite and the user is not a Super Admin (edit_css denied).

Several "Custom Styles" posts for one theme, styles seem to reset at random

A script or plugin created the user global styles post without a logged-in administrator, so the wp_theme term is missing. Delete the extra posts, keep the one with the term, and run such scripts with --user.

Styles vanish after switching themes

Global styles, Additional CSS and customized templates belong to the theme that was active. They return when switching back; export or copy them before a permanent switch.

The menu is empty after moving the site

The Navigation block references a wp_navigation ID from the old site. Import the content (with the menus) or reselect the menu.

Mobile values do nothing

The block is wider than the breakpoint, or the theme set settings.viewport to other values; responsive states also need 7.1 or later on the server that renders the page.

A font chosen in Styles does not appear on the front end

It is installed but not activated, or a block or the theme sets a different family more specifically. Check Appearance > Fonts and the block’s own typography.

Security notes for site owners: everyone with edit_theme_options can change every template, so the Administrator role should stay limited to people trusted with the whole site; the CSS editors are gated by edit_css because CSS can leak data or deface pages; and a theme export zip can contain anything the database copies contain, so it should be reviewed before being shared.

References