Theme Supports

add_theme_support() on after_setup_theme opts a theme into core features like title-tag, post-thumbnails, html5 and editor styles.

Syntaxadd_theme_support( string $feature, mixed $args ): void|false

add_theme_support() is how a theme tells core which optional features it handles. Core keeps these off by default so old themes are not broken by new markup; opting in is a promise that your templates cope with the change.

Timing

Call it on after_setup_theme, or directly in functions.php. init is too late for several features — title-tag must be registered before wp_loaded, and post-thumbnails before init runs.

The common features

  • title-tag (WP 4.1) — core prints <title> via wp_head(), replacing a hand-written <title> tag or a wp_title() call in header.php. wp_title() was scheduled for deprecation in 4.4 but reinstated, so it is not formally deprecated — it is simply the wrong tool in a modern theme.
  • post-thumbnails — enables the featured image box; optionally scoped to an array of post types.
  • html5 — modern markup for 'search-form', 'comment-form', 'comment-list', 'gallery', 'caption', 'script' and 'style'.
  • editor-styles plus add_editor_style() — loads your CSS inside the editor.
  • responsive-embeds (WP 5.0) — makes embeds fluid.
  • appearance-tools (WP 6.5) — turns on border, colour, spacing and typography controls in one call.

What trips people up

Support is only permission. post-thumbnails adds the metabox but your template still has to call the_post_thumbnail(). Block themes enable editor-styles and several others implicitly, and express the same switches through theme.json.

Example

Example · php
<?php
/**
 * Theme setup. after_setup_theme is early enough for every feature below.
 */
function mytheme_setup() {
	// Core prints <title> in wp_head(). Remove any <title> tag from header.php.
	add_theme_support( 'title-tag' );

	// Featured images. Pass an array to limit it to specific post types.
	add_theme_support( 'post-thumbnails', array( 'post', 'page' ) );
	add_image_size( 'mytheme-card', 640, 360, true ); // true = hard crop

	// Opt into HTML5 markup for the forms and galleries core outputs.
	add_theme_support(
		'html5',
		array( 'search-form', 'comment-form', 'comment-list', 'gallery', 'caption', 'script', 'style' )
	);

	// Fluid embeds (WP 5.0+).
	add_theme_support( 'responsive-embeds' );

	// Core block styles on the front end, and wide/full alignment.
	add_theme_support( 'wp-block-styles' );
	add_theme_support( 'align-wide' );

	// Editor styles: the support flag AND the stylesheet, in that order.
	add_theme_support( 'editor-styles' );
	add_editor_style( 'assets/css/editor.css' ); // path relative to the theme

	// <link rel="alternate" type="application/rss+xml"> in wp_head().
	add_theme_support( 'automatic-feed-links' );

	// Site logo control; output it with the_custom_logo() (WP 4.5+).
	add_theme_support( 'custom-logo' );
}
add_action( 'after_setup_theme', 'mytheme_setup' );

When to use it

  • Setting up a new theme's supported features in one after_setup_theme callback
  • Getting the theme's own typography into the block editor with editor-styles
  • Removing a parent theme's wp-block-styles from a child theme
  • Checking from a plugin whether the active theme handles a feature before using it

More examples

The theme.json equivalent of appearance-tools

add_theme_support( 'appearance-tools' ) (WP 6.5) and "appearanceTools": true in theme.json are the same switch — both turn on the border, link colour, spacing and typography controls in the editor. Use the PHP form in a classic theme; if the theme already ships a theme.json, set it there instead so the whole configuration lives in one file. Do not do both.

Example · json
{
  "$schema": "https://schemas.wp.org/trunk/theme.json",
  "version": 3,
  "settings": {
    "appearanceTools": true
  }
}

Using the support you declared

current_theme_supports() takes an optional second argument for features with sub-features, so you can test 'html5' and a specific sub-feature. This is the check a plugin should make before assuming a theme handles something.

Example · php
<?php
// The support call does nothing on the front end by itself.
if ( has_post_thumbnail() ) {
	the_post_thumbnail( 'mytheme-card', array( 'loading' => 'lazy' ) );
}

if ( has_custom_logo() ) {
	the_custom_logo(); // outputs the logo, linked to home
}

// Branch on a feature rather than assuming it, especially in a parent theme
// whose child may have removed it.
if ( current_theme_supports( 'html5', 'search-form' ) ) {
	get_search_form();
}

Adjusting a parent theme's supports from a child

add_theme_support() replaces rather than merges the args for a feature, so re-declaring 'html5' with one entry silently drops everything the parent registered. Read the existing value with get_theme_support() first — it returns the args wrapped in an array.

Example · php
<?php
// The child's functions.php loads first, but after_setup_theme callbacks run
// in priority order — so register at a priority higher than the parent's 10.
add_action( 'after_setup_theme', function () {
	// Drop a feature the parent declared.
	remove_theme_support( 'wp-block-styles' );

	// Extend one: read the current args, then re-add with your additions.
	$html5 = get_theme_support( 'html5' );
	$features = is_array( $html5 ) && isset( $html5[0] ) ? $html5[0] : array();
	add_theme_support( 'html5', array_unique( array_merge( $features, array( 'style', 'script' ) ) ) );
}, 20 );

Discussion

  • Be the first to comment on this lesson.