In the previous parts you built the foundation of your source plugin, from now on it’s about adding new functionalities, improving the user experience, and making the plugin better overall.
This part of the tutorial will focus on a subset of the Node API helpers. Feel free to revisit this document as it can also work as standalone instructions for those APIs.
By the end of this part of the tutorial, you will be able to:
reporter
API to output structured terminal messagescache
API to save artifacts between runsreporter.setErrorMap
pluginOptionsSchema
API to verify your plugin’s optionsreporter
API
You, as a source plugin author, can interact with your users in a variety of ways. You can share information in the README, chat in issues & PRs, create guides, etc. But no matter how active of a maintainer you are, it is your source plugin that will interact the most with your users through the terminal when they run gatsby develop
or gatsby build
. Gatsby outputs what it does behind the scenes with the goal of giving the users rich information that helps them be more productive with Gatsby itself.
For this, Gatsby internally uses the reporter
API and you can use it, too! When adding logs to the terminal be mindful of keeping of them concise, informative, and to a minimum. Most Gatsby plugins don’t even need to add logs (other than errors), so in doubt leave them out at the beginning and reevaluate once you have a bigger user base.
If you need information about an API, always be sure to check Gatsby’s API docs, in this case the reporter API docs. The reporter
API is available in all Node APIs and its methods can be used like this:
Here’s a short explanation on the most important methods:
info
: Print a message to the terminal.warn
: Print a warning message to the terminal.error
: Print an error message to the terminal.panic
: Print an error message to the terminal and immediately exit the process.panicOnBuild
: Print an error message to the terminal and immediately exit the process (only during gatsby build
). Most often you should use this over panic
as it’ll allow users to debug the error during gatsby develop
.verbose
: Print a message to the terminal that is only visible when the “verbose” flag is enabled (e.g. gatsby build --verbose
).activityTimer
: Print an informational message to the terminal that has a timer attached to it (e.g. how long that step took).setErrorMap
: Set a custom error map to the reporter. This allows the reporter to extend Gatsby’s internal error map.You’ll learn how to use panicOnBuild
, activityTimer
, and setErrorMap
in the next three tasks as they are the most relevant and common for plugins.
As source plugins interact with third-party APIs they are inherently prone to errors (network issues, API outages, etc.). As explained in Part 2 it’s good practice to gracefully handle all possible error states, as there’s nothing worse than no actionable feedback at all.
In Part 2 you also added the fetchGraphQL
function that accesses the GraphQL API inside the api
folder. When you send a malformed request to it, it can not only give back data
but also errors
. Use that information to output it with panicOnBuild
.
Go to your plugin/src/source-nodes.ts
file. Destructure reporter
from gatsbyApi
at the top of sourceNodes
. Add errors
to the destructuring statement for the fetchGraphQL
response. Also add a conditional statement when errors
is truthy directly below fetchGraphQL
:
Inside the errors
conditional, use panicOnBuild
to output the error message that you’re receiving from your API. error
, panicOnBuild
, and panic
accept a string
, Error
or a structured error (you’ll learn about the latter in Task: Define custom errors). Also add an early return to not continue creating nodes.
Pro tip: Try to rely on TypeScript types to check if the error you’re getting back is a valid Error
type. If not, you might need to modify the error a little bit to fit the requirements.
To see if it works, add a typo to your GraphQL request inside fetchGraphQL
:
Restart the develop:site
script and you should see an error in the terminal:
Yes, it works 🎉 Don’t forget to remove the typo again. In Task: Define custom errors you’ll learn how to further customize this error.
Pro tip: This is the error that the GraphQL API directly returns. You can see the same error if you go to http://localhost:4000/graphql
(the GraphQL API from api
folder) and try the query there. Get yourself familiar with how, when, and in which form your API throws errors.
You’re now surfacing any errors that occur during the data fetching to your users. Use the error
, panicOnBuild
, and panic
methods throughout your code to display all relevant errors to your user.
Please note: If necessary, add context to your error messages instead of only passing the error along. This is especially true if the error could be thrown in multiple places or if the original error is not clear enough.
Task: Show activities and timingsSince your source plugin is retrieving information from a third-party API, that process could take a while, especially if it’s fetching a lot of data. In these instances you can use the activityTimer
method to let the user know that something is happening in the background. You shouldn’t add separate activityTimer
instances (remember: Keep the output concise and to a minimum) but instead use the setStatus
method on activityTimer
to update its text.
Now you’ll learn exactly that, by adding an activity for the sourcing and node creation process of your plugin.
Create a new activityTimer
and store that in a variable (the name doesn’t matter). You’ll use the variable to start, update, and stop the timer. Try to use a text that clearly indicates your plugin name and what it’s doing.
Since the sourcingTimer
was initialized before the fetchGraphQL
call you can also start()
it before that call:
With the start()
call the timer is running until you’ll eventually call end()
.
If the sourcing fails, the timer should be stopped. So update the panicOnBuild
call to use the sourcingTimer
instead:
Please note: When looking at the TypeScript types for sourcingTimer
you’ll notice that it also implements panicOnBuild
and panic
. You can pass in the same arguments as you’re used to, the activityTimer
will handle everything for you. If you had continued to use reporter.panicOnBuild
the terminal would output a successful message for the timer which is incorrect of course!
Right now the sourcingTimer
shows “Sourcing from plugin API” together with a time in seconds. You can use the setStatus()
method to append text.
Pro tip: Adding the information of how many types were created is a really nice debugging help since it’ll allow folks to quickly compare builds and see if e.g. all the information they expect is sourced.
Stop the timer once node creation is done:
Restart the develop:site
script and you should read a new activity in the terminal:
That’s what we’d expect, nice!
The error you’ve output to the terminal in Task: Output errors was functional but missing some crucial information. Where is the error coming from? Which part of the plugin? Any suggestions for immediate fixes?
You can use the setErrorMap
method to create an error map for your plugin. Gatsby internally uses an error map to benefit from two things (among other additional benefits):
Both benefits also apply to you when you define a custom error map for your plugin. You’ll see the improvement at the end of this task. As a reminder, this is how an error (for the data fetching step) currently looks like:
Let’s get to work:
Open the plugin/src/constants.ts
file and add an ERROR_CODES
map with its first key GraphQLSourcing
:
The keys of the ERROR_CODES
object can be arbitrary, we’d recommend using names that make it easy to differentiate them for their different purposes. The value of each entry is the unique ID. So make sure that each ID only exists once inside ERROR_CODES
.
You don’t need to worry about other plugins or Gatsby itself as the IDs are namespaced with your plugin. If possible, use IDs that are not already used by Gatsby itself.
Import ERROR_CODES
into plugin/src/on-plugin-init.ts
and initialize setErrorMap
. Remove the existing info
call:
You can populate the error map like so:
text
: Your text that will be shown to the user for this specific errorlevel
: Set this to ERROR
category
: Set this to THIRD_PARTY
Add the following to plugin/src/on-plugin-init.ts
:
text
has to be a function that returns a string
and receives a freeform context
. You can pass through any information you’d like in context
and then use it to construct the final message that is shown to the user. So the error above requires sourceMessage
and graphqlError
to work.
Pro tip: If your text
should be a multi-line string, consider using the stripIndent
function from common-tags. This way your error will be displayed correctly independent from how you write the string.
In this step you’ll invoke the error with the custom ID and pass along the required context
information. Open plugin/src/source-nodes.ts
and add the ERROR_CODES
import and use it inside the existing panicOnBuild
call:
As in step 3 of Task: Output errors, add a typo to the GraphQL error again (e.g. title2
), restart the develop:site
script and you should see a new output for the error:
Your error now has a custom ID (#plugin_10000
), it prints the name of the plugin (PLUGIN
), and you’ve added additional context to the error itself (Sourcing from the GraphQL API failed:
). Whenever you need to output an error message, create a new structured error inside setErrorMap
and use it to display the best possible error to the user.
cache
API
You might already know that Gatsby creates two folders during its build phase: .cache
and public
. It’s recommended to keep both folders around in between builds as then Gatsby can leverage its caching functionalities the best.
With the cache API you have access to a key-value store to persist data inside the .cache
folder. You can use it to persist timestamps, tokens, etc. to use delta updates, or to store the artifacts of time/memory/cpu intensive tasks (e.g. images, big files).
If the term “delta update” doesn’t ring a bell, here’s a short explanation in context of a source plugin: A delta update requires the source plugin to only fetch data from the remote API that is new, or has been changed from a previous state, in contrast to having to fetch all the data again.
Example usage: Your API endpoint accepts an optional query parameter of lastFetched
. If used, only updates since that date are returned. When storing the timestamp of last sourcing with the cache
API you then can use that date in your request, e.g. http://yourapi.com/endpoint?lastFetched=2023-01-1
.
Important: Your remote API has to support delta updates, it’s not a universal thing that works everywhere.
Task: Save the timestamp of last sourcingThe most common use case of the cache
API is to store timestamps, tokens, etc. and therefore you’ll learn exactly that. Even though the example API this tutorial uses doesn’t support delta updates, you’ll go through all the necessary steps in this task to apply it to your own source plugin.
The cache
API is a key-value store, so you save specific data under a key, and then can retrieve a value with a given key.
Store the name of the key in an constants to minimize the likelyhood of typos. Open plugin/src/constants.ts
and add:
Import the new CACHE_KEYS
variable into plugin/src/source-nodes.ts
and grab the cache
API from gatsbyApi
inside sourceNodes
:
Generate a date timestamp just before the fetchGraphQL
call and use cache.set
to save the timestamp after the successful fetch. This way you’re ensuring accurate timestamps that are only saved when a successful data call happened. The key you use for cache.set
has to be unique.
Date.now()
returns the number of milliseconds elapsed since the epoch. If you need another format, read the Date.now() documentation.
It’s also important to know that cache
is shared by all instances of your plugin. So if your user can add your plugin multiple times to gatsby-config
(e.g. by setting a different workspaceId
in the plugin options), you need to ensure that those cache.set
calls use unique keys. In those cases, use a unique instance identifier, for example said plugin options.
The next step is to use cache.get
to retrieve the timestamp from the cache before the fetchGraphQL
call. Since you won’t use it for delta fetching in this tutorial, output the information in the verbose
mode of Gatsby with reporter.verbose
:
Time to test your changes! Restart the develop:site
script but this time with the --verbose
flag enabled:
The output got a lot busier! You should see a new log:
You’re seeing this because on this first run the lastFetchedDate
didn’t exist yet and thus undefined
is returned from the cache.get
call. Stop the development server and restart the script. You now should see a timestamp defined that looks something like this:
Great, it’s working!
Pro tip: If you save files with the cache
API you can also inspect what is saved by going to the .cache/caches
folder and looking for a folder named like your plugin. For this tutorial the path would be site/.cache/caches/plugin
.
pluginOptionsSchema
API
In most cases a Gatsby source plugin needs one or more options as it interacts with third-party APIs that require customization and authentication (e.g. different endpoints or API keys). You don’t want other users to use your credentials, so you’ll need a way for your users to provide theirs. That’s where plugin options come in!
The Configuring Plugin Usage with Plugin Options guide goes into depth about plugin options and the pluginOptionsSchema
API so if you want to know more beyond this guide, be sure to give it a read. But to give a short summary:
A Gatsby plugin with options included makes those options available in the second argument of Gatsby Node, Browser, and SSR APIs.
A sample gatsby-config
file:
Usage of that option in a Node API:
A plugin can optionally define a schema (through the pluginOptionsSchema
API) to enforce a type for each option. Gatsby will validate that the options users pass match the schema to help them correctly set up their site.
Here are some tips when working with plugin options:
noWarnings: true
, use warnings: false
. The double negation that would occur with noWarnings: false
is really confusing.options
object (e.g. options.sdkOptions
) and then refer people to the SDK’s documentation.endpoint
option
Instead of hardcoding the URL to the GraphQL API, you’ll use an endpoint
option and thus make the URL configurable.
The boilerplate you cloned at the beginning already has an endpoint
option defined in the site’s gatsby-config
. Go to site/gatsby-config.ts
to inspect the code:
So the plugin already receives the GraphQL endpoint through the endpoint
option.
The IPluginOptionsKeys
TypeScript type inside plugin/src/types.ts
currently has a generic [key: string]: any
catch-all. Replace it with your endpoint
option so that both internally and externally the types are correct:
Open the file plugin/src/source-nodes.ts
and validate that you can access the option. You can do this by accessing the second parameter of the sourceNodes
function and console.log
it. Use the IPluginOptionsInternal
type for it:
Restart the develop:site
script and you should see an output in the terminal like this:
Don’t worry about the plugins
key there. Since Gatsby’s plugins can also have sub-plugins, the key plugins
is added by default to the pluginOptions
object. What you really should care about is that the endpoint
option successfully comes through.
Destructure endpoint
from the pluginOptions
object and use it in the fetchGraphQL
call:
Once again, restart the develop:site
script and you should see the log Sourcing from plugin API - 0.033s - Processing 3 posts and 2 authors
to indicate that the sourcing was successful.
You’ve successfully used a plugin option in your source plugin’s source code.
Task: Verifyendpoint
option
Right now a user could use any serializable value for endpoint
like numbers, objects, strings, or booleans. But those are not valid options for endpoint
, it should be a string, more specifically a valid URI. Use the pluginOptionsSchema
API to validate exactly that.
Create a new file called plugin-options-schema.ts
inside the plugin’s src
folder with the following contents:
pluginOptionsSchema
uses Joi to define a validation schema. Use its API documentation to discover all available options.
Define endpoint
in the schema. It should be a string (a valid URI), should be required, and have a short description. You can write something like this:
Export the pluginOptionsSchema
API in the plugin’s gatsby-node
file so that it is run:
You can test that it works correctly by opening up the site’s gatsby-config
and changing the option to an invalid value, e.g. entering a number:
Restart the develop:site
script. The development server should crash and show an error:
It’s working correctly, great! Go back to site/gatsby-config.ts
and use the correct endpoint
option again.
Joi as a validation library is really powerful and as shown in Configuring Plugin Usage with Plugin Options you can define a really fine-grained validation schema.
SummaryAwesome! You’ve learned a lot about plugin options.
Take a moment to think back on what you’ve learned so far. Challenge yourself to answer the following questions from memory:
reporter
API and what methods does it have?panicOnBuild
and panic
?setErrorMap
?cache
API and for which purposes is it useful?reporter
API and its methods to output information (warnings, errors, logs, timers, etc.) to the terminal during gatsby develop
and gatsby build
. You can also stop the entire Gatsby process on errors if necessary.setErrorMap
API to define your own, custom errors to show richer information to your users.cache
API gives you access to a key-value store to save data between runs.pluginOptionsSchema
API to validate the input of your plugin options.Share Your Feedback!
Our goal is for this tutorial to be helpful and easy to follow. We’d love to hear your feedback about what you liked or didn’t like about this part of the tutorial.
Use the “Was this doc helpful to you?” form at the bottom of this page to let us know what worked well and what we can improve.
What’s coming next?In Part 5 you’ll learn all about Incremental Builds and how to best build your API and plugin for it.
Continue to Part 5Start building today on
Netlify!
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4