gatsby-plugin-node-fields
offers you a simple, consistent way to manage the creation of fields on your nodes, with support for default values, transformations and validation of values. It is well tested and uses helpful error messages to guide you away from the rocks.
yarn add gatsby-plugin-node-fields
You can use gatsby-plugin-node-fields
either as a standard Gatsby plugin, or you can use it as standalone function.
If you want to use it as a plugin, add it as the last plugin. You can place it anywhere, but bear in mind that it will only see changes made by plugins that come before it. Unless you have a good reason not to, place it last.
// gatsby-config.js
const plugins = [
…
{
resolve: `gatsby-plugin-node-fields`,
options: {
descriptors: [
…
],
},
}
]
module.exports = {
plugins,
}
If you'd prefer to use it as a function in your gatsby-node.js
file, you need to tie into Gatsby's onCreateNode
yourself:
// gatsby-node.js
const { attachFields } = require(`gatsby-plugin-node-fields`)
const descriptors = [
…
]
exports.onCreateNode = ({ node, boundActionCreators }) => {
const { createNodeField } = boundActionCreators
attachFields(node, createNodeField, descriptors)
}
gatsby-plugin-node-fields
can be used as a function or as a plugin. It allows you to describe any values on a node should be transformed into fields on the same node. It is particularly useful with Markdown nodes' frontmatter
fields, but you can use it with any values on a node.
I have found that mixing queries for values stored in a node's frontmatter
with queries for values stored in its fields is uneven and confusing, so I now transfer all values that I will use in the UI over to fields. This transfer gives us the opportunity to do a number of important things:
- set a default value
- validate a value
- transform a value
This library gives you a consistent way to do this using a series of descriptors.
The plugin hooks into Gatsby's onCreateNode
life cycle hook and will check the node it receives against an array of descriptors you provide. Each descriptor must provide a predicate - a function that looks at the node and decides whether the descriptor should be used to transform it. For example we might want to check if the node is Markdown node, or if it represents a file from a particular directory.
If a descriptor's predicate returns true, the descriptor will be used to create new fields on that node.
Here is an example of a list of descriptors:
[
{
predicate: isMarkdownNode,
fields: [
{
name: 'title',
validator: isString,
transformer: preventOrphans,
},
{
name: 'date',
defaultValue: dateToday,
validator: isValidDate,
}
]
}
]
Your descriptors will be validated against a schema when first used with useful error messages if you have added an invalid field or the value of required field is invalid or missing.
A descriptor decides if a node should have fields added to it, and describes how these new fields should be created. Each descriptor is an object with two required fields:
A function that receives the newly created node as its single argument and returns true
if the descriptor should apply to that node and false
if it doesn't. Multiple descriptors can be applied to the same node if their predicates return true.
An array of objects representing fields that will be created on the node. Each object comprises of a set of keys and values that describe the creation process of a new field. You can use as few or as many keys as needed. For example if all you want to do is set a default value you could use only the name
and defaultValue
keys:
{
name: 'example',
defaultValue: 'Unknown',
}
A field can contain one or more of the following keys that describe which fields it should add. You can perform a one-to-one, many-to-one, or one-to-many mapping between values and fields. The order in which these fields are used is
getter
orname
defaultValue
validator
transformer
setter
orname
A name
field represents the name of the field that will be created. If no getter
field is present on the descriptor, it will also be used to access a value on the node. For example if the name
is 'alpha', it will create a field on the node called 'alpha'. If no getter
field is present on the descriptor is will try and get the value for this field from node.alpha
.
A getter
is a function that gets the value or values from the node. If a getter
is not defined and a name
is defined, node[name]
will be used in its place.
A simple getter might look like:
node => node.frontmatter.title
You could also pull the value from a config object or anywhere else you like.
A defaultValue
supplies a value in instances where no value exists on the node, or no means of getting a value has been defined on the descriptor. For example if only a name
was defined and there is no prop on the node with that name, if a getter
was defined but didn't return any value(s), or if neither a name
or a getter
were defined. If defaultValue
is a function it should return a default value. If the value of defaultValue
is not a function, that value will be used as the default value.
For example you could use an alternative node value if the current value is nil:
node => node.someOtherValue
A validator
is just a predicate that receives the value and returns true or false, depending if it deems it to be valid or not. For example we might have a descriptor that has looked up node.Front Matter.slug
, but there is no slug defined, we use a sanitised version of the title instead:
value => isValidDate(value)
A transformer
transforms the value in some way. For example it might run the value through a function that cleans it up or formats it. A transformer function will be called with three arguments: the value, the node and the context, if defined.
value => preventOrphans(value)
A setter defines how the value(s) are translated to fields. If no setter
is defined, the name field will be used to create a field of that name using Gatsby's createNodeField
, however using a setter
function allows more flexibility. For example a value might be an object and we might want to transfer its values to multiple fields. A setter
will receive three arguments: the value, createNodeField
and any context. If you define a setter, that setter is responsible for using createNodeField
to create fields.
(value, node) => {
createNodeField({
node,
name: 'alpha',
value: value.beta
})
}
To keep things as functional as possible and prevent the need for you to reach out to external data sources from within your functions, you can pass in a context
. Context can be anything you like, but will probably be an object. getter
, defaultValue
, validator
, transformer
, and setter
functions all receive the context as their second argument.
If you are using the plugin, pass the context as an option:
{
resolve: `gatsby-plugin-node-fields`,
options: {
descriptors: [
…
],
context: {
…
}
},
}
If you are using the function, pass it in as the fourth argument:
attachFields(node, createNodeField, descriptors, context)
Gatsby doesn't support ES6 imports, so we need to compile our ./src
to ./lib
, then reference the compiled file from gatsby-node.js
.
Tests are written with Jest:
yarn test