{"id":5906,"date":"2026-07-21T18:57:12","date_gmt":"2026-07-21T18:57:12","guid":{"rendered":"https:\/\/frontlinenewsng.org\/?p=5906"},"modified":"2026-07-21T18:57:12","modified_gmt":"2026-07-21T18:57:12","slug":"gutenberg-times-how-wordpress-decides-a-theme-is-a-block-theme","status":"publish","type":"post","link":"https:\/\/frontlinenewsng.org\/?p=5906","title":{"rendered":"Gutenberg Times: How WordPress decides a theme is a \u201cblock theme\u201d"},"content":{"rendered":"<p class=\"wp-block-paragraph\">The term \u201cblock theme\u201d is used a lot in WordPress but it was never really clear to me what that meant exactly from a code point of view.<\/p>\n<p class=\"wp-block-paragraph\">While researching <a href=\"https:\/\/gutenbergtimes.com\/the-post-editor-is-going-full-iframe-what-block-developers-need-to-know-before-wordpress-7-1\/\">The post editor is going full iframe: what block developers need to know before WordPress 7.1<\/a>, I learned that the outcome of testing WordPress 7.1 Beta 1 <em>may<\/em> soften the plan: instead of forcing the iframe for everyone, core might force it only for block themes, while classic themes using blocks with apiVersion 2 or lower keep the current 7.0 behavior. (The linked article covers the 7.0 state of things in full.)<\/p>\n<p>If that\u2019s the split, then the exact definition of \u201cblock theme\u201d suddenly matters a great deal. So what does core actually check?<\/p>\n<p class=\"wp-block-paragraph\">The public API is <code>wp_is_block_theme()<\/code>, which just asks the active theme:<\/p>\n<div class=\"wp-block-kevinbatdorf-code-block-pro\"><span><span>PHP<\/span><\/span><span class=\"code-block-pro-copy-button\">\n<pre class=\"code-block-pro-copy-button-pre\"><textarea class=\"code-block-pro-copy-button-textarea\" readonly>\/\/ wp-includes\/theme.php (guard clause trimmed)\nfunction wp_is_block_theme() {\n\treturn wp_get_theme()-&gt;is_block_theme();\n}<\/textarea><\/pre>\n<p><\/p><\/span>\n<pre class=\"shiki dark-plus\"><code><span class=\"line\"><span>\/\/ wp-includes\/theme.php (guard clause trimmed)<\/span><\/span>\n<span class=\"line\"><span>function<\/span><span> <\/span><span>wp_is_block_theme<\/span><span>() {<\/span><\/span>\n<span class=\"line\"><span>\t<\/span><span>return<\/span><span> <\/span><span>wp_get_theme<\/span><span>()-&gt;<\/span><span>is_block_theme<\/span><span>();<\/span><\/span>\n<span class=\"line\"><span>}<\/span><\/span><\/code><\/pre>\n<\/div>\n<p class=\"wp-block-paragraph\">And <code>WP_Theme::is_block_theme()<\/code> is, in its entirety, a file-existence check:<\/p>\n<div class=\"wp-block-kevinbatdorf-code-block-pro\"><span><span>PHP<\/span><\/span><span class=\"code-block-pro-copy-button\">\n<pre class=\"code-block-pro-copy-button-pre\"><textarea class=\"code-block-pro-copy-button-textarea\" readonly>\/\/ wp-includes\/class-wp-theme.php (caching trimmed)\npublic function is_block_theme() {\n\t$paths_to_index_block_template = array(\n\t\t$this-&gt;get_file_path( '\/templates\/index.html' ),\n\t\t$this-&gt;get_file_path( '\/block-templates\/index.html' ),\n\t);\n\n\tforeach ( $paths_to_index_block_template as $path ) {\n\t\tif ( is_file( $path ) &amp;&amp; is_readable( $path ) ) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}<\/textarea><\/pre>\n<p><\/p><\/span>\n<pre class=\"shiki dark-plus\"><code><span class=\"line\"><span>\/\/ wp-includes\/class-wp-theme.php (caching trimmed)<\/span><\/span>\n<span class=\"line\"><span>public<\/span><span> <\/span><span>function<\/span><span> <\/span><span>is_block_theme<\/span><span>() {<\/span><\/span>\n<span class=\"line\"><span>\t<\/span><span>$paths_to_index_block_template<\/span><span> = <\/span><span>array<\/span><span>(<\/span><\/span>\n<span class=\"line\"><span>\t\t<\/span><span>$this<\/span><span>-&gt;<\/span><span>get_file_path<\/span><span>( <\/span><span>'\/templates\/index.html'<\/span><span> ),<\/span><\/span>\n<span class=\"line\"><span>\t\t<\/span><span>$this<\/span><span>-&gt;<\/span><span>get_file_path<\/span><span>( <\/span><span>'\/block-templates\/index.html'<\/span><span> ),<\/span><\/span>\n<span class=\"line\"><span>\t);<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span>\t<\/span><span>foreach<\/span><span> ( <\/span><span>$paths_to_index_block_template<\/span><span> as <\/span><span>$path<\/span><span> ) {<\/span><\/span>\n<span class=\"line\"><span>\t\t<\/span><span>if<\/span><span> ( <\/span><span>is_file<\/span><span>( <\/span><span>$path<\/span><span> ) &amp;&amp; <\/span><span>is_readable<\/span><span>( <\/span><span>$path<\/span><span> ) ) {<\/span><\/span>\n<span class=\"line\"><span>\t\t\t<\/span><span>return<\/span><span> <\/span><span>true<\/span><span>;<\/span><\/span>\n<span class=\"line\"><span>\t\t}<\/span><\/span>\n<span class=\"line\"><span>\t}<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span>\t<\/span><span>return<\/span><span> <\/span><span>false<\/span><span>;<\/span><\/span>\n<span class=\"line\"><span>}<\/span><\/span><\/code><\/pre>\n<\/div>\n<p class=\"wp-block-paragraph\"><strong>A theme is a \u201cblock theme\u201d if it ships an <code>index.html<\/code> block template<\/strong>, either in <code>templates\/<\/code>, or in <code>block-templates\/<\/code>, the pre-5.9 legacy location. Nothing else is consulted. That has a few consequences that may surprise people:<\/p>\n<p class=\"wp-block-paragraph\"><strong><code>theme.json<\/code> doesn\u2019t make you a block theme.<\/strong> Neither do patterns, block template <em>parts<\/em>, or <code>add_theme_support( 'block-templates' )<\/code>. A theme can adopt every one of those \u201chybrid\u201d features and still land on the classic side of this check, because the test only looks for a top-level <code>index.html<\/code> template.<\/p>\n<p class=\"wp-block-paragraph\"><strong>Child themes inherit the answer.<\/strong> <code>get_file_path()<\/code> looks in the child theme first and falls back to the parent, so a child theme of a block theme is a block theme even if the child ships no templates of its own.<\/p>\n<p class=\"wp-block-paragraph\"><strong>It\u2019s a filesystem check, not a declaration.<\/strong> There\u2019s no header in <code>style.css<\/code> that opts you in or out. Drop a <code>templates\/index.html<\/code> into a theme and, as far as WordPress is concerned, it <em>is<\/em> a block theme.<\/p>\n<h3 class=\"wp-block-heading\">The minimum files required for block and classic themes<\/h3>\n<p class=\"wp-block-paragraph\">This is the entire minimum viable <strong>block theme<\/strong> \u2014 two files:<\/p>\n<div class=\"wp-block-kevinbatdorf-code-block-pro\"><span><span>Markdown<\/span><\/span><span class=\"code-block-pro-copy-button\">\n<pre class=\"code-block-pro-copy-button-pre\"><textarea class=\"code-block-pro-copy-button-textarea\" readonly>my-block-theme\/\n\u251c\u2500\u2500 style.css          \u2190 standard theme header\n\u2514\u2500\u2500 templates\/\n    \u2514\u2500\u2500 index.html     \u2190 this file IS the decider<\/textarea><\/pre>\n<p><\/p><\/span>\n<pre class=\"shiki dark-plus\"><code><span class=\"line\"><span>my-block-theme\/<\/span><\/span>\n<span class=\"line\"><span>\u251c\u2500\u2500 style.css          \u2190 standard theme header<\/span><\/span>\n<span class=\"line\"><span>\u2514\u2500\u2500 templates\/<\/span><\/span>\n<span class=\"line\"><span>    \u2514\u2500\u2500 index.html     \u2190 this file IS the decider<\/span><\/span><\/code><\/pre>\n<\/div>\n<p class=\"wp-block-paragraph\">(WordPress considers a theme valid if it has <code>style.css<\/code> plus <em>either<\/em> <code>index.php<\/code> or <code>templates\/index.html<\/code>, which means for a block theme, <code>index.php<\/code>, <code>functions.php<\/code>, and even <code>theme.json<\/code> are all optional.)<\/p>\n<p class=\"wp-block-paragraph\">And this is a theme that is <strong>guaranteed to stay classic<\/strong>:<\/p>\n<div class=\"wp-block-kevinbatdorf-code-block-pro\"><span><span>Markdown<\/span><\/span><span class=\"code-block-pro-copy-button\">\n<pre class=\"code-block-pro-copy-button-pre\"><textarea class=\"code-block-pro-copy-button-textarea\" readonly>my-classic-theme\/\n\u251c\u2500\u2500 style.css\n\u2514\u2500\u2500 index.php          \u2190 the classic fallback template<\/textarea><\/pre>\n<p><\/p><\/span>\n<pre class=\"shiki dark-plus\"><code><span class=\"line\"><span>my-classic-theme\/<\/span><\/span>\n<span class=\"line\"><span>\u251c\u2500\u2500 style.css<\/span><\/span>\n<span class=\"line\"><span>\u2514\u2500\u2500 index.php          \u2190 the classic fallback template<\/span><\/span><\/code><\/pre>\n<\/div>\n<p class=\"wp-block-paragraph\">Staying on the classic side of the check comes down to two conditions:<\/p>\n<ol class=\"wp-block-list\">\n<li><strong>No<\/strong> <strong><code>templates\/index.html<\/code><\/strong> <strong>and no legacy<\/strong> <strong><code>block-templates\/index.html<\/code>.<\/strong> Other block templates don\u2019t matter: a theme with `templates\/single.html` but no `templates\/index.html` still tests as classic. (In practice, though, if you\u2019re shipping block templates, ship the index and be a block theme on purpose.)<\/li>\n<li><strong>No block-theme parent.<\/strong> The check falls back to the parent theme, so a child of Twenty Twenty-Five is a block theme no matter what the child contains. To be classic, the whole chain has to be.<\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\">Everything else is fair game. <code>theme.json<\/code>, patterns, <code>add_theme_support( 'block-template-parts' )<\/code>` custom templates registered from plugins \u2014 none of them flip the switch., patterns, <code>add_theme_support( 'block-template-parts' )<\/code>, custom templates registered from plugins: none of them flip the switch.<\/p>\n<h3 class=\"wp-block-heading\">Where real themes land<\/h3>\n<p class=\"wp-block-paragraph\">Running that check against some current releases from the theme directory<\/p>\n<figure class=\"wp-block-table\">\n<table class=\"has-fixed-layout\">\n<thead>\n<tr>\n<th>Tests <strong>classic<\/strong><\/th>\n<th>Tests <strong>block<\/strong><\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Twenty Twenty-One and every earlier default<\/td>\n<td>Twenty Twenty-Two and every later default<\/td>\n<\/tr>\n<tr>\n<td>Astra, Kadence, Blocksy, Botiga, Sydney, Hello Elementor \u2014 <em>all ship <code>theme.json<\/code><\/em><\/td>\n<td><\/td>\n<\/tr>\n<tr>\n<td>GeneratePress, Neve, OceanWP, Storefront<\/td>\n<td><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p class=\"wp-block-paragraph\">Six of the themes in the first column ship <code>theme.json<\/code>, the marquee \u201cblock\u201d feature, and still test classic, because the check never looks at <code>theme.json<\/code><\/p>\n<p>Twenty Twenty and OceanWP are another good gotcha: both ship a <code>templates\/<\/code> directory and are still classic, because it\u2019s full of PHP page templates (<code>template-cover.php<\/code>, <code>landing.php<\/code>). The check wants <code>templates\/index.html<\/code> specifically, so \u201cdoes it have a templates folder\u201d is not the indicator you might assume.<\/p>\n<h3 class=\"wp-block-heading\">How the editor reads it<\/h3>\n<p class=\"wp-block-paragraph\">On the JavaScript side, the block-theme flag surfaces in two different places, which is worth knowing if you go source-diving.<\/p>\n<p class=\"wp-block-paragraph\">As an editor setting:<\/p>\n<div class=\"wp-block-kevinbatdorf-code-block-pro\"><span><span>PHP<\/span><\/span><span class=\"code-block-pro-copy-button\">\n<pre class=\"code-block-pro-copy-button-pre\"><textarea class=\"code-block-pro-copy-button-textarea\" readonly>\/\/ wp-includes\/block-editor.php, get_block_editor_settings()\n$editor_settings['__unstableIsBlockBasedTheme'] = wp_is_block_theme();<\/textarea><\/pre>\n<p><\/p><\/span>\n<pre class=\"shiki dark-plus\"><code><span class=\"line\"><span>\/\/ wp-includes\/block-editor.php, get_block_editor_settings()<\/span><\/span>\n<span class=\"line\"><span>$editor_settings<\/span><span>[<\/span><span>'__unstableIsBlockBasedTheme'<\/span><span>] = <\/span><span>wp_is_block_theme<\/span><span>();<\/span><\/span><\/code><\/pre>\n<\/div>\n<p class=\"wp-block-paragraph\">and on the REST themes endpoint, which is where <code>@wordpress\/core-data<\/code> picks it up:<\/p>\n<div class=\"wp-block-kevinbatdorf-code-block-pro\"><span><span>PHP<\/span><\/span><span class=\"code-block-pro-copy-button\">\n<pre class=\"code-block-pro-copy-button-pre\"><textarea class=\"code-block-pro-copy-button-textarea\" readonly>\/\/ wp-includes\/rest-api\/endpoints\/class-wp-rest-themes-controller.php\n$data['is_block_theme'] = $theme-&gt;is_block_theme();<\/textarea><\/pre>\n<p><\/p><\/span>\n<pre class=\"shiki dark-plus\"><code><span class=\"line\"><span>\/\/ wp-includes\/rest-api\/endpoints\/class-wp-rest-themes-controller.php<\/span><\/span>\n<span class=\"line\"><span>$data<\/span><span>[<\/span><span>'is_block_theme'<\/span><span>] = <\/span><span>$theme<\/span><span>-&gt;<\/span><span>is_block_theme<\/span><span>();<\/span><\/span><\/code><\/pre>\n<\/div>\n<h2 class=\"wp-block-heading\">The one-file switch<\/h2>\n<p class=\"wp-block-paragraph\">For all the weight the term carries, \u201cblock theme\u201d boils down to a single file: <code>templates\/index.html<\/code> exists, or it doesn\u2019t. Not <code>theme.json<\/code>, not patterns, not any amount of hybrid adoption. Just one index template, checked up the parent chain.<\/p>\n<p class=\"wp-block-paragraph\">That\u2019s worth keeping in mind if 7.1 does end up drawing the iframe line at <code>wp_is_block_theme()<\/code>. A hybrid theme that has adopted everything <em>except<\/em> block templates would keep the classic editor behavior, while adding a single <code>templates\/index.html<\/code> (even accidentally, even in a parent theme you don\u2019t control) would flip a site to the forced iframe. If your theme or your users\u2019 sites sit anywhere near that line, now is a good time to check which side of it you\u2019re actually on: it\u2019s one <code>is_file()<\/code> call away.<\/p>\n<p class=\"wp-block-paragraph\">\n<\/p>","protected":false},"excerpt":{"rendered":"<p>The term \u201cblock theme\u201d is used a lot in WordPress but it was never really clear to me what that meant exactly from a code point of view. While researching The post editor is going full iframe: what block developers need to know before WordPress 7.1, I learned that the outcome of testing WordPress 7.1 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"advanced_seo_description":"","jetpack_seo_html_title":"","jetpack_seo_noindex":false,"jetpack_seo_schema_type":"","_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[1],"tags":[],"class_list":["post-5906","post","type-post","status-publish","format-standard","hentry","category-latest-news"],"jetpack_featured_media_url":"","jetpack_likes_enabled":true,"jetpack-related-posts":[],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontlinenewsng.org\/index.php?rest_route=\/wp\/v2\/posts\/5906","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontlinenewsng.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontlinenewsng.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontlinenewsng.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/frontlinenewsng.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5906"}],"version-history":[{"count":0,"href":"https:\/\/frontlinenewsng.org\/index.php?rest_route=\/wp\/v2\/posts\/5906\/revisions"}],"wp:attachment":[{"href":"https:\/\/frontlinenewsng.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5906"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontlinenewsng.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5906"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontlinenewsng.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5906"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}