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.
Lessons
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.
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.
<?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';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.
| Hook | Fires | Use it for |
|---|---|---|
after_setup_theme | Right after the theme loads | Theme supports, menus, image sizes, text domain |
widgets_init | When widgets initialise | register_sidebar() |
wp_enqueue_scripts | While building a front-end page | Loading CSS and JS |
customize_register | When the Customizer loads | Adding Customizer settings |
init | After WordPress has loaded | Most other registration |
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.
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: Useget_template_directory()for files inside this theme, andget_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.
?> at the end of the file. Any whitespace after it is sent to the browser and causes the same error. Leave it off.functions.php with the setup function above and confirm the site still loads with no notices in debug.log.require lines into an $includes array as shown.?> and read the error it causes, then remove the ?>.In accordance with Section 6 & Section 7 of the DPDP Act 2023, third-party analytics and marketing trackers are strictly gated behind your affirmative consent. You may withdraw consent at any time under Section 6(4).
Essential for session authentication (ci_session), Cross-Site Request Forgery security (csrf_cookie_name), and Cloudflare DDoS/bot prevention. Cannot be disabled.
Gated under DPDP Section 7. Allows Google Analytics 4 (Google Consent Mode v2) to measure aggregate, anonymized tool usage.
Enables browser local storage to preserve your theme selection (dark/light mode) and code editor layout between visits.
Used to display developer sponsorships and native tool partner notices.