Skip to content

Grav Theme Inheritance — Child Themes and Template Overrides

DodaTech Updated 2026-06-27 8 min read

In this tutorial, you'll learn Grav theme inheritance — how to create child themes, override parent templates without modifying core files, extend parent theme functionality, and structure theme customization for maintainability.

What You'll Learn

  • What child themes are and why they are essential
  • Creating a child theme from a parent theme
  • Overriding templates, assets, and configuration
  • Extending parent theme blocks and macros
  • Best practices for child theme organization
  • Updating parent themes without losing customizations

Why It Matters

In WordPress, child themes are the standard way to customize themes without losing changes on update. The same concept applies to Grav. If you edit a parent theme directly, your changes are lost when the parent theme is updated. A child theme inherits everything from the parent but lets you override specific files. This means you can customize any template, CSS file, or configuration while keeping the parent theme installable and updatable.

Real-World Use

A design agency builds sites on the Quark theme (Grav's default). Each client needs custom templates, colors, and layouts. Instead of copying and forking the Quark theme for each client, they create a child theme. The child theme overrides 3 templates and a CSS file. When Quark gets a security update, they apply it without touching the client's customizations. The child theme continues to work because it only overrides specific files, inheriting the rest from the updated parent.

Learning Path

flowchart LR
    A["Theme Assets"] --> B["Theme Inheritance
← You are here"]:::current B --> C["Theme Languages"] C --> D["Plugin Architecture"] D --> E["Plugin Events"] E --> F["Plugin Forms"] classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px

What Is a Child Theme?

A child theme is a theme that inherits all templates, assets, and configuration from a parent theme. You only create files for the parts you want to change.

Parent theme (Quark):

user/themes/quark/
├── quark.yaml
├── blueprints.yaml
├── templates/
│   ├── partials/
│   │   ├── base.html.twig
│   │   ├── header.html.twig
│   │   └── footer.html.twig
│   ├── default.html.twig
│   ├── blog.html.twig
│   └── item.html.twig
├── css/
│   ├── styles.css
│   └── custom.css
├── js/
│   └── main.js
└── images/

Child theme (mytheme):

user/themes/mytheme/
├── mytheme.yaml
├── blueprints.yaml
├── templates/
│   └── partials/
│       └── header.html.twig    # ← Only file to override
├── css/
│   └── custom.css              # ← Additional CSS
└── child-theme.yaml

Everything else is inherited from Quark.

Creating a Child Theme

Step 1: Create Theme Directory

mkdir -p user/themes/mytheme

Step 2: Create child-theme.yaml

This file tells Grav that this theme is a child of another theme:

user/themes/mytheme/child-theme.yaml:

name: My Child Theme
version: 1.0.0
parent: quark
description: A child theme of Quark for DodaTech documentation

Step 3: Create mytheme.yaml

A minimal mytheme.yaml is needed for theme configuration:

user/themes/mytheme/mytheme.yaml:

enabled: true

Step 4: Enable the Child Theme

Navigate to the Admin panel and set "My Child Theme" as the active theme. Or edit user/config/system.yaml:

pages:
    theme: mytheme

Overriding Templates

Any template file you create in the child theme overrides the parent theme's file at the same path.

Override a Single Template

Create user/themes/mytheme/templates/blog.html.twig:

{% extends 'partials/base.html.twig' %}

{% block content %}
    <div class="blog-listing custom-listing">
        <h1>{{ page.title }}</h1>
        {% for child in page.collection %}
            <article class="blog-card">
                <h2><a href="{{ child.url }}">{{ child.title }}</a></h2>
                <p>{{ child.summary }}</p>
            </article>
        {% endfor %}
    </div>
{% endblock %}

This overrides the parent's blog.html.twig completely. All other templates (default.html.twig, item.html.twig, etc.) are inherited from the parent.

Override a Partial

Create user/themes/mytheme/templates/partials/header.html.twig:

<header class="custom-header">
    <div class="logo">
        <a href="{{ base_url_absolute }}">
            <img src="{{ url('theme://images/dodatech-logo.svg') }}" alt="DodaTech" />
        </a>
    </div>
    <nav>
        {% include 'partials/navigation.html.twig' %}
    </nav>
</header>

Extending Parent Templates

Instead of completely replacing a template, you can extend it and override specific blocks:

templates/default.html.twig in child theme:

{% extends 'default.html.twig' %}

{% block content %}
    {{ parent() }}  {# Includes parent content #}
    <section class="page-footer">
        <p>Last updated: {{ page.modified|date('F j, Y') }}</p>
    </section>
{% endblock %}

This keeps the parent's content block and adds a "Last updated" section at the bottom.

Overriding Assets

CSS

Create user/themes/mytheme/css/custom.css:

/* Override parent theme styles */
:root {
    --primary: #e74c3c;
    --secondary: #f39c12;
}

.header {
    background: var(--primary);
}

Load it in the child theme's base template override:

{% extends 'partials/base.html.twig' %}

{% block stylesheets %}
    {{ parent() }}
    {% do assets.addCss('theme://css/custom.css') %}
{% endblock %}

JS

{% block javascripts %}
    {{ parent() }}
    {% do assets.addJs('theme://js/custom.js') %}
{% endblock %}

Overriding Configuration

Create user/themes/mytheme/blueprints.yaml to add or override parent theme settings:

form:
    validation: loose
    fields:
        custom_logo:
            type: file
            label: Custom logo
            destination: 'theme://images'

Add the child theme's configuration in mytheme.yaml:

enabled: true
custom_logo: 'theme://images/dodatech-logo.svg'

Inheritance Resolution Order

When Grav looks for a template file, it checks:

  1. Child theme — does the file exist in the child theme directory?
  2. Parent theme — if not, does it exist in the parent theme directory?
  3. Error — if neither exists, Twig throws a "template not found" error

This means the child theme does not need to replicate the entire parent theme — only the files it wants to change.

Multiple Levels of Inheritance

Grav supports multi-level theme inheritance:

mytheme-child (child of mytheme)
    └── mytheme (child of quark)
        └── quark (base theme)

user/themes/mytheme-child/child-theme.yaml:

parent: mytheme

If a file is not found in mytheme-child, Grav checks mytheme, then quark.

Learning Path

flowchart LR
    A["Theme Assets"] --> B["Theme Inheritance
← You are here"]:::current B --> C["Theme Languages"] C --> D["Plugin Architecture"] D --> E["Plugin Events"] classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px

Common Mistakes

  1. Editing the parent theme directly: Any change to the parent theme is lost when it is updated. Always create a child theme for customizations.

  2. Not creating child-theme.yaml: Without this file, Grav does not know that the theme is a child theme. It treats it as a standalone theme, and inheritance does not work.

  3. Forgetting parent() when extending blocks: If you override a block and want to keep the parent's content, call {{ parent() }}. Without it, the parent block content is replaced entirely.

  4. Wrong template path in child theme: The path in the child theme must exactly match the path in the parent theme. A file at templates/partials/header.html.twig in the parent must be at templates/partials/header.html.twig in the child.

  5. Overriding too much: Only override files that need changes. Everything else inherits automatically. Overriding unmodified files defeats the purpose of inheritance and creates maintenance burden.

Practice Questions

  1. What file tells Grav that a theme is a child theme? Answer: child-theme.yaml with a parent: field specifying the parent theme name. Without this file, inheritance does not work.

  2. How does Grav resolve template files between parent and child themes? Answer: Grav checks the child theme first. If the file exists there, it is used. If not, Grav falls back to the parent theme directory. This continues up the inheritance chain.

  3. How do you add new CSS files in a child theme while keeping the parent's CSS? Answer: Override the {% block stylesheets %} in the base template, call {{ parent() }} to keep parent CSS, then add new assets: {% do assets.addCss('theme://css/custom.css') %}.

  4. What happens to a child theme when the parent theme is updated? Answer: The child theme's overridden files are unaffected. New features in the parent theme are inherited automatically by the child theme unless overridden.

  5. Challenge: Create a two-level child theme system. Base theme (quark) with standard templates. First child theme (my-base) that overrides the header template, adds a CSS file, and adds 3 configuration options. Second child theme (my-client) that is a child of my-base and overrides the footer template, adds its own CSS, and sets custom configuration values. Verify that templates resolve correctly at each level of the inheritance chain.

FAQ

Can a child theme add new templates that do not exist in the parent?

Yes. Create any new template file in the child theme. It works like a regular template because Twig finds it in the child theme directory first.

How do I override a template that uses `@extends` from the parent?

Create the template file at the same path in the child theme. In the child version, you can extend a different template or override blocks as needed.

Can I use a child theme with any parent theme?

Yes, as long as the parent theme is installed. Create child-theme.yaml with the parent theme name and enable the child theme in configuration.

What if the parent theme is updated and removes a template my child theme overrides?

The child theme's override file still works. However, the parent update might introduce new features that rely on the removed template. Test after parent updates.

Can I override plugin templates with a child theme?

Yes, if the plugin provides templates in a theme-overridable location. Plugin templates in templates/ can be overridden by creating the same path in the child theme.

Mini Project

Goal: Create a child theme from Quark with 10 customizations.

  1. Create the child theme directory and child-theme.yaml pointing to quark
  2. Create mytheme.yaml and blueprints.yaml with 3 custom options
  3. Override the header template to use a custom logo
  4. Override the footer template with custom copyright and social links
  5. Override default.html.twig to add a sidebar (using extended block with parent())
  6. Override blog.html.twig to use a card-based layout
  7. Override item.html.twig with an author bio section
  8. Add a custom CSS file that changes primary colors
  9. Add a custom JS file for interactive features
  10. Enable the child theme, verify all overrides work, and confirm unmodified templates are inherited

What's Next

Now you can customize themes without losing updates. Next, learn theme language support:

Continue to Lesson 23: Theme Languages — Multilingual theme strings, translation files, and language-aware templates.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro