SponsoredHosinger Special Offer

WP WordPress Theme Development

Build classic and block WordPress themes from scratch: the template hierarchy, The Loop, the_title() and the_content(), template parts, menus, the Customizer, child themes and theme.json.

functions.php and after_setup_theme

WordPress Theme Development Lesson 10 of 30 ~4 min read

Overview

functions.php is the PHP file WordPress loads automatically every time your theme is active, and it is where a theme registers its features, menus, widget areas, image sizes and assets by hooking functions into WordPress.

It behaves like a plugin that only runs while your theme is active. It is not a template — it never prints HTML directly.

Hooks in one paragraph

WordPress fires named events, called hooks, at fixed points while it runs. add_action( 'hook_name', 'your_function' ) asks WordPress to call your function when that event fires. Nearly everything in functions.php follows this pattern: you write a function, then attach it to the right moment. The WordPress Plugin Development path covers hooks in full depth.

The setup function

<?php
/**
 * GDC Starter functions and definitions.
 */

// Stop the file being loaded directly through its URL.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

define( 'GDC_STARTER_VERSION', wp_get_theme()->get( 'Version' ) );

if ( ! function_exists( 'gdc_starter_setup' ) ) :
    /**
     * Registers theme features. Runs on after_setup_theme, before init.
     */
    function gdc_starter_setup() {
        load_theme_textdomain( 'gdc-starter', get_template_directory() . '/languages' );

        add_theme_support( 'title-tag' );
        add_theme_support( 'post-thumbnails' );
        add_theme_support( 'automatic-feed-links' );

        register_nav_menus(
            array(
                'primary' => esc_html__( 'Primary Menu', 'gdc-starter' ),
                'footer'  => esc_html__( 'Footer Menu', 'gdc-starter' ),
            )
        );
    }
endif;
add_action( 'after_setup_theme', 'gdc_starter_setup' );

// Keep functions.php short by loading focused files.
require get_template_directory() . '/inc/template-tags.php';

Why after_setup_theme?

Theme features must be declared early — after the theme is loaded but before WordPress initialises things that read them. after_setup_theme is exactly that moment. Calling add_theme_support() directly at the top of the file, outside any hook, usually works by accident but can break when a child theme or plugin needs to adjust it.

The hooks a theme uses most

HookFiresUse it for
after_setup_themeRight after the theme loadsTheme supports, menus, image sizes, text domain
widgets_initWhen widgets initialiseregister_sidebar()
wp_enqueue_scriptsWhile building a front-end pageLoading CSS and JS
customize_registerWhen the Customizer loadsAdding Customizer settings
initAfter WordPress has loadedMost other registration

Prefix everything

Every function, constant and global in your theme shares one PHP namespace with WordPress core and every installed plugin. A function called setup() will eventually collide with someone else's and crash the site with "Cannot redeclare". Prefix everything with your theme slug: gdc_starter_setup(), GDC_STARTER_VERSION.

Organising a growing functions.php

A real theme's functions.php quickly passes 500 lines. Split it by job and load the pieces:

<?php
$gdc_starter_includes = array(
    '/inc/setup.php',         // Theme supports, menus, image sizes.
    '/inc/enqueue.php',       // CSS and JS.
    '/inc/widgets.php',       // Sidebars.
    '/inc/template-tags.php', // Reusable output helpers.
    '/inc/customizer.php',    // Customizer settings.
);

foreach ( $gdc_starter_includes as $file ) {
    require_once get_template_directory() . $file;
}
Tip: Use get_template_directory() for files inside this theme, and get_stylesheet_directory() when you deliberately want the active child theme's copy. In a theme with no child, both return the same path. Lesson 27 explains when the difference matters.

What does not belong here

  • Registering custom post types or taxonomies — plugin territory (lesson 1).
  • Echoing HTML at the top level. Output before headers are sent causes "headers already sent" errors.
  • A closing ?> at the end of the file. Any whitespace after it is sent to the browser and causes the same error. Leave it off.

Practice

  • Create functions.php with the setup function above and confirm the site still loads with no notices in debug.log.
  • Move the require lines into an $includes array as shown.
  • Deliberately add a stray space after a closing ?> and read the error it causes, then remove the ?>.
Create a free account to save which lessons you've finished. Save my progress