lil toast logo

Creating 'Site Settings' for a Sanity powered Gatsby site


Over the past couple of weeks I have been putting a lot of time in refining & expanding my first open-source project; Gatsby+Snipcart+Sanity. This project provides a Gatsby.js ecommerce starter powered by Snipcart & Sanity. One of the last minor features I've been wanting to add before I begin to use this starter in client projects is Site Settings. The concept here is to create a section of our Sanity Studio that contains fields pertaining to our Site generally; not related to any other content we have. Fields you may want to include are site name, address, hours of operation, etc. In this post we'll cover creating the Site Settings section in our Sanity Studio, creating any necessary fields, & lastly how we can use this data throughout our Gatsby site.

Before we start

By the time you're reading this, the code in the examples below will be merge into the main branch of the starter. If you want to follow along, simply confirm the follow things with your own project.

  1. your Gatsby site is using the gatsby-source-sanity plugin to connect to a Sanity project
  2. later on we'll be using Font Awesome to display links to different social media accounts, so make sure you have the gatsby font awesome plugin & run the following command in the root of your Gatsby project for later...
    npm i --save @fortawesome/fontawesome-svg-core @fortawesome/react-fontawesome @fortawesome/free-brands-svg-icons
    For more info on using Font Awesome with React, check out the docs

What is the Structure Builder?

Before we jump into any code, it's important to understand why the schema we're going to write is different than most other schemas in a Sanity project. The schema corresponding to our Site Settings is different from most other schemas in our project simply because we only want one document with this schema in our CMS. For this we need to reach for Sanity's Structure Builder API. The Structure Builder is a way for us to control the Structure of our Sanity Studio; there are a ton of really cool things you can accomplish with this API (learn more here), but for our purposes we are simply using this API to add a 'singleton' or one off document to our Sanity Studio. First let's create a file in the root of our Sanity project called deskStructure.js. In our sanity.json file, we now need to tell Sanity to include the structure tool, and to use the file we've just created to configure it. Your sanity.json should look likt this...

{
  ...
  "plugins": [
    ...,
        //this should be there by default, but double check!
    "@sanity/desk-tool"
  ],
  "parts": [
    ...,
    {
      "name": "part:@sanity/desk-tool/structure",
      "path": "./deskStructure.js"
    }
  ]
}

Now let's open up deskStructure.js and throw in this snippet...

import S from "@sanity/desk-tool/structure-builder";

export default () =>
//creating our list with a title & an items array
  S.list()
    .title('Content')
    .items([
      //creating a list item
      S.listItem()
        .title('Site settings')
        .child(
          //displaying out editor window using the siteSettings schema
          S.editor()
            .schemaType('siteSettings')
            .documentId('siteSettings')
        ),
      // add a visual divider (optional)
      S.divider(),
      // list out the rest of the document types,
            //but filter out the config type
      ...S.documentTypeListItems()
        .filter(listItem => !['siteSettings'].includes(listItem.getId()))
    ])

This is essentially a promise chain that will build our new item in the List section of the Sanity Studio. If you'd like to learn more about these built in functions, consult the docs. At this point, Sanity knows what we want to do with the Stucture Builder API, but we haven't created the schema we're referencing above. Let's do that next...

Data Modeling

Navigate into the schemas directory and create a file titled siteSettings.js (this is the schema we're referencing above in .schemaType() & .documentId()) as well as a file called social.js that we'll use later. Now we need to load these schema into Sanity from our schema.js file...

import siteSettings from './siteSettings'
import social from './social'

export default createSchema({
  name: 'default',
  types: schemaTypes.concat([
    ...,
    siteSettings,
        social
  ])
})

Our schemas are now loaded into Sanity and can be seen by the Structure Builder; hooray! The last step in setting up our Site Settings in Sanity is to write our schema. For my use case, I need a Site Name, Email Address, Phone Number, & an array of Social Media Links. We will also need to write social.js to describe the shape of each Social Media Link.

import social from "./social";

export default {
  title: 'Site Settings',
  name: 'siteSettings',
  type: 'object',
  fields: [
      {
          title: 'Site Name',
          name: 'siteName',
          type: 'string',
          validation: Rule => Rule.required()
      },
      {
          title: 'Email',
          name: 'email',
          type: 'string'
      },
      {
          title:'Phone',
          name: 'phone',
          type: 'string'
      },
      {
        title: 'Social Links',
        name: 'socialLinks',
        // creates an array of objects with 
                // the shape we described in social.js
              type: 'array',
        of: [{type: 'social'}]
      }
  ]
}
//social.js
export default {
  title: 'Social Link',
  name: 'social',
  type: 'document',
  fields: [
    {
      title: 'Name',
      name: 'name',
      type: 'string',
      validation: Rule => Rule.required()
    },
    {
      title: 'Link',
      name: 'link',
      type: 'string',
      validation: Rule => Rule.required()
    },
    {
      title: 'Icon',
      name: 'icon',
      type: 'string',
      description: 'go to this link to see what icon abreviation to use: https://fontawesome.com/icons?d=gallery&s=brands. You can only use icons from the "brands" collection of Font Awesome'
      validation: Rule => Rule.required()
    }
  ]
}

With these schemas created, we should now see a single tab in our Sanity Studio with the name Site Settings. Inside that tab should be text fields for Site Name, Email, & Phone, as well as an input to add social links to the project. The last step is to query this data where necessary throughout our Gatsby project. Displaying the Site Name, Email & Phone is a pretty trivial if you have any experience with Sanity, so I won't be covering those in this post. Rather, I'd like to show you how we can us the Font Awesome packages we installed earlier to create a great user experience for admins trying to add social icons to the frontend of our site.

Looking at social.js, we see each Social Link object has a Name, a Link & and a FA Icon name. We've added a descriptiton to the Icon input to show the Sanity Studio user that they should consult this page to identify which icon name to use for any given platform. As I indicated earlier, we've installed all icons from the 'fab' (or Font Awesome Brands) package, which should have any icon needed for this use case. Take this time to open up your Sanity Studio and add in some of your own social media accounts so we have some data to work with.

Now let's create our Social Icons component. Within src/components let's create a file called SocialLinks.js. First we'll import React along with all of our FA packages...

// SocialLinks.js
import React from 'react'
import { library } from '@fortawesome/fontawesome-svg-core'
import { fab } from '@fortawesome/free-brands-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'

We'll also need to bring in to functions from Gatsby; useStaticQuery & graphql. Because this query to our graphql api is not happening in a file within the Pages directory, we don't have access to our graphql api like we normally would in Gatsby. These two functions (as their names suggest) allow us to create a static query to our graphql api. We'll create a functional component & bring in styled components to style it. SocialLinks.js should look like this...

// SocialLinks.js
import React from 'react'
import { library } from '@fortawesome/fontawesome-svg-core'
import { fab } from '@fortawesome/free-brands-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { useStaticQuery, graphql } from 'gatsby'
import styled from 'styled-components'

export default function SocialLinks() {

    return()
}

First let's configure Font Awesome for use in our React component. If we look back at the FA docs regarding React, we can identify that there is a function provided by FA called library.add() that allows us to make imported icons accessible globally. In our case we want to make all icons in the fab package available. At this point we can also place the FontAwesomeIcon component in our return function...

// SocialLinks.js
import React from 'react'
import { library } from '@fortawesome/fontawesome-svg-core'
import { fab } from '@fortawesome/free-brands-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { useStaticQuery, graphql } from 'gatsby'
import styled from 'styled-components'

//adding all fab to global library
library.add(fab)

export default function SocialLinks() {

    return(
        // to see the format & props of this component,
        // visit the FA docs mentioned above
        <FontAwesomeIcon icon={['fab', elem.icon]} size='lg'/>
    )
}

At this point we need to create our Static Query to retrieve all of the social links. Like in any graphql client, we can rename our top level object by placing a new name followed by a colon before the name of the top level object (in this case 'links'). We can then destructure the object so that we have a constant variable named links that contains our data. Now we need to pull out the single array of objects that contain the only data we need. To accomplish this we'll create a variable called linksArr and set it equal to links.nodes[0].socialLinks. Because our site settings are a singleton document, we can hardcode in nodes[0] and be certain our data will always be located at that index. We're also quickly going to create an IconsWrap styled component to control the layout of our icons...

// SocialLinks.js
import React from 'react'
import { library } from '@fortawesome/fontawesome-svg-core'
import { fab } from '@fortawesome/free-brands-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { useStaticQuery, graphql } from 'gatsby'
import styled from 'styled-components'

const IconsWrap = styled.div`
  display: flex;
  align-items: center;
  & > * {
    margin: .25rem;
  }
  & > *:hover {
    opacity: 80%;
  }
`

library.add(fab)

export default function SocialLinks() {
    const { links } = useStaticQuery(graphql`
    query {
      links: allSanitySiteSettings {
        nodes {
          socialLinks {
            link
            icon
            _key
          }
        }
      }
    }
  `)

    // we select nodes[0] since our document is a singleton, 
    // therefore our data will always be at an index of 0
    const linksArr = links.nodes[0].socialLinks

    return(
        <IconsWrap>
            <FontAwesomeIcon icon={['fab', elem.icon]} size='lg'/>
        </IconsWrap>
    )
}

The last step here is to map over our linksArr and produce a <FontAwesomeIcon /> component for each object. Notice in our static query we asked for the key provided by Sanity; we'll use this as our key prop that is required for all children created from our map. We'll also wrap the FA component in an anchor tag to the icon links to the specified account...

// SocialLinks.js
import React from 'react'
import { library } from '@fortawesome/fontawesome-svg-core'
import { fab } from '@fortawesome/free-brands-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { useStaticQuery, graphql } from 'gatsby'
import styled from 'styled-components'

const IconsWrap = styled.div`
  display: flex;
  align-items: center;
  & > * {
    margin: .25rem;
  }
  & > *:hover {
    opacity: 80%;
  }
`

library.add(fab)

export default function SocialLinks() {
    const { links } = useStaticQuery(graphql`
    query {
      links: allSanitySiteSettings {
        nodes {
          socialLinks {
            link
            icon
            _key
          }
        }
      }
    }
  `)

    //we select nodes[0] since our document is a singleton, therefore our data will always be at an index of 0
    const linksArr = links.nodes[0].socialLinks

    return(
        <IconsWrap>
            {linksArr.map(elem => {
        return(
          <a href={elem.link} key={elem._key}>
            <FontAwesomeIcon icon={['fab', elem.icon]} size='lg'/>
          </a>
        )
      })}
        </IconsWrap>
    )
}

Now we can place our <SocialLinks /> component anywhere in our app! If you want to see a live demo of this, check out the demo site for the Gatsby+Snipcart+Sanity starter project.


Jacob Stordahl

Hi, I'm Jake. I'm a front end engineer & designer falling in love with javascript! You can follow me on Twitter, and see some of my work on GitHub.