I'd like to use optional images in frontmatter of markdown files processed with gatsby-plugin-mdx
. I have multiple markdown files which correspond to article pages which use the same template and same page query. Some of those articles have a hero image specified in frontmatter and some don't.
How can I tell GraphQL that the heroImage
frontmatter key can be an image but doesn't need to be?
Here is the current GraphQL behaviour when specifying a key in frontmatter if the image at ../assets/cms/hero-image.png
exists:
heroImage: ../assets/cms/hero-image.png
→ works fineheroImage: ''
→ Field "heroImage" must not have a selection since type "String" has no subfields# heroImage
→ Unknown field 'heroImage' on type 'MdxFrontmatter'.I'm a beginner to Gatsby and GraphQL but I think what I need is to make the heroImage
field in GraphQL of type File
instead of type File!
.
Here is a scenario on how it should work
---
# my-markdown-file.md
heroImageOne: ../assets/cms/hero-image.png
heroImageTwo: ''
# heroImageThree
---
// my-template.js
import React from 'react'
import { graphql } from 'gatsby'
export const pageQuery = graphql`
query MyMdxQuery($fileAbsolutePath: String!) {
mdx(fileAbsolutePath: { eq: $fileAbsolutePath }) {
frontmatter {
heroImageOne {
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
heroImageTwo {
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
heroImageThree {
childImageSharp {
fluid {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`
export default function MyTemplateComponent({ data }) {
const {
heroImageOne, // ← { src, srcSet, … }
heroImageTwo, // ← null
heroImageThree // ← null
} = data.mdx.frontmatter
return <JSX />
}
Note that I'm using the hero image as an example but I'm also generally interested in how to create optional frontmatter fields.
The markdown files will be generated by netlifycms and the content editor should be able to use non-required fields. That would also enable adding frontmatter fields to the template without having to add the necessary key to every markdown file.
System:
OS: macOS 10.14.6
CPU: (8) x64 Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz
Shell: 5.3 - /bin/zsh
Binaries:
Node: 10.16.3 - /var/folders/hl/cb4r8j7j0l79tbq9tzxntggr0000gn/T/yarn--1570473024192-0.3318895832156219/node
Yarn: 1.16.0 - /var/folders/hl/cb4r8j7j0l79tbq9tzxntggr0000gn/T/yarn--1570473024192-0.3318895832156219/yarn
npm: 6.9.0 - /usr/local/bin/npm
Languages:
Python: 2.7.10 - /usr/bin/python
Browsers:
Chrome: 77.0.3865.90
Firefox: 69.0.2
Safari: 13.0.1
gatsby-config.js
:
The project uses TypeScript but I use JavaScript examples in this issue to make it less complicated.
module.exports = {
siteMetadata: {
title: 'x',
description: 'x',
author: 'x',
},
plugins: [
'gatsby-plugin-catch-links',
// FIXME: Conflicts with gatsby-transformer-sharp in GitHub Actions and leads to image fragments for graphql not being copied into cache directory.
// {
// resolve: 'gatsby-plugin-generate-typings',
// options: {
// dest: 'src/generated/graphql-types.d.ts',
// },
// },
{
resolve: 'gatsby-plugin-manifest',
options: {
name: 'x',
short_name: 'x',
start_url: '/',
background_color: '#fff',
theme_color: '#fff',
display: 'standalone',
icon: 'src/assets/logo.svg', // This path is relative to the root of the site.
},
},
{
resolve: 'gatsby-plugin-mdx',
options: {
extensions: ['.md', '.mdx'],
gatsbyRemarkPlugins: [
{ resolve: 'gatsby-remark-copy-linked-files' },
// gatsby-remark-unwrap-images needs to be in front of gatsby-remark-images in order to work
{ resolve: 'gatsby-remark-unwrap-images' },
{
resolve: 'gatsby-remark-images',
options: {
maxWidth: 2880,
showCaptions: ['title'],
// Markdown captions do not work in mdx yet. More info: https://github.com/gatsbyjs/gatsby/pull/16574#issue-306869033
markdownCaptions: true,
linkImagesToOriginal: false,
backgroundColor: 'transparent',
quality: 75,
tracedSVG: true,
},
},
],
remarkPlugins: [require('remark-slug')],
// Necessary subplugin to fix bug with placeholder image not disappearing after final image is loaded. More info: https://github.com/gatsbyjs/gatsby/issues/15486
plugins: ['gatsby-remark-images'],
},
},
// this (optional) plugin enables Progressive Web App + Offline functionality. To learn more, visit: https://gatsby.dev/offline
// 'gatsby-plugin-offline',
'gatsby-plugin-react-helmet',
'gatsby-plugin-scss-typescript',
'gatsby-plugin-sharp',
'gatsby-plugin-typescript',
'gatsby-plugin-typescript-checker',
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'assets',
path: `${__dirname}/src/assets`,
},
},
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'pages',
path: `${__dirname}/src/pages`,
ignore: ['**/.*'],
},
},
'gatsby-transformer-sharp',
],
}
package.json
:
Used as yarn workspace.
{
"name": "x",
"version": "1.0.0",
"description": "x",
"author": "x",
"private": true,
"scripts": {
"build": "gatsby build",
"build:ci": "yarn lint:ci && yarn build",
"develop": "gatsby develop",
"format": "prettier --write \"**/*.{js,jsx,json,ts,tsx}\"",
"lint": "eslint --ext .ts,.tsx,.js,.jsx .",
"lint:ci": "yarn lint --max-warnings 0",
"serve": "gatsby serve",
"start": "yarn develop",
"test": "echo \"Write tests! -> https://gatsby.dev/unit-testing \"",
"type-check": "tsc --noEmit",
"type-check:watch": "yarn type-check --watch"
},
"dependencies": {
"@mdx-js/mdx": "^1.4.4",
"@mdx-js/react": "^1.4.4",
"@types/body-scroll-lock": "^2.6.1",
"@types/classnames": "^2.2.9",
"@types/react": "^16.9.2",
"@types/react-dom": "^16.9.0",
"@types/react-helmet": "^5.0.9",
"body-scroll-lock": "^2.6.4",
"classnames": "^2.2.6",
"gatsby": "^2.15.9",
"gatsby-image": "^2.2.17",
"gatsby-plugin-catch-links": "^2.1.8",
"gatsby-plugin-generate-typings": "^0.9.8-r1",
"gatsby-plugin-manifest": "^2.2.14",
"gatsby-plugin-mdx": "^1.0.39",
"gatsby-plugin-offline": "^2.2.10",
"gatsby-plugin-react-helmet": "^3.1.6",
"gatsby-plugin-scss-typescript": "^4.0.8",
"gatsby-plugin-sharp": "^2.2.20",
"gatsby-plugin-typescript": "^2.1.6",
"gatsby-plugin-typescript-checker": "^1.1.1",
"gatsby-remark-copy-linked-files": "^2.1.17",
"gatsby-remark-images": "^3.1.21",
"gatsby-remark-unwrap-images": "^1.0.1",
"gatsby-source-filesystem": "^2.1.21",
"gatsby-transformer-sharp": "^2.2.13",
"node-sass": "^4.12.0",
"normalize.css": "^8.0.1",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"react-helmet": "^5.2.1",
"remark-slug": "^5.1.2",
"tsconfig-paths-webpack-plugin": "^3.2.0"
},
"devDependencies": {
"typescript": "^3.6.2"
}
}
gatsby-node.js
:
const path = require('path')
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin')
exports.onCreateWebpackConfig = ({ actions }) => {
// Makes absolute paths defined in tsconfig available as aliases in webpack, enabling absolute imports.
actions.setWebpackConfig({
resolve: {
plugins: [new TsconfigPathsPlugin()],
},
})
}
exports.onCreatePage = ({ page, actions }) => {
if (path.extname(page.component) === '.md' && page.context.frontmatter) {
const { createPage, deletePage } = actions
const { template } = page.context.frontmatter
deletePage(page)
createPage({
...page,
component: path.resolve(`./src/templates/${template}.tsx`),
context: {
...page.context,
fileAbsolutePath: page.component,
},
})
}
}
gatsby-browser.js
: N/A
gatsby-ssr.js
: N/A
Thanks for helping me out! 😊 If I'm missing something obvious in the documentation I apologise and would be happy if you just point me into the right direction.
Hiya!
This issue has gone quiet. Spooky quiet. 👻
We get a lot of issues, so we currently close issues after 30 days of inactivity. It’s been at least 20 days since the last update here.
If we missed this issue or if you want to keep it open, please reply here. You can also add the label "not stale" to keep this issue open!
As a friendly reminder: the best way to see this issue, or any other, fixed is to open a Pull Request. Check out gatsby.dev/contribute for more information about opening PRs, triaging issues, and contributing!
Thanks for being a part of the Gatsby community! 💪💜
I'm having the same issue. I have a list GraphQL type defined with
type FlexibleListEntry @infer {
name: String!
text: String
href: String
}
type FlexibleList {
items: [FlexibleListEntry!]
title: String
}
type Frontmatter @infer {
templateKey: String
title: String
href: String
tags: [String!]
seo: SEO
people: FlexibleList
services: FlexibleList
}
Every entry in the list may contain an image. Unfortunately when some of the entries don't have an image field, I get the error Field "image" must not have a selection since type "String" has no subfields
. The weird thing is that this is pretty non-deterministic/intermittent. I'm testing it right now, and it failed the first time and it is working after removing the .cache and public folders. If I try to add the image field to the FlexibleListEntry with
type FlexibleListEntry @infer {
name: String!
text: String
href: String
image: File
}
then the build fails with Cannot return null for non-nullable field File.id
, which is strange because the query it is complaining about is not null, and so should have an id. This occurs even when every FlexibleListEntry has a valid, non-null path for the image
entry.
I found a solution myself, I'm posting it here in case anyone else searches for an answer.
In order to make the scenario above working, following code is needed in gatgsby-node.js
:
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions
createTypes(`
type Mdx implements Node {
frontmatter: MdxFrontmatter!
}
type MdxFrontmatter {
heroImageOne: File @fileByRelativePath
heroImageTwo: File @fileByRelativePath
heroImageThree: File @fileByRelativePath
}
`)
}
We have to start at a type which implements a built-in type like Node
because the createSchemaCustomization
hook runs before third party plugins implement their types. Our type definitions here will be merged with the third party types.
@WhiteAbeLincoln Sorry, I missed your comment. Did you try appending the directive @fileByRelativePath
?
type FlexibleListEntry @infer {
name: String!
text: String
href: String
image: File @fileByRelativePath
}
It tells GraphQL that this is a string containing a relative path to a file.
I found a solution myself, I'm posting it here in case anyone else searches for an answer.
In order to make the scenario above working, following code is needed in
gatgsby-node.js
:exports.createSchemaCustomization = ({ actions }) => { const { createTypes } = actions createTypes(` type Mdx implements Node { frontmatter: MdxFrontmatter! } type MdxFrontmatter { heroImageOne: File @fileByRelativePath heroImageTwo: File @fileByRelativePath heroImageThree: File @fileByRelativePath } `) }
We have to start at a type which implements a built-in type like
Node
because thecreateSchemaCustomization
hook runs before third party plugins implement their types. Our type definitions here will be merged with the third party types.
@dcastil just wanna say you saved my bacon! thanks!
would you or anyone here know how to find these type definitions that are defined by gatsby?this seems to be the only answer to something that Gatsby would have documentation to.
@panzerstadt-dev You can create a Gatsby project without any plugins and check out the GraphiQL explorer. There should only exist the “native” Gatsby types.
It would be nice if this documentation was updated to reflect that you need to customize the schema.
@Idmontie That’s a good idea! Maybe you can create a PR or an issue and someone might pick it up.
Most helpful comment
I found a solution myself, I'm posting it here in case anyone else searches for an answer.
In order to make the scenario above working, following code is needed in
gatgsby-node.js
:We have to start at a type which implements a built-in type like
Node
because thecreateSchemaCustomization
hook runs before third party plugins implement their types. Our type definitions here will be merged with the third party types.