Skip to content

Creating Pages Programmatically in Gatsby — Dynamic Page Generation

DodaTech Updated 2026-06-28 4 min read

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

  1. Not using path.resolve for template paths: Template paths must be absolute. Use path.resolve('./src/templates/template.js').
  2. Forgetting to restart after changing gatsby-node.js: Changes to this file require restarting the dev server.
  3. Not handling query errors: Always check result.errors before accessing data. A failed query silently returns no pages.
  4. Creating duplicate pages: If two items have the same slug, createPage throws an error. Ensure unique paths.
  5. Missing template file: The template path must exist. A wrong path causes a build error with a file-not-found message.

Practice Questions

  1. What Gatsby API creates pages programmatically? Answer: exports.createPages in gatsby-node.js. Use actions.createPage() within it.

  2. How do you pass data from gatsby-node.js to a page template? Answer: Via the context option in createPage. Each key becomes a $variable in the template's page query.

  3. What does path.resolve do in this context? Answer: It converts a relative path to an absolute file path, which Gatsby requires for the component option.

  4. How do you create paginated pages? Answer: Calculate total pages, loop with Array.from, and pass skip and limit as 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

Can I create pages from external API data?

: Yes. Query external APIs with fetch or gatsby-source-graphql in createPages and generate pages from the results.

How do I create pages conditionally?

: Filter data before looping. Use Array.filter or query with filters to select only the items you want as pages.

Can I update pages without rebuilding?

: With Gatsby Cloud's incremental builds, yes. Changed source data triggers a rebuild of only affected pages.

What is the maximum number of pages?

: There's no hard limit, but build time increases with page count. 10,000+ pages are feasible with optimizations.

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