Creating Pages Programmatically in Gatsby — Dynamic Page Generation
Learn how to create pages programmatically in Gatsby using gatsby-Node.js, enabling dynamic pages from Markdown, CMS, and API data.
In this lesson, you'll understand the createPages API, how to query data in gatsby-node.js, and how to generate pages with templates.
What You'll Learn
How to use gatsby-node.js to create pages from data, pass context variables, use templates, and create paginated archives.
Why It Matters
Programmatic page creation is essential for any data-driven site. Instead of writing individual page files, you generate pages from your data automatically.
flowchart LR
A[gatsby-node.js] --> B[Query Data]
B --> C[All Blog Posts]
C --> D[forEach Post]
D --> E[createPage]
E --> F[Template Component]
E --> G[Context Variables]
G --> H[Page Query]
style A fill:#639,color:#fff
style D fill:#4a148c,color:#fff
Basic createPages
Create pages from Markdown files:
// gatsby-node.js
const path = require('path');
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const result = await graphql(`
query {
allMarkdownRemark {
nodes {
fields { slug }
frontmatter { title }
}
}
}
`);
result.data.allMarkdownRemark.nodes.forEach(node => {
createPage({
path: node.fields.slug,
component: path.resolve('./src/templates/blog-post.js'),
context: {
slug: node.fields.slug
}
});
});
};
Output: For every Markdown file, Gatsby creates a page at the file's slug URL, rendered with the blog-post.js template component.
Querying in gatsby-node.js
The <a href="/apis/graphql/">Graphql</a> function in gatsby-node.js is a helper that queries the same data layer used by pages:
exports.createPages = async ({ graphql, actions }) => {
const result = await graphql(`
query {
allContentfulBlogPost {
nodes {
slug
title
category { slug }
}
}
allStrapiArticle {
nodes { slug }
}
}
`);
if (result.errors) {
console.error('Query errors:', result.errors);
return;
}
// Access data from multiple sources
console.log(`Found ${result.data.allContentfulBlogPost.nodes.length} Contentful posts`);
};
Output: The query can access multiple data sources. Always check for errors before accessing result.data.
Template Components
A template receives data through page queries:
// src/templates/blog-post.js
import { graphql } from 'gatsby';
import React from 'react';
export default function BlogPost({ data }) {
const post = data.markdownRemark;
return (
<article>
<h1>{post.frontmatter.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.html }} />
</article>
);
}
// $slug comes from context in createPage
export const query = graphql`
query BlogPostBySlug($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
html
frontmatter { title date tags }
}
}
`;
Output: Each blog post page renders its content using the template. The $slug variable matches the correct post.
Paginated Archives
Create multiple pages with pagination:
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const postsPerPage = 10;
const result = await graphql(`
query {
allMarkdownRemark {
totalCount
}
}
`);
const totalPages = Math.ceil(
result.data.allMarkdownRemark.totalCount / postsPerPage
);
Array.from({ length: totalPages }).forEach((_, i) => {
createPage({
path: i === 0 ? '/blog' : `/blog/${i + 1}`,
component: path.resolve('./src/templates/blog-list.js'),
context: {
limit: postsPerPage,
skip: i * postsPerPage,
currentPage: i + 1,
totalPages
}
});
});
};
Output: If there are 25 posts with 10 per page, Gatsby creates /blog, /blog/2, and /blog/3 with the appropriate posts.
Creating Pages from JSON/YAML
Generate pages from structured data files:
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const result = await graphql(`
query {
allProductsYaml {
nodes {
id
name
slug
}
}
}
`);
result.data.allProductsYaml.nodes.forEach(product => {
createPage({
path: `/products/${product.slug}/`,
component: path.resolve('./src/templates/product.js'),
context: { id: product.id }
});
});
};
Output: Each product in the YAML file gets its own page at /products/product-slug/.
Common Mistakes
- Not using
path.resolvefor template paths: Template paths must be absolute. Usepath.resolve('./src/templates/template.js'). - Forgetting to restart after changing
gatsby-node.js: Changes to this file require restarting the dev server. - Not handling query errors: Always check
result.errorsbefore accessing data. A failed query silently returns no pages. - Creating duplicate pages: If two items have the same slug,
createPagethrows an error. Ensure unique paths. - Missing template file: The template path must exist. A wrong path causes a build error with a file-not-found message.
Practice Questions
What Gatsby API creates pages programmatically? Answer:
exports.createPagesingatsby-node.js. Useactions.createPage()within it.How do you pass data from gatsby-node.js to a page template? Answer: Via the
contextoption increatePage. Each key becomes a$variablein the template's page query.What does
path.resolvedo in this context? Answer: It converts a relative path to an absolute file path, which Gatsby requires for thecomponentoption.How do you create paginated pages? Answer: Calculate total pages, loop with
Array.from, and passskipandlimitas context variables for the query.
Challenge
Create a product catalog where products are sourced from a YAML file. Generate individual product pages, a category archive page for each category, and a paginated product listing.
Mini Project
Build a complete blog with: individual post pages from Markdown, paginated blog archive (10 per page), tag archive pages, and an author page for each author in the frontmatter.
FAQ
What's Next
Learn about Gatsby Template Components for building reusable templates for programmatically created pages.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro