Skip to content

Grav Custom Page Types — folder.md vs default.md and Modular Pages

DodaTech Updated 2026-06-27 9 min read

In this tutorial, you'll learn Grav's custom page types including the difference between folder.md and default.md, how modular pages work, and when to use each type for complex layouts.

What You'll Learn

  • The difference between folder.md and default.md page types
  • How Grav maps filenames to Twig templates
  • Modular pages: what they are and how to build them
  • Combining page types for complex page layouts
  • When to use each page type for specific use cases

Why It Matters

In WordPress, you create a page and choose a template from a dropdown. The template file handles the layout. In Grav, the page type is determined by the Markdown filename itself. This direct mapping means you control the template simply by naming the file differently. Understanding page types lets you build anything from a simple content page to a complex landing page composed of multiple independent sections. Master this and you unlock Grav's full layout potential.

Real-World Use

A marketing site needs a landing page with a hero section, feature grid, testimonial carousel, pricing table, and contact form — all on one page. Using a modular page, you create each section as its own Markdown file and Grav renders them in order. The marketing team edits each section independently, and the page stays organized. Compare this to Drupal where you would need a Paragraphs or Layout Builder setup.

folder.md vs default.md

Grav page folders must contain at least one Markdown file. The filename determines the template type:

Filename Template Behavior
default.md default.html.twig Standard page content
folder.md default.html.twig Same template, different frontmatter handling
blog.md blog.html.twig Blog listing page
item.md item.html.twig Individual blog post
modular.md modular.html.twig Modular page container

default.md

The standard page file. Every page folder typically has a default.md:

user/pages/01.home/
└── default.md

folder.md

The folder.md file is used when you want to separate the page content from the page configuration. Think of folder.md as the page's "about itself" metadata, and default.md as the actual content.

user/pages/02.products/
├── folder.md          # Page config and frontmatter only
└── default.md         # Default content

When both exist, Grav reads them in this order:

  1. folder.md — frontmatter only (the body content is ignored)
  2. default.md — frontmatter merged with folder.md, body as page content
--- # user/pages/02.products/folder.md
title: Products
menu: Products
published: true
---
--- # user/pages/02.products/default.md
# Our Products

We offer a range of software tools for developers.

## Doda Browser
A fast, secure browser built for privacy.

## Durga Antivirus Pro
Enterprise-grade malware protection.
---

Why separate them? Because folder.md is inherited by child pages. Settings defined in folder.md apply to sub-pages unless overridden. This is powerful for section-wide defaults.

Practical Example: Section Defaults

user/pages/03.docs/
├── folder.md                  # Docs section: all children inherit
│   title: Documentation
│   menu: Docs
│   body_classes: docs-section
│   taxonomy:
│       category: documentation
├── 01.getting-started/
│   └── default.md             # Inherits parent folder settings
├── 02.installation/
│   └── default.md             # Inherits parent folder settings
└── 03.api-reference/
    └── default.md             # Inherits parent folder settings

The folder.md at 03.docs/ sets body_classes: docs-section and taxonomy: category: documentation. Every child page gets these settings automatically. If a child needs body_classes: different-class, it overrides in its own frontmatter.

Modular Pages

Modular pages are single pages composed of multiple independent content blocks called modules. Each module is a separate Markdown file with its own template.

How Modular Pages Work

  1. A modular page is defined by modular.md in a folder
  2. The modular page folder contains sub-folders, each with a default.md
  3. Grav renders each sub-page in order and concatenates the output
user/pages/04.home/
├── modular.md                    # The modular page container
├── 01.hero/
│   └── default.md                # Module 1: Hero section
├── 02.features/
│   └── default.md                # Module 2: Features grid
├── 03.testimonials/
│   └── default.md                # Module 3: Testimonials
└── 04.cta/
    └── default.md                # Module 4: Call to action

Creating a Modular Page

--- # user/pages/04.home/modular.md
title: Home
menu: Home
published: true
body_classes: modular-home

content:
    items: '@self.modular'
    order:
        by: default
        dir: asc
---

Each module has its own template and content:

--- # user/pages/04.home/01.hero/default.md
title: Hero Section
template: modular/hero
---
# Welcome to DodaTech

The best developer tools, built by developers for developers.

The template: modular/hero tells Grav to use templates/modular/hero.html.twig:

<section class="hero">
    <div class="hero-content">
        {{ page.content|raw }}
    </div>
</section>

Module Templates

Module templates live in templates/modular/ and each one renders a single section:

user/themes/mytheme/templates/modular/
├── hero.html.twig
├── features.html.twig
├── testimonials.html.twig
└── cta.html.twig

Passing Variables Between Modules

Each module operates independently. To share data, use the page.header frontmatter:

--- # 02.features/default.md
title: Features
template: modular/features
features:
    - title: Fast Performance
      icon: zap
      description: Grav loads in milliseconds.
    - title: No Database
      icon: database-off
      description: Flat-file architecture.
    - title: Git-Friendly
      icon: git-branch
      description: Version control by default.
---
{# templates/modular/features.html.twig #}
<section class="features">
    <h2>{{ page.title }}</h2>
    <div class="feature-grid">
        {% for feature in page.header.features %}
        <div class="feature-card">
            <h3>{{ feature.title }}</h3>
            <p>{{ feature.description }}</p>
        </div>
        {% endfor %}
    </div>
</section>

Output:

<section class="features">
    <h2>Features</h2>
    <div class="feature-grid">
        <div class="feature-card">
            <h3>Fast Performance</h3>
            <p>Grav loads in milliseconds.</p>
        </div>
        <div class="feature-card">
            <h3>No Database</h3>
            <p>Flat-file architecture.</p>
        </div>
        <div class="feature-card">
            <h3>Git-Friendly</h3>
            <p>Version control by default.</p>
        </div>
    </div>
</section>

Modular Page Ordering

Modules render in the order they appear in the folder (sorted by number prefix):

01.hero/       → renders first
02.features/   → renders second
03.testimonials/ → renders third
04.cta/        → renders fourth

Change the order by renaming the folders:

mv 01.hero/ 02.hero/      # Swap: features renders first

Combining Page Types

You can nest modular pages inside standard pages. A blog page can have a modular home page:

user/pages/
├── 01.home/                 # Modular home page
│   ├── modular.md
│   ├── 01.hero/
│   └── 02.features/
├── 02.blog/                 # Standard blog listing
│   ├── blog.md
│   └── 01.first-post/
│       └── item.md
└── 03.about/               # Standard about page
    └── default.md

Template Override with Custom Page Types

Create custom page types by creating new templates:

  1. Create the template: templates/custom-type.html.twig
  2. Name the file: custom-type.md in the page folder
  3. Grav automatically maps custom-type.md to custom-type.html.twig
{# templates/landing.html.twig #}
{% extends 'partials/base.html.twig' %}

{% block content %}
    <div class="landing-page">
        {{ page.content|raw }}
    </div>
{% endblock %}
--- # user/pages/05.special/landing.md
title: Special Landing Page
---
This page uses the landing.html.twig template.

Learning Path

flowchart LR
    A["What is Grav?"] --> B["Installation"]
    B --> C["Pages & Content"]
    C --> D["Navigation"]
    D --> E["Twig Templating"]
    E --> F["Themes"]
    F --> G["Taxonomy & Blog"]
    G --> H["Plugins & Admin"]
    H --> I["Configuration & Caching"]
    I --> J["Deployment & Maintenance"]
    J --> K["Custom Page Types
← You are here"]:::current K --> L["Page Meta & Frontmatter"] L --> M["Page Collections"] M --> N["Markdown & Shortcodes"] classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px

Common Mistakes

  1. Using default.md when you need folder.md: If child pages need to inherit frontmatter settings, use folder.md. Using default.md means each child must duplicate the settings.

  2. Missing modular.md for modular pages: A modular page requires modular.md as the container file. Using default.md instead will not trigger modular rendering — child modules will be ignored.

  3. Module templates in wrong directory: Module templates go in templates/modular/, not templates/. A module with template: modular/hero expects templates/modular/hero.html.twig.

  4. Forgetting content.items: '@self.modular': The modular page's frontmatter must include content.items: '@self.modular' to tell Grav to collect and render child modules. Without it, the modular page renders as empty.

  5. Duplicate frontmatter between folder.md and child pages: When both folder.md and child/default.md define the same field, the child wins. This is intentional for overrides, but can confuse when you expect the parent value to apply.

Practice Questions

  1. What is the difference between default.md and folder.md? Answer: folder.md stores page configuration that child pages inherit. default.md stores the page body content. When both exist, Grav merges frontmatter (child overrides parent) and uses default.md for body content.

  2. How do you create a modular page in Grav? Answer: Create a page folder with modular.md as the container, then add sub-folders (each with default.md) for each module. The modular.md must include content.items: '@self.modular'.

  3. What template file would features.md use? Answer: Grav maps features.md to features.html.twig in the templates directory. Any custom filename creates a custom template mapping.

  4. How do you control the order of modules on a modular page? Answer: By numbering the module folders (e.g., 01.hero/, 02.features/). Grav sorts by the folder number prefix and renders in ascending order.

  5. Challenge: Build a complete modular landing page for a product. The landing page should have: a hero section with headline and subtitle, a features section with a three-column grid (each feature has icon, title, description), a testimonials section with customer quotes in a carousel layout, a pricing section with three tiers, and a call-to-action section with a contact form. Create all module templates and ensure the page renders correctly in order.

FAQ

What happens if both default.md and folder.md exist in the same page folder?

Grav reads folder.md first for frontmatter (the body is ignored), then default.md for body content and additional frontmatter. Child frontmatter overrides parent frontmatter.

Can I have a modular page inside another modular page?

No. Grav does not support nested modular pages. Each modular page can only contain regular page modules. You can, however, use standard pages as children of a modular page.

Do modular pages affect site performance?

Each module requires a separate file read and template render. With 5-10 modules the impact is negligible. With 50+ modules, consider combining content or caching aggressively.

How do I pass data between modules on the same page?

Modules are independent. Use the page header frontmatter to pass data, or use a Twig variable set in the modular.html.twig template that each module template can access.

Can I use folder.md on a modular page?

Yes. The modular.md file serves a similar role to folder.md for modular pages. You can also include a folder.md alongside modular.md for additional configuration.

Mini Project

Goal: Build a modular marketing landing page for a fictional product.

  1. Create a modular page folder at user/pages/01.home/ with modular.md
  2. Add five modules: hero, features, testimonials, pricing, cta
  3. Create templates for each module in templates/modular/
  4. The hero module must include a background image and CTA button
  5. The features module must display a 3-column grid using header data
  6. The testimonials module must display customer quotes
  7. The pricing module must show three pricing tiers
  8. The cta module must include a contact form (use the Form plugin)
  9. Test that all modules render in the correct order
  10. Add folder.md to the parent page to set section-wide defaults

What's Next

Now you understand custom page types and modular pages. Next, learn how to control every aspect of page metadata:

Continue to Lesson 12: Page Meta & Frontmatter — Advanced YAML frontmatter, field overrides, and image headers.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro