Grav Custom Page Types — folder.md vs default.md and Modular Pages
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:
folder.md— frontmatter only (the body content is ignored)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
- A modular page is defined by
modular.mdin a folder - The modular page folder contains sub-folders, each with a
default.md - 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:
- Create the template:
templates/custom-type.html.twig - Name the file:
custom-type.mdin the page folder - Grav automatically maps
custom-type.mdtocustom-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
Using
default.mdwhen you needfolder.md: If child pages need to inherit frontmatter settings, usefolder.md. Usingdefault.mdmeans each child must duplicate the settings.Missing
modular.mdfor modular pages: A modular page requiresmodular.mdas the container file. Usingdefault.mdinstead will not trigger modular rendering — child modules will be ignored.Module templates in wrong directory: Module templates go in
templates/modular/, nottemplates/. A module withtemplate: modular/heroexpectstemplates/modular/hero.html.twig.Forgetting
content.items: '@self.modular': The modular page's frontmatter must includecontent.items: '@self.modular'to tell Grav to collect and render child modules. Without it, the modular page renders as empty.Duplicate frontmatter between
folder.mdand child pages: When bothfolder.mdandchild/default.mddefine the same field, the child wins. This is intentional for overrides, but can confuse when you expect the parent value to apply.
Practice Questions
What is the difference between
default.mdandfolder.md? Answer:folder.mdstores page configuration that child pages inherit.default.mdstores the page body content. When both exist, Grav merges frontmatter (child overrides parent) and usesdefault.mdfor body content.How do you create a modular page in Grav? Answer: Create a page folder with
modular.mdas the container, then add sub-folders (each withdefault.md) for each module. Themodular.mdmust includecontent.items: '@self.modular'.What template file would
features.mduse? Answer: Grav mapsfeatures.mdtofeatures.html.twigin the templates directory. Any custom filename creates a custom template mapping.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.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
Mini Project
Goal: Build a modular marketing landing page for a fictional product.
- Create a modular page folder at
user/pages/01.home/withmodular.md - Add five modules: hero, features, testimonials, pricing, cta
- Create templates for each module in
templates/modular/ - The hero module must include a background image and CTA button
- The features module must display a 3-column grid using header data
- The testimonials module must display customer quotes
- The pricing module must show three pricing tiers
- The cta module must include a contact form (use the Form plugin)
- Test that all modules render in the correct order
- Add
folder.mdto 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