Chris Biscardi

moon indicating dark mode
sun indicating light mode

Applying theme options using custom configuration nodes

Building large Gatsby themes can result in a large array of configuration options to manage as a theme author. We can also find ourselves wanting the ability to let other themes or users’ sites to add additional configuration instances (ex: get more blog posts from this folder and process them as usual).

Let’s take a look at an example of this pattern. First, set up our configuration node type in the Gatsby system using createSchemaCustomization.

exports.createSchemaCustomization = (
{ actions },
) => {
const { createTypes } = actions
createTypes(`type NotesConfig implements Node {
basePath: String!
homeText: String
breadcrumbSeparator: String
}`)

Then in sourceNodes we’ll talk the options configured with defaults and inject them into the node system as a node.

var crypto = require("crypto");
exports.sourceNodes = (
{ actions: { createNode }, schema },
{
basePath = `/`,
homeText = `~`,
breadcrumbSeparator = `/`
}
) => {
// create garden data from plugin config
const notesConfig = {
breadcrumbSeparator,
basePath,
homeText
};
createNode({
...notesConfig,
id: `gatsby-theme-notes-config`,
parent: null,
children: [],
internal: {
type: `NotesConfig`,
contentDigest: crypto
.createHash(`md5`)
.update(JSON.stringify(notesConfig))
.digest(`hex`),
content: JSON.stringify(notesConfig),
description: `Notes Config`
}
});
};

Note that in this case we’ve specified a specific known ID we can query. It is best practice to use createNodeId for ids like this and we’re doing it this way in this post for some clarity.

Because we’ve specified a specific ID, and because of Gatsby’s implementation of node ownership, our theme effectively owns this object and we can be confident it hasn’t been modified. Using useStaticQuery we can now access this data inside our components anywhere in our application.

import React from "react";
import { useStaticQuery, graphql } from "gatsby";
export default () => {
const data = useStaticQuery(graphql`
query NotesConfigObjectQuery {
nodesConfig(id: { eq: "gatsby-theme-notes-config" }) {
homeText
breadcrumbSeparator
}
}
`);
return (
<header>
<h1>{data.notesConfig.homeText}</h1>
</header>
);
};

Combining Nodes

If we had some field that semantically could be included multiple times, we could skip the specific ID and query allNotesConfig with our own merge algorithm. Let’s take a look at a hypothetical someOption field that is typed as such:

type someOption = [String!]!

Querying this gives us a set of nested arrays.

import React from "react";
import { useStaticQuery, graphql } from "gatsby";
export default () => {
const data = useStaticQuery(graphql`
query NotesConfigObjectQuery {
allNotesConfig {
nodes {
someOption
}
}
}
`);
return (
<header>
<ul>
{data.allNotesConfig.nodes
.map(({ someOption }) => someOption)
.flat()
.map(item => (
<li>{item}</li>
))}
</ul>
</header>
);
};