Theme Supports
add_theme_support() on after_setup_theme opts a theme into core features like title-tag, post-thumbnails, html5 and editor styles.
add_theme_support( string $feature, mixed $args ): void|falseadd_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>viawp_head(), replacing a hand-written<title>tag or awp_title()call inheader.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-stylesplusadd_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
<?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.
{
"$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.
<?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.
<?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