{"id":251384,"date":"2026-10-18T15:05:00","date_gmt":"2026-10-18T15:05:00","guid":{"rendered":"https:\/\/muhamedahmed.com\/wordpress-child-theme\/"},"modified":"2026-11-02T04:15:51","modified_gmt":"2026-11-02T04:15:51","slug":"wordpress-child-theme","status":"publish","type":"post","link":"https:\/\/muhamedahmed.com\/en\/wordpress-child-theme\/","title":{"rendered":"WordPress Child Theme Done Right: Customizer Options Instead of Hardcoding"},"content":{"rendered":"<p>A <strong>WordPress child theme<\/strong> is a small theme that inherits everything from a parent theme and lets you override only what you change, so updating the parent never erases your work. To create one you need a folder, a <code>style.css<\/code> with a <code>Template:<\/code> header naming the parent, and a <code>functions.php<\/code> that enqueues styles.<\/p>\n<p>That part is in every tutorial. What I want to add is the part most child themes get wrong: they become a pile of hardcoded text, colours and markup that only a developer can change.<\/p>\n<h2>How do you create a WordPress child theme?<\/h2>\n<p>Create a folder in <code>wp-content\/themes\/<\/code>, for example <code>astra-child<\/code>. Add a <code>style.css<\/code> with at least a <code>Theme Name<\/code> and <code>Template<\/code> header (the parent&#8217;s folder name), add a <code>functions.php<\/code> that loads the stylesheets, then activate the child theme under Appearance \u2192 Themes.<\/p>\n<h3>style.css<\/h3>\n<pre><code class=\"language-css\">\/*\nTheme Name:   Astra Child\nTemplate:     astra\nVersion:      1.0.0\nText Domain:  astra-child\n*\/\n<\/code><\/pre>\n<h3>functions.php<\/h3>\n<pre><code class=\"language-php\">&lt;?php\nadd_action( 'wp_enqueue_scripts', function () {\n    wp_enqueue_style(\n        'astra-child',\n        get_stylesheet_uri(),\n        array(),\n        wp_get_theme()-&gt;get( 'Version' )\n    );\n} );\n<\/code><\/pre>\n<p>Whether you also need to enqueue the parent stylesheet depends on how the parent loads its own CSS; many modern parents already do it. Check the page source to avoid loading it twice. Avoid <code>@import<\/code> in CSS: it delays stylesheet loading.<\/p>\n<p>Versioning the stylesheet with the theme version means browsers fetch the new file after each release, which matters when you use long cache headers (see <a href=\"\/how-to-improve-lcp-wordpress\/\">caching and LCP<\/a>).<\/p>\n<h2>Overriding templates<\/h2>\n<p>Copy a template file from the parent into the child with the same relative path, then edit it. WordPress loads the child&#8217;s version first. Keep overrides minimal: every copied template is code you now maintain when the parent changes. Prefer hooks and filters when the parent offers them.<\/p>\n<h2>The real problem: hardcoding<\/h2>\n<p>Here is a pattern I find in many inherited child themes:<\/p>\n<pre><code class=\"language-php\">&lt;h1&gt;We build fast websites&lt;\/h1&gt;\n&lt;a href=&quot;https:\/\/wa.me\/20100...&quot; style=&quot;background:#0f766e&quot;&gt;Chat now&lt;\/a&gt;\n<\/code><\/pre>\n<p>Every change to that headline or colour needs a developer and a deployment. Multiply this by every section and the site becomes frozen.<\/p>\n<h2>Making a child theme editable<\/h2>\n<h3>1. Customizer settings for site-wide options<\/h3>\n<p>Text, colours, toggles and links that appear on many pages belong in the Customizer:<\/p>\n<pre><code class=\"language-php\">add_action( 'customize_register', function ( $wp_customize ) {\n    $wp_customize-&gt;add_section( 'mx_hero', array(\n        'title' =&gt; __( 'Hero', 'astra-child' ),\n    ) );\n    $wp_customize-&gt;add_setting( 'mx_hero_title', array(\n        'default'           =&gt; __( 'Websites that work', 'astra-child' ),\n        'sanitize_callback' =&gt; 'sanitize_text_field',\n        'transport'         =&gt; 'postMessage',\n    ) );\n    $wp_customize-&gt;add_control( 'mx_hero_title', array(\n        'label'   =&gt; __( 'Hero title', 'astra-child' ),\n        'section' =&gt; 'mx_hero',\n        'type'    =&gt; 'text',\n    ) );\n} );\n<\/code><\/pre>\n<p>And in the template:<\/p>\n<pre><code class=\"language-php\">&lt;h1&gt;&lt;?php echo esc_html( get_theme_mod( 'mx_hero_title', __( 'Websites that work', 'astra-child' ) ) ); ?&gt;&lt;\/h1&gt;\n<\/code><\/pre>\n<p>Note three habits: a <strong>sanitize callback<\/strong> on every setting, <strong>escaping<\/strong> on output, and <strong>translatable defaults<\/strong>.<\/p>\n<h3>2. A single options helper<\/h3>\n<p>Instead of calling <code>get_theme_mod()<\/code> with different defaults in ten places, define defaults once in an array and read them through one helper function. Then defaults never drift apart.<\/p>\n<h3>3. Post meta for per-page content<\/h3>\n<p>Content that differs per page or post (a summary box, FAQ items, an SEO title) belongs in post meta, registered with <code>register_post_meta()<\/code> and <code>show_in_rest<\/code> so it also works in the block editor.<\/p>\n<h3>4. Custom post types for repeated items<\/h3>\n<p>Testimonials, clients, projects: anything that is a list of similar items should be a custom post type, not a hardcoded array. Register post types in a plugin, so they survive a theme switch.<\/p>\n<h3>5. CSS custom properties for design tokens<\/h3>\n<p>Output colours from settings as CSS variables once:<\/p>\n<pre><code class=\"language-css\">:root { --accent: #0f766e; }\n.button { background: var(--accent); }\n<\/code><\/pre>\n<p>Then a palette change is one setting, not a search-and-replace.<\/p>\n<h2>Child theme checklist<\/h2>\n<ul>\n<li><code>style.css<\/code> header correct; theme activates without errors.<\/li>\n<li>Styles enqueued with versions, no <code>@import<\/code>.<\/li>\n<li>No hardcoded copy, links, phone numbers or colours in templates.<\/li>\n<li>Every setting sanitised; every output escaped.<\/li>\n<li>Strings translatable with the theme&#8217;s text domain (needed for an <a href=\"\/wordpress-multilingual-site\/\">Arabic or multilingual site<\/a>).<\/li>\n<li>Functionality that must survive a theme change lives in a plugin.<\/li>\n<li>Code checked with PHP_CodeSniffer and the WordPress Coding Standards.<\/li>\n<\/ul>\n<h2>Common mistakes<\/h2>\n<ul>\n<li>Editing the parent theme directly; the next update wipes it.<\/li>\n<li>Copying the entire parent template folder &#8220;just in case.&#8221;<\/li>\n<li>Storing API keys in theme files.<\/li>\n<li>Inline styles everywhere, impossible to theme later.<\/li>\n<\/ul>\n<h2>Why a child theme still matters in 2026<\/h2>\n<p>WordPress now ships block themes and Full Site Editing, so some people assume child themes are obsolete. They are not. The official Theme Handbook still recommends a child theme whenever you change a theme you did not write, because a parent update replaces every file in the parent folder (<a href=\"https:\/\/developer.wordpress.org\/themes\/advanced-topics\/child-themes\/\" target=\"_blank\" rel=\"noopener\">WordPress Theme Handbook: Child Themes<\/a>). What changed is where the customisation lives. With a classic theme, you mostly override PHP templates and enqueue a stylesheet. With a block theme, a child theme can ship its own <code>theme.json<\/code>, block templates and template parts, and WordPress merges them over the parent&#8217;s settings (<a href=\"https:\/\/developer.wordpress.org\/themes\/global-settings-and-styles\/\" target=\"_blank\" rel=\"noopener\">Global Settings and Styles<\/a>).<\/p>\n<p>One trap with block themes: changes made in the Site Editor are saved in the database, not in files. They survive updates, but they are also invisible in your Git repository. For client sites I export those changes back into the child theme files before handover, so the next developer can see what was customised.<\/p>\n<h2>Classic theme vs block theme child: what to put where<\/h2>\n<table>\n<thead>\n<tr>\n<th>Change<\/th>\n<th>Classic parent<\/th>\n<th>Block parent<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Colours, fonts, spacing<\/td>\n<td>Child <code>style.css<\/code><\/td>\n<td>Child <code>theme.json<\/code><\/td>\n<\/tr>\n<tr>\n<td>Header or footer layout<\/td>\n<td>Copy <code>header.php<\/code> \/ <code>footer.php<\/code><\/td>\n<td>Template parts in <code>parts\/<\/code><\/td>\n<\/tr>\n<tr>\n<td>Page layout<\/td>\n<td>Copy the template file<\/td>\n<td>Block template in <code>templates\/<\/code><\/td>\n<\/tr>\n<tr>\n<td>New behaviour<\/td>\n<td><code>functions.php<\/code> hooks<\/td>\n<td><code>functions.php<\/code> hooks<\/td>\n<\/tr>\n<tr>\n<td>Business logic (post types, integrations)<\/td>\n<td colspan=\"2\">A small plugin, not the theme<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The last row is the one most projects get wrong. If a custom post type or payment integration lives in the theme, switching themes later breaks the site&#8217;s data. The Plugin Handbook&#8217;s guidance is simple: themes handle presentation, plugins handle functionality (<a href=\"https:\/\/developer.wordpress.org\/plugins\/intro\/\" target=\"_blank\" rel=\"noopener\">Plugin Developer Handbook<\/a>).<\/p>\n<h2>Keeping overrides maintainable<\/h2>\n<p>Every template you copy into a child theme is a copy you now own. When the parent theme fixes a bug or adds accessibility improvements to that template, your copy does not get them. A few habits keep this under control:<\/p>\n<ul>\n<li><strong>Prefer hooks and filters over copies.<\/strong> Many themes and WooCommerce expose actions around their markup. Adding a hook is a few lines; maintaining a copied template is a permanent job.<\/li>\n<li><strong>Record the parent version.<\/strong> Add a comment at the top of every copied file with the parent version it came from, so you can diff it after updates.<\/li>\n<li><strong>Watch WooCommerce template notices.<\/strong> WooCommerce reports outdated template overrides under WooCommerce \u203a Status, which is a quick check after every major update (<a href=\"https:\/\/woocommerce.com\/document\/template-structure\/\" target=\"_blank\" rel=\"noopener\">WooCommerce: Template structure and overriding templates<\/a>).<\/li>\n<li><strong>Keep the child theme in version control.<\/strong> Even a small agency benefits from Git: you can see who changed what and roll back a bad deploy in minutes.<\/li>\n<\/ul>\n<h2>Bilingual sites: translate the child theme properly<\/h2>\n<p>For Arabic and English sites, hardcoded English strings in a child theme are the most common reason an Arabic page still shows &#8220;Read more&#8221; or &#8220;Contact us&#8221;. Wrap every string in translation functions such as <code>__()<\/code> and <code>esc_html_e()<\/code> with your own text domain, then translate them through your multilingual plugin&#8217;s string translation or a <code>.po<\/code> file (<a href=\"https:\/\/developer.wordpress.org\/themes\/functionality\/internationalization\/\" target=\"_blank\" rel=\"noopener\">Theme Handbook: Internationalization<\/a>). Use logical CSS properties like <code>margin-inline-start<\/code> instead of <code>margin-left<\/code> so the layout mirrors correctly in RTL without a second stylesheet. More on that in the <a href=\"\/wordpress-multilingual-site\/\">multilingual WordPress guide<\/a>.<\/p>\n<h2>Handover notes for agencies and freelancers<\/h2>\n<p>Clients in Egypt and the Gulf often change developers every few years. A child theme that the next person can understand protects the client and your reputation. Before handover I include:<\/p>\n<ul>\n<li>A short README in the child theme listing every overridden template and why.<\/li>\n<li>Which settings live in the Customizer, the Site Editor or ACF options, so content editors know where to look.<\/li>\n<li>The parent theme name, version and licence, and where updates come from.<\/li>\n<li>A note of any custom functionality that lives in a companion plugin.<\/li>\n<\/ul>\n<p>This takes an hour and saves the client days later. It also makes routine work such as <a href=\"\/wp-cli-commands\/\">WP-CLI updates<\/a> and a periodic <a href=\"\/wordpress-plugin-audit-checklist\/\">plugin audit<\/a> much safer.<\/p>\n<p>A child theme should make the site easier to own, not harder. If your current theme is full of hardcoded content and you want it made editable, <a href=\"\/contact\/\">get in touch<\/a>. For the bigger platform question, read <a href=\"\/laravel-vs-wordpress\/\">Laravel vs WordPress<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>How to create a WordPress child theme the right way, and how to make it editable: Customizer settings, post meta and templates instead of hardcoded text and colours.<\/p>\n","protected":false},"author":1,"featured_media":252105,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":"","_mx_tldr":"A child theme lets you customise a parent theme without losing changes when the parent updates.\nYou need only a folder, a style.css with a Template header, and usually a functions.php.\nEnqueue stylesheets in functions.php; do not use @import.\nMove text, colours and toggles into Customizer settings so the site owner can edit them without code.","_mx_faq":"What is a WordPress child theme? | A child theme is a theme that inherits the templates and styles of a parent theme and overrides only what you change, so parent updates do not erase your work.\nDo I still need a child theme with block themes? | For block themes many changes can be saved in the Site Editor, but code changes such as PHP functions, custom templates and assets still belong in a child theme or a plugin.\nShould custom functionality go in the child theme or a plugin? | Design and presentation belong in the theme. Functionality that should survive a theme change, such as custom post types, belongs in a plugin."},"categories":[98],"tags":[176,177,178,179],"class_list":["post-251384","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-wordpress-platforms","tag-child-theme","tag-customizer","tag-functions-php","tag-theme-development"],"_links":{"self":[{"href":"https:\/\/muhamedahmed.com\/en\/wp-json\/wp\/v2\/posts\/251384","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/muhamedahmed.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/muhamedahmed.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/muhamedahmed.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/muhamedahmed.com\/en\/wp-json\/wp\/v2\/comments?post=251384"}],"version-history":[{"count":1,"href":"https:\/\/muhamedahmed.com\/en\/wp-json\/wp\/v2\/posts\/251384\/revisions"}],"predecessor-version":[{"id":252504,"href":"https:\/\/muhamedahmed.com\/en\/wp-json\/wp\/v2\/posts\/251384\/revisions\/252504"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/muhamedahmed.com\/en\/wp-json\/wp\/v2\/media\/252105"}],"wp:attachment":[{"href":"https:\/\/muhamedahmed.com\/en\/wp-json\/wp\/v2\/media?parent=251384"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/muhamedahmed.com\/en\/wp-json\/wp\/v2\/categories?post=251384"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/muhamedahmed.com\/en\/wp-json\/wp\/v2\/tags?post=251384"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}