If several languages coalesce, the grammar of the resulting language is\n more simple and regular than that of the individual languages.
\n
\n
The new common language will be more simple and regular than the existing\n European languages. It will be as simple as Occidental; in fact, it will be\n
If several languages coalesce, the grammar of the resulting language is\n more simple and regular than that of the individual languages.
\n
\n
The new common language will be more simple and regular than the existing\n European languages. It will be as simple as Occidental; in fact, it will be\n
To achieve this, it would be necessary to have uniform grammar,\n pronunciation and more common words.
\n
\n
If several languages coalesce, the grammar of the resulting language is\n more simple and regular than that of the individual languages.
\n
\n
The new common language will be more simple and regular than the existing\n European languages. It will be as simple as Occidental; in fact, it will be\n
\n \n
"
},
"rich-text: Embedded Entry": {
- "1": "
\n
Rich Text: Embedded Entry
\n
Embedded Entry
\n
[ContentfulText]\n The quick brown fox jumps over the lazy dog.
\n \n \n \n
"
+ "1": "
\n
Rich Text: Embedded Entry
\n
Embedded Entry
\n
[ContentfulContentTypeText]\n The quick brown fox jumps over the lazy dog.
\n \n \n \n
"
},
"rich-text: Embedded Asset": {
"1": "
\n
Rich Text: Embedded asset
\n
Embedded Asset
\n
\n
\n
\n \n \n \n
\n \n \n \n \n
"
},
"rich-text: Embedded Entry With Deep Reference Loop": {
- "1": "
\n
Rich Text: Embedded entry with deep reference loop
\n
Embedded entry with deep reference loop
\n
[ContentfulReference]\n Content Reference: Many (2nd level loop)\n : [\n Number: Integer, Text: Short, Content Reference: One (Loop A ->\n B)\n ]
\n \n \n \n
"
+ "1": "
\n
Rich Text: Embedded entry with deep reference loop
\n
Embedded entry with deep reference loop
\n
[ContentfulContentTypeContentReference]\n Content Reference: Many (2nd level loop)\n : [\n Number: Integer, Text: Short, Content Reference: One (Loop A ->\n B)\n ]
[ContentfulText]\n The European languages are members of the same family. Their\n separate existence is a myth. For science, music, sport, etc, Europe uses\n the same vocabulary.\n\n The languages only differ in their grammar, their pronunciation and their\n most common words. Everyone realizes why a new common language would be\n desirable: one could refuse to pay expensive translators.\n\n To achieve this, it would be necessary to have uniform grammar,\n pronunciation and more common words. If several languages coalesce, the\n grammar of the resulting language is more simple and regular than that of\n the individual languages. The new common language will be more simple and\n regular than the existing European languages. It will be as simple as\n Occidental; in fact, it will be.
\n
"
+ "1": "
\n
Content Reference: Many Localized
\n
[ContentfulContentTypeNumber]\n 4.2
\n
[ContentfulContentTypeText]\n The European languages are members of the same family. Their\n separate existence is a myth. For science, music, sport, etc, Europe uses\n the same vocabulary.\n\n The languages only differ in their grammar, their pronunciation and their\n most common words. Everyone realizes why a new common language would be\n desirable: one could refuse to pay expensive translators.\n\n To achieve this, it would be necessary to have uniform grammar,\n pronunciation and more common words. If several languages coalesce, the\n grammar of the resulting language is more simple and regular than that of\n the individual languages. The new common language will be more simple and\n regular than the existing European languages. It will be as simple as\n Occidental; in fact, it will be.
\n
"
}
},
"media-reference localized": {
"media-reference: many with localized field": {
- "1": "
\n
Media Reference: Many Localized Field
\n
",
- "2": "
\n
Media Reference: Many Localized Field
\n
"
+ "1": "
\n
Media Reference: Many Localized Field
\n
",
+ "2": "
\n
Media Reference: Many Localized Field
\n
"
},
"media-reference: many with localized asset": {
- "1": "
\n
Media Reference: Many With Localized Asset
\n
",
- "2": "
\n
Media Reference: Many With Localized Asset
\n
"
+ "1": "
\n
Media Reference: Many With Localized Asset
\n
",
+ "2": "
\n
Media Reference: Many With Localized Asset
\n
"
},
"media-reference: one with localized asset": {
- "1": "
\n
Media Reference: One Localized Asset
\n
",
- "2": "
\n
Media Reference: One Localized Asset
\n
"
+ "1": "
\n
Media Reference: One Localized Asset
\n
",
+ "2": "
\n
Media Reference: One Localized Asset
\n
"
},
"media-reference: one with localized field": {
- "1": "
))}
@@ -70,10 +70,10 @@ ProductTemplate.propTypes = propTypes
export default ProductTemplate
export const pageQuery = graphql`
- query($id: String!) {
- contentfulProduct(id: { eq: $id }) {
+ query ($id: String!) {
+ contentfulContentTypeProduct(id: { eq: $id }) {
productName {
- productName
+ raw
}
productDescription {
childMarkdownRemark {
@@ -86,14 +86,14 @@ export const pageQuery = graphql`
}
brand {
companyName {
- companyName
+ raw
}
}
categories {
id
- gatsbyPath(filePath: "/categories/{ContentfulCategory.id}")
+ gatsbyPath(filePath: "/categories/{ContentfulContentTypeCategory.id}")
title {
- title
+ raw
}
}
}
diff --git a/packages/gatsby-codemods/package.json b/packages/gatsby-codemods/package.json
index 0d2ae76a4fc3a..47d7dfeeac88f 100644
--- a/packages/gatsby-codemods/package.json
+++ b/packages/gatsby-codemods/package.json
@@ -32,6 +32,7 @@
"execa": "^5.1.1",
"graphql": "^16.6.0",
"jscodeshift": "0.13.1",
+ "lodash": "^4.17.21",
"recast": "0.20.5"
},
"devDependencies": {
diff --git a/packages/gatsby-codemods/src/bin/__tests__/gatsby-codemods-test.js b/packages/gatsby-codemods/src/bin/__tests__/gatsby-codemods-test.js
index f65ad483dd485..6cf8c28ee3980 100644
--- a/packages/gatsby-codemods/src/bin/__tests__/gatsby-codemods-test.js
+++ b/packages/gatsby-codemods/src/bin/__tests__/gatsby-codemods-test.js
@@ -66,7 +66,7 @@ describe("transform", () => {
run()
expect(console.log).toBeCalledWith(
- `You have passed in invalid codemod name: does-not-exist. Please pass in one of the following: gatsby-plugin-image, global-graphql-calls, import-link, navigate-calls, rename-bound-action-creators, sort-and-aggr-graphql.`
+ `You have passed in invalid codemod name: does-not-exist. Please pass in one of the following: gatsby-source-contentful, gatsby-plugin-image, global-graphql-calls, import-link, navigate-calls, rename-bound-action-creators, sort-and-aggr-graphql.`
)
})
})
diff --git a/packages/gatsby-codemods/src/bin/cli.js b/packages/gatsby-codemods/src/bin/cli.js
index 589762d6a1298..9c3b7e061d7f6 100644
--- a/packages/gatsby-codemods/src/bin/cli.js
+++ b/packages/gatsby-codemods/src/bin/cli.js
@@ -2,6 +2,7 @@ import path from "path"
import execa from "execa"
const codemods = [
+ `gatsby-source-contentful`,
`gatsby-plugin-image`,
`global-graphql-calls`,
`import-link`,
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/content-types-typescript.input.ts b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/content-types-typescript.input.ts
new file mode 100644
index 0000000000000..d06d64244e704
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/content-types-typescript.input.ts
@@ -0,0 +1,3 @@
+interface Data {
+ allContentfulTemplatePage: FooConnection
+}
\ No newline at end of file
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/content-types-typescript.output.ts b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/content-types-typescript.output.ts
new file mode 100644
index 0000000000000..cf15eeef36404
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/content-types-typescript.output.ts
@@ -0,0 +1,3 @@
+interface Data {
+ allContentfulContentTypeTemplatePage: FooConnection
+}
\ No newline at end of file
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/content-types.input.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/content-types.input.js
new file mode 100644
index 0000000000000..c4e29d055ab67
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/content-types.input.js
@@ -0,0 +1,10 @@
+const demo = [
+ ...data.allContentfulFoo.nodes,
+ ...data.allContentfulBar.nodes,
+]
+const content = data.contentfulPage.content
+const {
+ data: {
+ allContentfulTemplatePage: { nodes: templatePages },
+ },
+} = await graphql(``)
\ No newline at end of file
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/content-types.output.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/content-types.output.js
new file mode 100644
index 0000000000000..97d0afc1e4848
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/content-types.output.js
@@ -0,0 +1,10 @@
+const demo = [
+ ...data.allContentfulContentTypeFoo.nodes,
+ ...data.allContentfulContentTypeBar.nodes,
+]
+const content = data.contentfulContentTypePage.content
+const {
+ data: {
+ allContentfulContentTypeTemplatePage: { nodes: templatePages },
+ },
+} = await graphql(``)
\ No newline at end of file
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/contentful-asset.input.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/contentful-asset.input.js
new file mode 100644
index 0000000000000..34b8bac2c5301
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/contentful-asset.input.js
@@ -0,0 +1,7 @@
+import React from "react"
+
+export default () => (
+
+)
\ No newline at end of file
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/contentful-asset.output.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/contentful-asset.output.js
new file mode 100644
index 0000000000000..330c083d93f19
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/contentful-asset.output.js
@@ -0,0 +1,7 @@
+import React from "react"
+
+export default () => (
+
+)
\ No newline at end of file
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/contentful-sys.input.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/contentful-sys.input.js
new file mode 100644
index 0000000000000..e6e4e4696953a
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/contentful-sys.input.js
@@ -0,0 +1,11 @@
+const res1 = allContentfulPage.nodes.contentful_id
+const res2 = allContentfulPage.nodes.sys.contentType.__typename
+const { contentful_id, createdAt, updatedAt } = allContentfulPage.nodes
+const { title, metaDescription, metaImage, content } = data.contentfulPage
+const { foo } = result.data.allContentfulPage.nodes[0]
+const {
+ revision,
+ sys: {
+ contentType: { __typename },
+ },
+} = allContentfulPage.nodes
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/contentful-sys.output.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/contentful-sys.output.js
new file mode 100644
index 0000000000000..3162c4b43f8b1
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/contentful-sys.output.js
@@ -0,0 +1,17 @@
+const res1 = allContentfulPage.nodes.sys.id
+const res2 = allContentfulPage.nodes.sys.contentType.name
+const {
+ sys: {
+ id: contentful_id,
+ firstPublishedAt: createdAt,
+ publishedAt: updatedAt
+ }
+} = allContentfulPage.nodes
+const { title, metaDescription, metaImage, content } = data.contentfulContentTypePage
+const { foo } = result.data.allContentfulContentTypePage.nodes[0]
+const {
+ sys: {
+ publishedVersion: revision,
+ contentType: { name }
+ }
+} = allContentfulPage.nodes
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/gatsby-node.input.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/gatsby-node.input.js
new file mode 100644
index 0000000000000..0c603f87e12d8
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/gatsby-node.input.js
@@ -0,0 +1,14 @@
+exports.createSchemaCustomization = null
+export const createSchemaCustomization = null
+
+// export function createResolvers(actions) {
+// actions.createResolvers({
+// ContentfulFoo: {},
+// })
+// }
+
+export const createResolvers = (actions) => {
+ actions.createResolvers({
+ ContentfulFoo: {},
+ })
+}
\ No newline at end of file
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/gatsby-node.output.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/gatsby-node.output.js
new file mode 100644
index 0000000000000..b4b44cc19fab2
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/gatsby-node.output.js
@@ -0,0 +1,14 @@
+exports.createSchemaCustomization = null
+export const createSchemaCustomization = null
+
+// export function createResolvers(actions) {
+// actions.createResolvers({
+// ContentfulFoo: {},
+// })
+// }
+
+export const createResolvers = (actions) => {
+ actions.createResolvers({
+ ContentfulContentTypeFoo: {},
+ })
+}
\ No newline at end of file
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-content-type-all.input.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-content-type-all.input.js
new file mode 100644
index 0000000000000..c66bf5eaf0890
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-content-type-all.input.js
@@ -0,0 +1,9 @@
+const result = await graphql`
+ {
+ allContentfulPage(limit: 1000) {
+ nodes {
+ id
+ }
+ }
+ }
+`
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-content-type-all.output.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-content-type-all.output.js
new file mode 100644
index 0000000000000..10a770abaddc4
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-content-type-all.output.js
@@ -0,0 +1,7 @@
+const result = await graphql`{
+ allContentfulContentTypePage(limit: 1000) {
+ nodes {
+ id
+ }
+ }
+}`
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-content-type-fragment.input.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-content-type-fragment.input.js
new file mode 100644
index 0000000000000..a368562c6f3ca
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-content-type-fragment.input.js
@@ -0,0 +1,34 @@
+export const ExampleFragment = graphql`
+ fragment Example on ContentfulExample {
+ title
+ contentful_id
+ logo {
+ file {
+ url
+ fileName
+ contentType
+ details {
+ size
+ image {
+ width
+ height
+ }
+ }
+ }
+ }
+ }
+ {
+ allContentfulFoo {
+ nodes {
+ ... on ContentfulExample {
+ contentful_id
+ logo {
+ file {
+ url
+ }
+ }
+ }
+ }
+ }
+ }
+`
\ No newline at end of file
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-content-type-fragment.output.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-content-type-fragment.output.js
new file mode 100644
index 0000000000000..2fedb08a9113a
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-content-type-fragment.output.js
@@ -0,0 +1,29 @@
+export const ExampleFragment = graphql`fragment Example on ContentfulContentTypeExample {
+ title
+ sys {
+ id
+ }
+ logo {
+ url
+ fileName
+ contentType
+ size
+ width
+ height
+ }
+}
+
+{
+ allContentfulContentTypeFoo {
+ nodes {
+ ... on ContentfulContentTypeExample {
+ sys {
+ id
+ }
+ logo {
+ url
+ }
+ }
+ }
+ }
+}`
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-content-type-single.input.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-content-type-single.input.js
new file mode 100644
index 0000000000000..6f24e1c68328e
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-content-type-single.input.js
@@ -0,0 +1,7 @@
+const result = await graphql(`
+ {
+ contentfulPage {
+ id
+ }
+ }
+`)
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-content-type-single.output.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-content-type-single.output.js
new file mode 100644
index 0000000000000..799e641992d3e
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-content-type-single.output.js
@@ -0,0 +1,5 @@
+const result = await graphql(`{
+ contentfulContentTypePage {
+ id
+ }
+}`)
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-contentful-assets.input.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-contentful-assets.input.js
new file mode 100644
index 0000000000000..248f80f3c80e5
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-contentful-assets.input.js
@@ -0,0 +1,37 @@
+const result = await graphql(`
+ {
+ allContentfulPage(
+ filter: { logo: { file: { url: { ne: null } } } },
+ sort: [{createdAt: ASC}, {logo: {file: {fileName: ASC}}}]
+ ) {
+ nodes {
+ id
+ logo {
+ file {
+ url
+ fileName
+ contentType
+ details {
+ size
+ image {
+ width
+ height
+ }
+ }
+ }
+ }
+ }
+ }
+ allContentfulAsset(
+ filter: {file: { url: { ne: null } }},
+ sort: [{createdAt: ASC}, {file: {fileName: ASC}}]
+ ) {
+ nodes {
+ id
+ }
+ }
+ contentfulAsset(file: { url: { ne: null } }) {
+ id
+ }
+ }
+`)
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-contentful-assets.output.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-contentful-assets.output.js
new file mode 100644
index 0000000000000..98f4790628c6e
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-contentful-assets.output.js
@@ -0,0 +1,29 @@
+const result = await graphql(`{
+ allContentfulContentTypePage(
+ filter: {logo: {url: {ne: null}}}
+ sort: [{sys: {firstPublishedAt: ASC}}, {logo: {fileName: ASC}}]
+ ) {
+ nodes {
+ id
+ logo {
+ url
+ fileName
+ contentType
+ size
+ width
+ height
+ }
+ }
+ }
+ allContentfulAsset(
+ filter: {url: {ne: null}}
+ sort: [{sys: {firstPublishedAt: ASC}}, {fileName: ASC}]
+ ) {
+ nodes {
+ id
+ }
+ }
+ contentfulAsset(url: {ne: null}) {
+ id
+ }
+}`)
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-contentful-metadata.input.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-contentful-metadata.input.js
new file mode 100644
index 0000000000000..a946df7fd715a
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-contentful-metadata.input.js
@@ -0,0 +1,29 @@
+const result = await graphql(`
+ {
+ allContentfulTag(sort: { fields: contentful_id }) {
+ nodes {
+ name
+ contentful_id
+ }
+ }
+ allContentfulNumber(
+ sort: { fields: contentful_id }
+ filter: {
+ metadata: {
+ tags: { elemMatch: { contentful_id: { eq: "numberInteger" } } }
+ }
+ }
+ ) {
+ nodes {
+ title
+ integer
+ metadata {
+ tags {
+ name
+ contentful_id
+ }
+ }
+ }
+ }
+ }
+`)
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-contentful-metadata.output.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-contentful-metadata.output.js
new file mode 100644
index 0000000000000..e9e6e1b952b4c
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-contentful-metadata.output.js
@@ -0,0 +1,23 @@
+const result = await graphql(`{
+ allContentfulTag(sort: {fields: contentful_id}) {
+ nodes {
+ name
+ contentful_id
+ }
+ }
+ allContentfulContentTypeNumber(
+ sort: {fields: contentful_id}
+ filter: {contentfulMetadata: {tags: {elemMatch: {contentful_id: {eq: "numberInteger"}}}}}
+ ) {
+ nodes {
+ title
+ integer
+ contentfulMetadata {
+ tags {
+ name
+ contentful_id
+ }
+ }
+ }
+ }
+}`)
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-contentful-sys.input.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-contentful-sys.input.js
new file mode 100644
index 0000000000000..6a2dbc9d3103e
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-contentful-sys.input.js
@@ -0,0 +1,45 @@
+const result = await graphql(`
+ {
+ allContentfulPage(limit: 1000) {
+ nodes {
+ contentful_id
+ customName: node_locale
+ createdAt
+ updatedAt
+ revision
+ spaceId
+ sys {
+ type
+ contentType {
+ __typename
+ }
+ }
+ }
+ }
+ contentfulPage {
+ contentful_id
+ node_locale
+ createdAt
+ updatedAt
+ revision
+ spaceId
+ sys {
+ type
+ contentType {
+ __typename
+ }
+ }
+ }
+ allContentfulPage(
+ filter: { slug: { eq: "blog" }, node_locale: { eq: $locale } }
+ sort: { updatedAt: DESC }
+ ) {
+ nodes {
+ id
+ }
+ }
+ contentfulPage(slug: { eq: "blog" }, node_locale: { eq: $locale }) {
+ id
+ }
+ }
+`)
diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-contentful-sys.output.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-contentful-sys.output.js
new file mode 100644
index 0000000000000..a12f1e1e4998c
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/gatsby-source-contentful/graphql-contentful-sys.output.js
@@ -0,0 +1,43 @@
+const result = await graphql(`{
+ allContentfulContentTypePage(limit: 1000) {
+ nodes {
+ sys {
+ id
+ customName: locale
+ firstPublishedAt
+ publishedAt
+ publishedVersion
+ spaceId
+ type
+ contentType {
+ name
+ }
+ }
+ }
+ }
+ contentfulContentTypePage {
+ sys {
+ id
+ locale
+ firstPublishedAt
+ publishedAt
+ publishedVersion
+ spaceId
+ type
+ contentType {
+ name
+ }
+ }
+ }
+ allContentfulContentTypePage(
+ filter: {slug: {eq: "blog"}, sys: {locale: {eq: $locale}}}
+ sort: {sys: {publishedAt: DESC}}
+ ) {
+ nodes {
+ id
+ }
+ }
+ contentfulContentTypePage(slug: {eq: "blog"}, sys: {locale: {eq: $locale}}) {
+ id
+ }
+}`)
diff --git a/packages/gatsby-codemods/src/transforms/__tests__/gatsby-source-contentful.js b/packages/gatsby-codemods/src/transforms/__tests__/gatsby-source-contentful.js
new file mode 100644
index 0000000000000..f9a7c86eb4d56
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/__tests__/gatsby-source-contentful.js
@@ -0,0 +1,27 @@
+const tests = [
+ `content-types`,
+ `content-types-typescript`,
+ `contentful-asset`,
+ `contentful-sys`,
+ `gatsby-node`,
+ `graphql-content-type-all`,
+ `graphql-content-type-fragment`,
+ `graphql-content-type-single`,
+ `graphql-contentful-assets`,
+ `graphql-contentful-metadata`,
+ `graphql-contentful-sys`,
+]
+
+const defineTest = require(`jscodeshift/dist/testUtils`).defineTest
+
+describe(`codemods`, () => {
+ tests.forEach(test =>
+ defineTest(
+ __dirname,
+ `gatsby-source-contentful`,
+ null,
+ `gatsby-source-contentful/${test}`,
+ { parser: test.indexOf(`typescript`) !== -1 ? `ts` : `js` }
+ )
+ )
+})
diff --git a/packages/gatsby-codemods/src/transforms/gatsby-source-contentful.js b/packages/gatsby-codemods/src/transforms/gatsby-source-contentful.js
new file mode 100644
index 0000000000000..9e77eeba1b580
--- /dev/null
+++ b/packages/gatsby-codemods/src/transforms/gatsby-source-contentful.js
@@ -0,0 +1,734 @@
+import * as graphql from "graphql"
+import { parse, print } from "recast"
+import { transformFromAstSync, parseSync } from "@babel/core"
+import { cloneDeep } from "lodash"
+
+export default function jsCodeShift(file) {
+ if (
+ file.path.includes(`node_modules`) ||
+ file.path.includes(`.cache`) ||
+ file.path.includes(`public`)
+ ) {
+ return file.source
+ }
+ const transformedSource = babelRecast(file.source, file.path)
+ return transformedSource
+}
+
+export function babelRecast(code, filePath) {
+ const transformedAst = parse(code, {
+ parser: {
+ parse: source => runParseSync(source, filePath),
+ },
+ })
+
+ const changedTracker = { hasChanged: false, filename: filePath } // recast adds extra semicolons that mess with diffs and we want to avoid them
+
+ const options = {
+ cloneInputAst: false,
+ code: false,
+ ast: true,
+ plugins: [[updateImport, changedTracker]],
+ }
+
+ const { ast } = transformFromAstSync(transformedAst, code, options)
+
+ if (changedTracker.hasChanged) {
+ return print(ast, { lineTerminator: `\n` }).code
+ }
+ return code
+}
+
+const CONTENT_TYPE_SELECTOR_REGEX = /^(allContentful|[cC]ontentful)([A-Z0-9].+)/
+const CONTENT_TYPE_SELECTOR_BLACKLIST = [`Asset`, `Reference`, `Id`, `Tag`]
+const SYS_FIELDS_TRANSFORMS = new Map([
+ [`node_locale`, `locale`],
+ [`contentful_id`, `id`],
+ [`spaceId`, `spaceId`],
+ [`createdAt`, `firstPublishedAt`],
+ [`updatedAt`, `publishedAt`],
+ [`revision`, `publishedVersion`],
+])
+
+const isContentTypeSelector = selector => {
+ if (!selector) {
+ return false
+ }
+ const res = selector.match(CONTENT_TYPE_SELECTOR_REGEX)
+ return res && !CONTENT_TYPE_SELECTOR_BLACKLIST.includes(res[2])
+}
+const updateContentfulSelector = selector =>
+ selector.replace(`ontentful`, `ontentfulContentType`)
+
+const renderFilename = (path, state) =>
+ `${state.opts.filename} (Line ${path.node.loc.start.line})`
+
+const injectNewFields = (selections, newFields, fieldToReplace) => {
+ if (!fieldToReplace) {
+ return [...selections, ...newFields]
+ }
+
+ const fieldIndex = selections.findIndex(
+ ({ name }) => name?.value === fieldToReplace
+ )
+
+ return [
+ ...selections.slice(0, fieldIndex),
+ ...newFields,
+ ...selections.slice(fieldIndex + 1),
+ ]
+}
+
+function getNestedMemberExpressionProperties(node, t) {
+ const properties = []
+ let current = node
+ while (t.isMemberExpression(current)) {
+ if (t.isIdentifier(current.property)) {
+ properties.push(current.property.name)
+ }
+ current = current.object
+ }
+ return properties.reverse()
+}
+
+export function updateImport(babel) {
+ const { types: t } = babel
+ // Stack to keep track of nesting
+ const stack = []
+ // Flag to indicate whether we are inside createResolvers function
+ let insideCreateResolvers = false
+ return {
+ visitor: {
+ Identifier(path, state) {
+ if (
+ path.node.name === `createSchemaCustomization` &&
+ state.opts.filename.match(/gatsby-node/)
+ ) {
+ console.log(
+ `${renderFilename(
+ path,
+ state
+ )}: Check your custom schema customizations if you patch or adjust schema related to Contentful. You probably can remove it now.`
+ )
+ }
+
+ // Rename content type identifiers within createResolvers
+ if (insideCreateResolvers) {
+ const variableName = path.node.name
+
+ // Check if the variable name matches the regex
+ if (isContentTypeSelector(variableName)) {
+ path.node.name = updateContentfulSelector(variableName)
+ state.opts.hasChanged = true
+ }
+ }
+ },
+ ObjectPattern: {
+ enter(path) {
+ // Push this ObjectPattern onto the stack as we enter it
+ stack.push(path.node.properties.map(prop => prop.key?.name))
+ },
+ exit(path, state) {
+ // Check if the variable name matches the regex
+ path.node.properties.forEach(prop => {
+ if (isContentTypeSelector(prop.key?.name)) {
+ prop.key.name = updateContentfulSelector(prop.key.name)
+ state.opts.hasChanged = true
+ }
+ })
+
+ // Rename contentType.__typename to contentType.name
+ if (
+ JSON.stringify([[`sys`], [`contentType`], [`__typename`]]) ===
+ JSON.stringify(stack)
+ ) {
+ const typenameProp = path.node.properties.find(
+ prop => prop.key.name === `__typename`
+ )
+ if (typenameProp) {
+ typenameProp.key = t.identifier(`name`)
+ typenameProp.value = t.identifier(`name`)
+ }
+ // Merge old sys fields into new structure
+ } else {
+ const transformedSysProperties = []
+ path.node.properties.forEach(property => {
+ if (SYS_FIELDS_TRANSFORMS.has(property.key?.name)) {
+ const transformedProp = {
+ ...property,
+ key: {
+ ...property.key,
+ name: SYS_FIELDS_TRANSFORMS.get(property.key.name),
+ },
+ }
+ transformedSysProperties.push(transformedProp)
+ }
+ })
+ if (transformedSysProperties.length) {
+ const sysField = {
+ type: `Property`,
+ key: {
+ type: `Identifier`,
+ name: `sys`,
+ },
+ value: {
+ type: `ObjectPattern`,
+ properties: transformedSysProperties,
+ },
+ }
+ path.node.properties = injectSysField(
+ sysField,
+ path.node.properties
+ )
+
+ state.opts.hasChanged = true
+ }
+ }
+
+ // Pop this ObjectPattern off the stack as we exit it
+ stack.pop()
+ },
+ },
+ MemberExpression(path, state) {
+ const nestedProperties = getNestedMemberExpressionProperties(
+ path.node,
+ t
+ )
+
+ const assetFlatStructure = new Map([
+ [`url`, [`url`, `file`]],
+ [`fileName`, [`fileName`, `file`]],
+ [`contentType`, [`contentType`, `file`]],
+ [`size`, [`size`, `details`, `file`]],
+ [`width`, [`width`, `image`, `details`, `file`]],
+ [`height`, [`height`, `image`, `details`, `file`]],
+ ])
+
+ for (const [newProp, oldProps] of assetFlatStructure) {
+ if (
+ nestedProperties.slice(-oldProps.length).join(`.`) ===
+ oldProps.reverse().join(`.`)
+ ) {
+ // We found a matching nested property.
+ // Rebuild the MemberExpression with the new structure.
+ let baseExpression = path.node
+ for (let i = 0; i < oldProps.length; i++) {
+ baseExpression = baseExpression.object
+ }
+ const newExpression = t.memberExpression(
+ baseExpression,
+ t.identifier(newProp)
+ )
+ path.replaceWith(newExpression)
+ state.opts.hasChanged = true
+ return
+ }
+ }
+
+ // Identify MemberExpression for `allContentfulPage.nodes.contentfulId`
+ const propName = path.node.property.name
+ const replacement = SYS_FIELDS_TRANSFORMS.get(propName)
+
+ if (replacement) {
+ // Rewrite the MemberExpression with the new property
+ path.node.property = t.identifier(replacement)
+
+ // Also rewrite the parent node to `.sys.` if it's not already
+ if (path.node.object.property?.name !== `sys`) {
+ path.node.object = t.memberExpression(
+ path.node.object,
+ t.identifier(`sys`)
+ )
+ }
+ state.opts.hasChanged = true
+ }
+
+ // Rename sys.contentType.__typename to sys.contentType.name
+ if (
+ propName === `__typename` &&
+ t.isMemberExpression(path.node.object) &&
+ t.isIdentifier(path.node.object.property, { name: `contentType` }) &&
+ t.isMemberExpression(path.node.object.object) &&
+ t.isIdentifier(path.node.object.object.property, { name: `sys` })
+ ) {
+ path.node.property = t.identifier(`name`)
+ return
+ }
+
+ if (isContentTypeSelector(path.node.property?.name)) {
+ if (
+ path.node.object?.name === `data` ||
+ path.node.object.property?.name === `data`
+ ) {
+ path.node.property.name = updateContentfulSelector(
+ path.node.property.name
+ )
+ state.opts.hasChanged = true
+ } else {
+ console.log(
+ `${renderFilename(path, state)}: You might need to change "${
+ path.node.property?.name
+ }" to "${updateContentfulSelector(path.node.property.name)}"`
+ )
+ }
+ }
+ },
+ ExportNamedDeclaration: {
+ enter(path) {
+ const declaration = path.node.declaration
+
+ // For "export function createResolvers() {}"
+ if (
+ t.isFunctionDeclaration(declaration) &&
+ t.isIdentifier(declaration.id, { name: `createResolvers` })
+ ) {
+ insideCreateResolvers = true
+ }
+
+ // For "export const createResolvers = function() {}" or "export const createResolvers = () => {}"
+ else if (t.isVariableDeclaration(declaration)) {
+ const declarators = declaration.declarations
+ for (const declarator of declarators) {
+ if (
+ t.isIdentifier(declarator.id, { name: `createResolvers` }) &&
+ (t.isFunctionExpression(declarator.init) ||
+ t.isArrowFunctionExpression(declarator.init))
+ ) {
+ insideCreateResolvers = true
+ }
+ }
+ }
+ },
+ exit() {
+ insideCreateResolvers = false
+ },
+ },
+ TSInterfaceDeclaration(path, state) {
+ path.node.body.body.forEach(property => {
+ if (
+ t.isTSPropertySignature(property) &&
+ isContentTypeSelector(property.key.name)
+ ) {
+ property.key.name = updateContentfulSelector(property.key.name)
+ state.opts.hasChanged = true
+ }
+ })
+ },
+ TaggedTemplateExpression({ node }, state) {
+ if (node.tag.name !== `graphql`) {
+ return
+ }
+ const query = node.quasi?.quasis?.[0]?.value?.raw
+ if (query) {
+ const { ast: transformedGraphQLQuery, hasChanged } =
+ processGraphQLQuery(query, state)
+
+ if (hasChanged) {
+ node.quasi.quasis[0].value.raw = graphql.print(
+ transformedGraphQLQuery
+ )
+ state.opts.hasChanged = true
+ }
+ }
+ },
+ CallExpression({ node }, state) {
+ if (node.callee.name !== `graphql`) {
+ return
+ }
+ const query = node.arguments?.[0].quasis?.[0]?.value?.raw
+
+ if (query) {
+ const { ast: transformedGraphQLQuery, hasChanged } =
+ processGraphQLQuery(query, state)
+
+ if (hasChanged) {
+ node.arguments[0].quasis[0].value.raw = graphql.print(
+ transformedGraphQLQuery
+ )
+ state.opts.hasChanged = true
+ }
+ }
+ },
+ },
+ }
+}
+
+// Locate a subfield within a selection set or fields
+function locateSubfield(node, fieldName) {
+ const subFields = Array.isArray(node)
+ ? node
+ : node.selectionSet?.selections || node.value?.fields
+ if (!subFields) {
+ return null
+ }
+ return subFields.find(({ name }) => name?.value === fieldName)
+}
+
+// Replace first old field occurence with new sys field
+const injectSysField = (sysField, selections) => {
+ let sysInjected = false
+
+ selections = selections
+ .map(field => {
+ const fieldName = field.name?.value || field.key?.name
+ if (fieldName === `sys`) {
+ const existingSysFields = (
+ field.selectionSet?.selections || field.value.properties
+ ).map(subField => {
+ const kind = subField.kind || subField.type
+ // handle contentType rename
+ if (
+ (kind === `ObjectProperty`
+ ? subField.key.name
+ : subField.name.value) === `contentType`
+ ) {
+ const subfields =
+ kind === `ObjectProperty`
+ ? subField.value.properties
+ : subField.selectionSet.selections
+
+ subfields.map(contentTypeField => {
+ const fieldName =
+ kind === `ObjectProperty`
+ ? contentTypeField.key.name
+ : contentTypeField.name.value
+
+ if (fieldName === `__typename`) {
+ if (kind === `ObjectProperty`) {
+ contentTypeField.key.name = `name`
+ } else {
+ contentTypeField.name.value = `name`
+ }
+ }
+ })
+ }
+ return subField
+ })
+ if (sysField?.type === `Property`) {
+ sysField.value.properties.push(...existingSysFields)
+ } else {
+ sysField.selectionSet.selections.push(...existingSysFields)
+ }
+ return null
+ }
+ return field
+ })
+ .filter(Boolean)
+
+ // Replace first old field occurence with new sys field
+ return selections
+ .map(field => {
+ const fieldName = field.name?.value || field.key?.name
+ if (SYS_FIELDS_TRANSFORMS.has(fieldName)) {
+ if (!sysInjected) {
+ // Inject for first occurence of a sys field
+ sysInjected = true
+ return sysField
+ }
+ // Remove all later fields
+ return null
+ }
+ // Keep non-sys fields as they are
+ return field
+ })
+ .filter(Boolean)
+}
+
+// Flatten the old deeply nested Contentful asset structure
+const flattenAssetFields = node => {
+ const flatAssetFields = []
+
+ // Flatten asset file field
+ const fileField = locateSubfield(node, `file`)
+
+ if (fileField) {
+ // Top level file fields
+ const urlField = locateSubfield(fileField, `url`)
+ if (urlField) {
+ flatAssetFields.push(urlField)
+ }
+ const fileNameField = locateSubfield(fileField, `fileName`)
+ if (fileNameField) {
+ flatAssetFields.push(fileNameField)
+ }
+ const contentTypeField = locateSubfield(fileField, `contentType`)
+ if (contentTypeField) {
+ flatAssetFields.push(contentTypeField)
+ }
+
+ // details subfield with size and dimensions
+ const detailsField = locateSubfield(fileField, `details`)
+ if (detailsField) {
+ const sizeField = locateSubfield(detailsField, `size`)
+ if (sizeField) {
+ flatAssetFields.push(sizeField)
+ }
+ // width & height from image subfield
+ const imageField = locateSubfield(detailsField, `image`)
+ if (imageField) {
+ const widthField = locateSubfield(imageField, `width`)
+ if (widthField) {
+ flatAssetFields.push(widthField)
+ }
+ const heightField = locateSubfield(imageField, `height`)
+ if (heightField) {
+ flatAssetFields.push(heightField)
+ }
+ }
+ }
+ }
+ return flatAssetFields
+}
+
+function createNewSysField(fields, fieldType = `Field`) {
+ const kind = fieldType === `Argument` ? `Argument` : `Field`
+ const subKindValue = fieldType === `Argument` ? `ObjectValue` : `SelectionSet`
+ const subKindIndex = fieldType === `Argument` ? `value` : `selectionSet`
+ const subKindIndex2 = fieldType === `Argument` ? `fields` : `selections`
+
+ const contentfulSysFields = fields.filter(({ name }) =>
+ SYS_FIELDS_TRANSFORMS.has(name?.value)
+ )
+
+ if (contentfulSysFields.length) {
+ const transformedSysFields = cloneDeep(contentfulSysFields).map(field => {
+ return {
+ ...field,
+ name: {
+ ...field.name,
+ value: SYS_FIELDS_TRANSFORMS.get(field.name.value),
+ },
+ }
+ })
+
+ const sysField = {
+ kind: kind,
+ name: {
+ kind: `Name`,
+ value: `sys`,
+ },
+ [subKindIndex]: {
+ kind: subKindValue,
+ [subKindIndex2]: transformedSysFields,
+ },
+ }
+ return sysField
+ }
+ return null
+}
+
+function processGraphQLQuery(query) {
+ try {
+ let hasChanged = false // this is sort of a hack, but print changes formatting and we only want to use it when we have to
+ const ast = graphql.parse(query)
+
+ function processArguments(node) {
+ // flatten Contentful Asset filters
+ // Queries directly on allContentfulAssets
+ const flatAssetFields = flattenAssetFields(node)
+ if (flatAssetFields.length) {
+ node.value.fields = injectNewFields(
+ node.value.fields,
+ flatAssetFields,
+ `file`
+ )
+ hasChanged = true
+ }
+ // Subfields that might be asset fields
+ node.value.fields.forEach((field, fieldIndex) => {
+ const flatAssetFields = flattenAssetFields(field)
+ if (flatAssetFields.length) {
+ node.value.fields[fieldIndex].value.fields = injectNewFields(
+ node.value.fields[fieldIndex].value.fields,
+ flatAssetFields,
+ `file`
+ )
+ hasChanged = true
+ }
+ })
+
+ // Rename metadata -> contentfulMetadata
+ node.value.fields.forEach(field => {
+ if (field.name.value === `metadata`) {
+ field.name.value = `contentfulMetadata`
+ hasChanged = true
+ }
+ })
+
+ const sysField = createNewSysField(node.value.fields, `Argument`)
+ if (sysField) {
+ node.value.fields = injectSysField(sysField, node.value.fields)
+ hasChanged = true
+ }
+ }
+
+ graphql.visit(ast, {
+ Argument(node) {
+ // Update filters and sort of collection endpoints
+ if ([`filter`, `sort`].includes(node.name.value)) {
+ if (node.value.kind === `ListValue`) {
+ node.value.values.forEach(node => processArguments({ value: node }))
+ return
+ }
+ processArguments(node)
+ }
+ },
+ SelectionSet(node) {
+ // Rename content type node selectors
+ node.selections.forEach(field => {
+ if (isContentTypeSelector(field.name?.value)) {
+ field.name.value = updateContentfulSelector(field.name.value)
+ hasChanged = true
+ }
+ })
+ },
+ InlineFragment(node) {
+ if (isContentTypeSelector(node.typeCondition.name?.value)) {
+ node.typeCondition.name.value = updateContentfulSelector(
+ node.typeCondition.name.value
+ )
+ hasChanged = true
+
+ const sysField = createNewSysField(node.selectionSet.selections)
+ if (sysField) {
+ node.selectionSet.selections = injectSysField(
+ sysField,
+ node.selectionSet.selections
+ )
+ hasChanged = true
+ }
+ }
+ },
+ FragmentDefinition(node) {
+ if (isContentTypeSelector(node.typeCondition.name?.value)) {
+ node.typeCondition.name.value = updateContentfulSelector(
+ node.typeCondition.name.value
+ )
+ hasChanged = true
+
+ const sysField = createNewSysField(node.selectionSet.selections)
+ if (sysField) {
+ node.selectionSet.selections = injectSysField(
+ sysField,
+ node.selectionSet.selections
+ )
+ hasChanged = true
+ }
+ }
+ },
+ Field(node) {
+ // Flatten asset fields
+ if (node.name.value === `contentfulAsset`) {
+ const flatAssetFields = flattenAssetFields({
+ value: { fields: node.arguments },
+ })
+
+ node.arguments = injectNewFields(
+ node.arguments,
+ flatAssetFields,
+ `file`
+ )
+
+ hasChanged = true
+ }
+
+ // Rename metadata -> contentfulMetadata
+ if (node.name.value === `metadata`) {
+ const tagsField = locateSubfield(node, `tags`)
+ if (tagsField) {
+ node.name.value = `contentfulMetadata`
+ hasChanged = true
+ }
+ }
+
+ if (isContentTypeSelector(node.name.value)) {
+ // Move sys properties into new sys property
+ const nodesField =
+ node.name.value.indexOf(`all`) === 0 &&
+ locateSubfield(node, `nodes`)
+ const rootNode = nodesField || node
+
+ const sysField = createNewSysField(rootNode.selectionSet.selections)
+ if (sysField) {
+ rootNode.selectionSet.selections = injectSysField(
+ sysField,
+ rootNode.selectionSet.selections
+ )
+ hasChanged = true
+ }
+
+ const filterNode =
+ node.name.value.indexOf(`all`) === 0 &&
+ locateSubfield(node.arguments, `filter`)
+
+ const filterFields = filterNode?.value?.fields || node.arguments
+
+ if (filterFields && filterFields.length) {
+ const sysField = createNewSysField(filterFields, `Argument`)
+ // Inject the new sys at the first occurence of any old sys field
+ if (sysField) {
+ if (node.name.value.indexOf(`all`) === 0) {
+ const filterFieldIndex = node.arguments.findIndex(
+ field => field.name?.value === `filter`
+ )
+ node.arguments[filterFieldIndex].value.fields = injectSysField(
+ sysField,
+ node.arguments[filterFieldIndex].value.fields
+ )
+ } else {
+ node.arguments = injectSysField(sysField, filterFields)
+ }
+ hasChanged = true
+ }
+ }
+ }
+
+ // Flatten asset file field
+ const flatAssetFields = flattenAssetFields(node)
+ if (flatAssetFields.length) {
+ node.selectionSet.selections = injectNewFields(
+ node.selectionSet.selections,
+ flatAssetFields,
+ `file`
+ )
+ hasChanged = true
+ }
+ },
+ })
+ return { ast, hasChanged }
+ } catch (err) {
+ throw new Error(
+ `GatsbySourceContentfulCodemod: GraphQL syntax error in query:\n\n${query}\n\nmessage:\n\n${err}`
+ )
+ }
+}
+
+function runParseSync(source, filePath) {
+ let ast
+ try {
+ ast = parseSync(source, {
+ plugins: [
+ `@babel/plugin-syntax-jsx`,
+ `@babel/plugin-proposal-class-properties`,
+ ],
+ overrides: [
+ {
+ test: [`**/*.ts`, `**/*.tsx`],
+ plugins: [[`@babel/plugin-syntax-typescript`, { isTSX: true }]],
+ },
+ ],
+ filename: filePath,
+ parserOpts: {
+ tokens: true, // recast uses this
+ },
+ })
+ } catch (e) {
+ console.error(e)
+ }
+ if (!ast) {
+ console.log(
+ `The codemod was unable to parse ${filePath}. If you're running against the '/src' directory and your project has a custom babel config, try running from the root of the project so the codemod can pick it up.`
+ )
+ }
+ return ast
+}
diff --git a/packages/gatsby-source-contentful/.babelrc b/packages/gatsby-source-contentful/.babelrc
deleted file mode 100644
index ac0ad292bb087..0000000000000
--- a/packages/gatsby-source-contentful/.babelrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "presets": [["babel-preset-gatsby-package"]]
-}
diff --git a/packages/gatsby-source-contentful/.babelrc.js b/packages/gatsby-source-contentful/.babelrc.js
new file mode 100644
index 0000000000000..1e7ab4a86bfca
--- /dev/null
+++ b/packages/gatsby-source-contentful/.babelrc.js
@@ -0,0 +1,4 @@
+module.exports = {
+ presets: [["babel-preset-gatsby-package", { esm: true }]],
+ plugins: ["@babel/plugin-transform-modules-commonjs"],
+}
diff --git a/packages/gatsby-source-contentful/.gitignore b/packages/gatsby-source-contentful/.gitignore
index 1a46274e77c91..e69787c28dfd7 100644
--- a/packages/gatsby-source-contentful/.gitignore
+++ b/packages/gatsby-source-contentful/.gitignore
@@ -1,4 +1,35 @@
-/__tests__
-/yarn.lock
-/*.js
+# Logs
+logs
+*.log
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directory
+# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
+node_modules
+
+decls
+dist
+*.js
+!gatsby-node.js
+!.babelrc.js
+!jest.config.js
!index.js
diff --git a/packages/gatsby-source-contentful/MIGRATION.md b/packages/gatsby-source-contentful/MIGRATION.md
new file mode 100644
index 0000000000000..3e6b44c4b3a3b
--- /dev/null
+++ b/packages/gatsby-source-contentful/MIGRATION.md
@@ -0,0 +1,440 @@
+# Migration Guide for `gatsby-source-contentful` v9
+
+The v9 release of `gatsby-source-contentful` brings significant improvements, focusing on stability and enhancing the developer experience.
+
+
+Table of contents
+
+- [Migration Guide for `gatsby-source-contentful` v9](#migration-guide-for-gatsby-source-contentful-v9)
+ - [Core Changes, Updates and new Features](#core-changes-updates-and-new-features)
+ - [1. Introduction](#1-introduction)
+ - [2. Automated Migration with Codemods](#2-automated-migration-with-codemods)
+ - [3. Manual Changes and Additional Updates](#3-manual-changes-and-additional-updates)
+ - [Breaking Changes](#breaking-changes)
+ - [1. Remove Your Workarounds](#1-remove-your-workarounds)
+ - [2. New content type naming pattern](#2-new-content-type-naming-pattern)
+ - [3. Metadata / Data from Contentful's `sys` field](#3-metadata--data-from-contentfuls-sys-field)
+ - [Sorting by `sys`](#sorting-by-sys)
+ - [Filtering by `sys`](#filtering-by-sys)
+ - [4. Fields](#4-fields)
+ - [Text fields](#text-fields)
+ - [JSON fields](#json-fields)
+ - [Rich Text field](#rich-text-field)
+ - [Schema Comparison](#schema-comparison)
+ - [Query Updates](#query-updates)
+ - [Rendering changes for Rich Text](#rendering-changes-for-rich-text)
+ - [5. Assets](#5-assets)
+ - [Old GraphlQL schema for assets](#old-graphlql-schema-for-assets)
+ - [New GraphlQL schema for assets](#new-graphlql-schema-for-assets)
+ - [6. Using the Contentful Preview API (CPA)](#6-using-the-contentful-preview-api-cpa)
+ - [Conclusion and Support](#conclusion-and-support)
+
+
+
+## Core Changes, Updates and new Features
+
+- **Dynamic Schema Generation**: Schema types are now dynamically generated based on the Contentful content model, eliminating site breakages due to empty content fields.
+- **Rich Text and JSON Field Enhancements**: Improved handling of Rich Text and JSON fields for more accurate processing, querying, and rendering.
+- **Schema and Performance Optimizations**: An optimized, leaner schema reduces node count and improves build times, especially for larger projects.
+- **Contentful GraphQL API Alignment**: Enhanced alignment with Contentful's GraphQL API enables more intuitive and consistent querying.
+- **Removed Content Type Name Restrictions**: New naming patterns now allow all content type names, including previously restricted names like `entity` and `reference`.
+- **Updated Field Name Restrictions**: Fields can now be named `contentful_id`, with restrictions now applied to names like `sys`, `contentfulMetadata`, and `linkedFrom`.
+- **Refined Backlinks**: Backlinks/references are now located in the `linkedFrom` field, aligning with Contentful's GraphQL API structure.
+- **Expanded Configuration Options**: Additional [configuration options](#advanced-configuration) provide greater control and customization for your specific project needs.
+
+## 1. Introduction
+
+Version 9 of the `gatsby-source-contentful` plugin marks a significant evolution, introducing changes mainly in the structure of GraphQL queries.
+
+This guide helps you to navigate through these updates, ensuring a straightforward migration to the latest version. We'll cover both automated and manual steps to adapt your project seamlessly to v9's advancements.
+
+## 2. Automated Migration with Codemods
+
+Before you start manual updates, take advantage of our codemods that automate most of the transition process. You can use one of the following options to apply the codemods to your project:
+
+**Option A:** (Direct Execution)
+
+```bash
+npx gatsby-codemods@ctf-next gatsby-source-contentful .
+```
+
+**Hint:** If you use `.mjs` files, rename them to `.js` or `.ts` first.
+
+**Option B:** (Install, Execute, Uninstall)
+
+```bash
+# Install codemods
+npm install -D jscodeshift gatsby-codemods@ctf-next
+
+# Execute codemods
+npx jscodeshift -t ./node_modules/gatsby-codemods/transforms/gatsby-source-contentful.js .
+
+# Uninstall codemods
+npm remove jscodeshift gatsby-codemods
+```
+
+**Handling Large Codebases:** If your project is particularly large, you may need to increase the maximum memory allocation for the process:
+
+```bash
+NODE_OPTIONS=--max_old_space_size=10240 npx ...
+```
+
+## 3. Manual Changes and Additional Updates
+
+After applying the codemods, review your project for any additional changes that need to be made manually. The following sections detail specific areas that require attention.
+
+## Breaking Changes
+
+With v9, the schema generated by `gatsby-source-contentful` is now closely aligned with Contentful's GraphQL API. This significant alignment means several changes in how you structure your GraphQL queries:
+
+### 1. Remove Your Workarounds
+
+If you previously implemented workarounds, such as freezing your schema with `gatsby-plugin-schema-snapshot` or using `createSchemaCustomization` to address issues in your Contentful GraphQL schema, you can safely remove these.
+
+The enhancements in v9 have been designed to eliminate the need for such workarounds, offering a more reliable schema generation process that mirrors the structure and functionality of Contentful's native GraphQL API.
+
+### 2. New content type naming pattern
+
+The old naming pattern for generated node types based on your Contentful content model was pretty simple. This gave us shorter queries, but came with restrictions for content type names. Content type names like `entity`, `reference`, `tag`, `asset` did cause problems or simply did not work.
+
+1. Generated GraphQL node types are now prefixed with `ContentfulContentType` instead of `Contentful`
+2. Hardcoded types and interfaces stay short. For example: `ContentfulEntry`, `ContentfulAsset`, `ContentfulLocation` or
+ `ContentfulRichText`
+
+This is close to the [behaviour of the Contentful GraphQL API](https://www.contentful.com/developers/docs/references/graphql/#/reference/schema-generation/types):
+
+> If the generated name starts with a number or collides with a reserved type name, it gets prefixed with 'ContentType'
+
+Instead of doing this on a collision, we always prefix for consistency.
+
+```diff
+export const pageQuery = graphql`
+ query {
+- allContentfulProduct {
++ allContentfulContentTypeProduct {
+ ...
+ }
+ }
+`
+```
+
+### 3. Metadata / Data from Contentful's `sys` field
+
+The `sys` field now reflects the structure of the sys field in the Contentful [GraphQL API](https://www.contentful.com/developers/docs/references/graphql/#/reference/schema-generation/sys-field)
+
+| Old Path | New Path | Comment |
+| --------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| node_locale | sys.locale | this does not exists in Contentful's sys but we need it (for now) [as Contentful GraphQL handles locales different](https://www.contentful.com/developers/docs/references/graphql/#/reference/locale-handling) |
+| contentful_id | sys.id |
+| spaceId | sys.spaceId |
+| createdAt | sys.firstPublishedAt |
+| updatedAt | sys.publishedAt |
+| revision | sys.publishedVersion |
+| | sys.environmentId | This is **new** |
+| sys.contentType | sys.contentType | This is now a real reference to the Content Type node. If you used this field, you will probably need to change the`__typename` subfield to `name` |
+
+#### Sorting by `sys`
+
+```graphql
+allContentfulPage(sort: { fields: contentful_id }) { ... }
+```
+
+becomes
+
+```graphql
+allContentfulContentTypePage(sort: { fields: sys___id }) { ... }
+```
+
+#### Filtering by `sys`
+
+```graphql
+contentfulPage(
+ contentful_id: {
+ eq: "38akBjGb3T1t4AjB87wQjo"
+ }
+) { ... }
+```
+
+becomes
+
+```graphql
+contentfulContentTypePage(
+ sys: {
+ id: {
+ eq: "38akBjGb3T1t4AjB87wQjo"
+ }
+ }
+) { ... }
+```
+
+### 4. Fields
+
+#### Text fields
+
+- The raw field value is now stored in `FIELD_NAME { raw }` instead of `FIELD_NAME { FIELD_NAME }`
+- This helps for consistency on your side, while reducing node type bload in the Gatsby backend
+
+#### JSON fields
+
+1. You can no more queries sub-fields, which ensures your queries won't break on changed content. This means you now have to check if the results have a value on your own.
+ - You can achieve this with a [custom Field Editor](https://www.contentful.com/blog/apps-and-open-source-guide-to-making-contentful-truly-yours/) in Contentful.
+2. Spaces within keys of JSON fields are now kept, means your data is accessible in you code the same way it is entered in contentful.
+
+```jsx
+
Born at: {actor.Born_At}
+```
+
+becomes
+
+```jsx
+
Born at: {actor["Born At"]}
+```
+
+#### Rich Text field
+
+The schema for Rich Text fields in `gatsby-source-contentful` v9 has been updated to align with Contentful's GraphQL API. This change enhances the flexibility and consistency of querying Rich Text content. You will need to adjust your GraphQL queries and rendering code accordingly.
+
+Key Changes:
+
+1. **Unified Link Subfield**: You can now query all linked Contentful content types within the `links` subfield of Rich Text, irrespective of whether they are directly linked in the current content or not.
+2. **Direct Entry ID Querying**: Instead of navigating through various fields, the entry ID can now be queried directly as a child field of `links`. This simplification makes your queries more concise.
+
+##### Schema Comparison
+
+**Old Schema:**
+
+```graphql
+type ContentfulRichTextRichText {
+ raw: String
+ references: [ContentfulAssetContentfulContentReferenceContentfulLocationContentfulTextUnion]
+ @link(by: "id", from: "references___NODE")
+}
+union ContentfulAssetContentfulContentReferenceContentfulLocationContentfulTextUnion =
+ ContentfulAsset
+ | ContentfulContentReference
+ | ContentfulLocation
+ | ContentfulText
+```
+
+**New Schema:**
+
+```graphql
+type ContentfulNodeTypeRichText @dontInfer {
+ json: JSON
+ links: ContentfulNodeTypeRichTextLinks
+}
+
+type ContentfulNodeTypeRichTextLinks {
+ assets: ContentfulNodeTypeRichTextAssets
+ entries: ContentfulNodeTypeRichTextEntries
+}
+
+type ContentfulNodeTypeRichTextAssets {
+ block: [ContentfulAsset]!
+ hyperlink: [ContentfulAsset]!
+}
+
+type ContentfulNodeTypeRichTextEntries {
+ inline: [ContentfulEntry]!
+ block: [ContentfulEntry]!
+ hyperlink: [ContentfulEntry]!
+}
+```
+
+##### Query Updates
+
+Update your Rich Text queries to reflect the new schema:
+
+**Old Example Query:**
+
+```graphql
+query pageQuery {
+ allContentfulRichText {
+ nodes {
+ richText {
+ raw
+ references {
+ ... on ContentfulAsset {
+ url
+ }
+ ... on ContentfulText {
+ id
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+**New Example Query:**
+
+```graphql
+query pageQuery {
+ allContentfulContentTypeRichText {
+ nodes {
+ richText {
+ json
+ links {
+ assets {
+ block {
+ url
+ }
+ }
+ entries {
+ block {
+ ... on ContentfulContentTypeText {
+ id
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+**Notes**:
+
+- The `raw` field is now `json`.
+- The `references` field is replaced by `links`, providing a more structured and granular approach to handling linked content.
+
+##### Rendering changes for Rich Text
+
+Instead of passing your option object into `renderRichText()` you now pass a option factory. This allows you to access the data you queried for all the linked entities (`assetBlockMap`, `entryBlockMap`, `entryInlineMap`).
+
+**Old rendering logic:**
+
+```tsx
+import { renderRichText } from "gatsby-source-contentful/rich-text"
+import { Options } from "@contentful/rich-text-react-renderer"
+import { BLOCKS, MARKS } from "@contentful/rich-text-types"
+
+const options: Options = {
+ renderNode: {
+ [BLOCKS.EMBEDDED_ASSET]: node => {
+ const image = getImage(node.data.target)
+ return image ? (
+
+ ) : null
+ },
+ },
+}
+
+;
+```
+
+Find more details abour [rendering Rich Text in our README file section](./README.md#leveraging-contentful-rich-text-with-gatsby).
+
+### 5. Assets
+
+Assets got simplified a lot and aligned to the [Contentful GraphQL API](https://www.contentful.com/developers/docs/references/graphql/#/reference/schema-generation/assets).
+
+#### Old GraphlQL schema for assets
+
+```graphql
+type ContentfulAsset implements ContentfulInternalReference & Node @dontInfer {
+ file: ContentfulAssetFile
+ title: String
+ description: String
+ sys: ContentfulInternalSys
+}
+
+type ContentfulAssetFile {
+ url: String
+ details: ContentfulAssetFileDetails
+ fileName: String
+ contentType: String
+}
+
+type ContentfulAssetFileDetails {
+ size: Int
+ image: ContentfulAssetFileDetailsImage
+}
+
+type ContentfulAssetFileDetailsImage {
+ width: Int
+ height: Int
+}
+```
+
+#### New GraphlQL schema for assets
+
+```graphql
+type ContentfulAsset implements ContentfulInternalReference & Node @dontInfer {
+ sys: ContentfulInternalSys
+ title: String
+ description: String
+ mimeType: String
+ fileName: String
+ url: String
+ size: Int
+ width: Int
+ height: Int
+}
+```
+
+### 6. Using the Contentful Preview API (CPA)
+
+In version 9, fields marked as required in Contentful are automatically treated as non-nullable in Gatsby's GraphQL schema. This aligns the GraphQL schema more closely with your Contentful content model, enhancing type safety and predictability in your Gatsby project.
+
+However, when using the Contentful Preview API (CPA), you might encounter scenarios where unpublished content doesn't yet fulfill all required fields. To accommodate this, `gatsby-source-contentful` introduces the `enforceRequiredFields` configuration option.
+
+- **Configuration**: By default, `enforceRequiredFields` is `true`, enforcing the non-nullability of required fields. To override this behavior, particularly in development or preview environments, set `enforceRequiredFields` to `false`:
+
+```javascript
+// In your gatsby-config.js
+{
+ resolve: `gatsby-source-contentful`,
+ options: {
+ spaceId: process.env.CONTENTFUL_SPACE_ID,
+ accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
+ enforceRequiredFields: process.env.NODE_ENV !== 'production', // Example condition
+ },
+}
+```
+
+- **Environment Variables**: You can control this setting through environment variables, enabling non-nullable enforcement in production while disabling it in development or preview environments where you might be working with incomplete content.
+
+- **Impact on TypeScript**: For projects using TypeScript, changing the `enforceRequiredFields` setting will alter the generated types. With `enforceRequiredFields` set to `false`, fields that are required in Contentful but may be missing in the preview content will be nullable in the GraphQL schema. As a result, TypeScript users should ensure their code can handle potentially null values in these fields.
+
+## Conclusion and Support
+
+We understand that the changes introduced in `gatsby-source-contentful` v9 are significant. These updates were necessary to resolve architectural issues from the early stages of the plugin and to align it more closely with the evolving capabilities of Contentful and Gatsby.
+
+As we step into this new era, our focus is on ensuring that you experience the most stable, efficient, and future-proof integration when using Contentful with Gatsby. While we've made every effort to simplify the migration process, we also recognize that transitions like these can be challenging.
+
+If you encounter any difficulties or have questions, we encourage you to reach out for support:
+
+- For detailed discussions and sharing experiences: [Join our GitHub Discussion](https://github.com/gatsbyjs/gatsby/discussions/38585).
+- To report bugs or specific issues: [Submit an Issue on GitHub](https://github.com/gatsbyjs/gatsby/issues).
diff --git a/packages/gatsby-source-contentful/README.md b/packages/gatsby-source-contentful/README.md
index 819ff78ad3467..eaaaa34557445 100644
--- a/packages/gatsby-source-contentful/README.md
+++ b/packages/gatsby-source-contentful/README.md
@@ -1,209 +1,416 @@
-# gatsby-source-contentful
+# gatsby-source-contentful v9
+
+`gatsby-source-contentful` is a powerful Gatsby plugin that brings [Contentful's rich content management capabilities](https://www.contentful.com/) into the Gatsby ecosystem. It enables developers to seamlessly integrate Contentful's Content Delivery API with their Gatsby sites, allowing for efficient content retrieval and dynamic site generation based on Contentful's structured content.
Table of contents
-- [gatsby-source-contentful](#gatsby-source-contentful)
- - [Install](#install)
- - [Setup Instructions](#setup-instructions)
- - [How to use](#how-to-use)
- - [Restrictions and limitations](#restrictions-and-limitations)
+- [gatsby-source-contentful v9](#gatsby-source-contentful-v9)
+ - [Core Features](#core-features)
+ - [What's New in Version v9](#whats-new-in-version-v9)
+ - [Restrictions and Limitations](#restrictions-and-limitations)
+ - [Installation](#installation)
+ - [Migration](#migration)
+ - [Configuration Instructions](#configuration-instructions)
- [Using Delivery API](#using-delivery-api)
- [Using Preview API](#using-preview-api)
- - [Offline](#offline)
- - [Configuration options](#configuration-options)
- - [How to query for nodes](#how-to-query-for-nodes)
- - [Query for all nodes](#query-for-all-nodes)
- - [Query for a single node](#query-for-a-single-node)
- - [A note about LongText fields](#a-note-about-longtext-fields)
- - [Duplicated entries](#duplicated-entries)
- - [Query for Assets in ContentType nodes](#query-for-assets-in-contenttype-nodes)
- - [More on Queries with Contentful and Gatsby](#more-on-queries-with-contentful-and-gatsby)
- - [Displaying responsive image with gatsby-plugin-image](#displaying-responsive-image-with-gatsby-plugin-image)
- - [Building images on the fly via `useContentfulImage`](#building-images-on-the-fly-via-usecontentfulimage)
- - [On-the-fly image options:](#on-the-fly-image-options)
- - [Contentful Tags](#contentful-tags)
- - [List available tags](#list-available-tags)
- - [Filter content by tags](#filter-content-by-tags)
- - [Contentful Rich Text](#contentful-rich-text)
- - [Query Rich Text content and references](#query-rich-text-content-and-references)
- - [Rendering](#rendering)
- - [Embedding an image in a Rich Text field](#embedding-an-image-in-a-rich-text-field)
- - [Download assets for static distribution](#download-assets-for-static-distribution)
- - [Enable the feature with the `downloadLocal: true` option.](#enable-the-feature-with-the-downloadlocal-true-option)
- - [Updating Queries for downloadLocal](#updating-queries-for-downloadlocal)
- - [Sourcing From Multiple Contentful Spaces](#sourcing-from-multiple-contentful-spaces)
+ - [Get Started Guide](#get-started-guide)
+ - [Step 0: Our Contentful Data Model](#step-0-our-contentful-data-model)
+ - [Step 1: Creating a Gatsby Page with Contentful Data](#step-1-creating-a-gatsby-page-with-contentful-data)
+ - [Step 2: Dynamic Page Creation with `gatsby-node.js`](#step-2-dynamic-page-creation-with-gatsby-nodejs)
+ - [Step 3: Using Markdown to Render the Content](#step-3-using-markdown-to-render-the-content)
+ - [Further Tutorials and Documentation](#further-tutorials-and-documentation)
+ - [Advanced configuration](#advanced-configuration)
+ - [Offline Mode](#offline-mode)
+ - [Querying Contentful Data in Gatsby](#querying-contentful-data-in-gatsby)
+ - [Overview of Querying Nodes](#overview-of-querying-nodes)
+ - [Querying All Nodes](#querying-all-nodes)
+ - [Querying Single Nodes](#querying-single-nodes)
+ - [Handling Long Text Fields](#handling-long-text-fields)
+ - [Addressing Duplicated Entries Caused by Internationalization](#addressing-duplicated-entries-caused-by-internationalization)
+ - [Querying Assets and Tags within ContentType Nodes](#querying-assets-and-tags-within-contenttype-nodes)
+ - [Advanced Queries with Contentful and Gatsby](#advanced-queries-with-contentful-and-gatsby)
+ - [Working with Images and Contentful](#working-with-images-and-contentful)
+ - [Displaying Responsive Images with `gatsby-plugin-image`](#displaying-responsive-images-with-gatsby-plugin-image)
+ - [Building Images on the Fly with `useContentfulImage`](#building-images-on-the-fly-with-usecontentfulimage)
+ - [Leveraging Contentful Rich Text with Gatsby](#leveraging-contentful-rich-text-with-gatsby)
+ - [Querying Rich Text Content](#querying-rich-text-content)
+ - [Basic Rich Text Rendering](#basic-rich-text-rendering)
+ - [Embedding Images in Rich Text with Gatsby Plugin Image](#embedding-images-in-rich-text-with-gatsby-plugin-image)
+ - [Embedding Entries in Rich Text](#embedding-entries-in-rich-text)
+ - [Further Contentful Rich Text resources](#further-contentful-rich-text-resources)
+ - [Downloading Assets for Static Distribution](#downloading-assets-for-static-distribution)
+ - [Benefits of `downloadLocal`](#benefits-of-downloadlocal)
+ - [Trade-offs](#trade-offs)
+ - [Enabling `downloadLocal`](#enabling-downloadlocal)
+ - [Updating GraphQL Queries](#updating-graphql-queries)
+ - [Troubleshooting](#troubleshooting)
+ - [Multi-Space Sourcing: Example for Sourcing from Multiple Contentful Spaces](#multi-space-sourcing-example-for-sourcing-from-multiple-contentful-spaces)
+ - [Current Limitations](#current-limitations)
-## Install
+## Core Features
-```shell
-npm install gatsby-source-contentful gatsby-plugin-image
-```
+- **Comprehensive Content Integration**: Easily connect your Contentful space with Gatsby, ensuring all content types, references, and custom fields are handled effectively.
+- **Advanced Contentful Features Support**: Harness the full power of Contentful's offerings, including the Image API for creating responsive images and the comprehensive Rich Text feature for versatile content representation.
+- **Efficient Content Synchronization**: Utilizes Contentful's Sync API for incremental updates, significantly speeding up build times.
+- **Aligned Data Structure**: Data in Gatsby closely mirrors the structure of Contentful's GraphQL API, with minimal discrepancies, for a consistent development experience.
+- **Preview API Support**: Leverage Contentful's Preview API for content previews before publishing, with some limitations due to incremental sync constraints. Refer to [Restrictions & Limitations](#restrictions-and-limitations) for more details.
-## Setup Instructions
+### What's New in Version v9
-To get setup quickly with a new site and have Netlify do the heavy lifting, [deploy a new Gatsby Contentful site with just a few clicks on netlify.com](https://app.netlify.com/start/deploy?repository=https://github.com/contentful/starter-gatsby-blog).
+The v9 release of `gatsby-source-contentful` brings significant improvements, focusing on stability and enhancing the developer experience.
-## How to use
+- **Dynamic Schema Generation**: Schema types are now dynamically generated based on the Contentful content model, eliminating site breakages due to empty content fields.
+- **Rich Text and JSON Field Enhancements**: Improved handling of Rich Text and JSON fields for more accurate processing, querying, and rendering.
+- **Schema and Performance Optimizations**: An optimized, leaner schema reduces node count and improves build times, especially for larger projects.
+- **Contentful GraphQL API Alignment**: Enhanced alignment with Contentful's GraphQL API enables more intuitive and consistent querying.
+- **Removed Content Type Name Restrictions**: New naming patterns now allow all content type names, including previously restricted names like `entity` and `reference`.
+- **Updated Field Name Restrictions**: Fields can now be named `contentful_id`, with restrictions now applied to names like `sys`, `contentfulMetadata`, and `linkedFrom`.
+- **Refined Backlinks**: Backlinks/references are now located in the `linkedFrom` field, aligning with Contentful's GraphQL API structure.
+- **Expanded Configuration Options**: Additional [configuration options](#advanced-configuration) provide greater control and customization for your specific project needs.
+- **Updated @link Directive Usage**: The new version of the "gatsby-source-contentful" plugin adopts the @link directive, eliminating the warnings in the build log about the deprecated \_\_\_NODE convention in Gatsby v5.
-First, you need a way to pass environment variables to the build process, so secrets and other secured data aren't committed to source control. We recommend using [`dotenv`][dotenv] which will then expose environment variables. [Read more about `dotenv` and using environment variables here][envvars]. Then we can _use_ these environment variables and configure our plugin.
+For a detailed migration guide and to leverage these improvements, refer to the [Migration Guide](./MIGRATION.md) section.
-## Restrictions and limitations
+## Restrictions and Limitations
-This plugin has several limitations, please be aware of these:
+Please note the following limitations when using `gatsby-source-contentful`:
-1. At the moment, fields that do not have at least one populated instance will not be created in the GraphQL schema. This can break your site when field values get removed. You may workaround with an extra content entry with all fields filled out.
+1. **Environment Access**: Your access token must have permissions for both your desired environment and the `master` environment.
+2. **Preview API Usage**: While using Contentful's Preview API, content may become out-of-sync over time as incremental syncing is not officially supported. Regular cache clearing is recommended.
+3. **Restricted Content Type Names**: Content type names such as `entity` and `reference` are not allowed.
+4. **Field Name Prefixes**: Certain field names are restricted and will be automatically prefixed for consistency, including `id`, `children`, `parent`, `fields`, `internal`, `sys`, `contentfulMetadata`, and `linkedFrom`.
-2. When using reference fields, be aware that this source plugin will automatically create the reverse reference. You do not need to create references on both content types.
+Your revisions to the installation section are clear and informative, effectively guiding users through the initial setup process. The emphasis on `gatsby-plugin-image` for leveraging Gatsby and Contentful's image capabilities is a valuable recommendation. Here's a slightly refined version for clarity and flow:
-3. When working with environments, your access token has to have access to your desired environment and the `master` environment.
+## Installation
+
+To install the plugin, run the following command in your Gatsby project:
+
+```shell
+npm install gatsby-source-contentful gatsby-plugin-image
+```
-4. Using the preview functionality might result in broken content over time, as syncing data on preview is not officially supported by Contentful. Make sure to regularly clean your cache when using Contentful's preview API.
+While `gatsby-plugin-image` is optional, it is highly recommended to fully utilize the benefits of [Gatsby's Image Plugin](https://www.gatsbyjs.com/plugins/gatsby-plugin-image/) and [Contentful's Images API](https://www.contentful.com/developers/docs/references/images-api/), enhancing your site's image handling and performance.
-5. The following content type names are not allowed: `entity`, `reference`
+## Migration
-6. The following field names are restricted and will be prefixed: `children`, `contentful_id`, `fields`, `id`, `internal`, `parent`,
+Please see our [Migration Guide](./MIGRATION.md)
-7. The Plugin has a dependency on `gatsby-plugin-image` which itself has dependencies. Check [Displaying responsive image with gatsby-plugin-image](#displaying-responsive-image-with-gatsby-plugin-image) to determine which additional plugins you'll need to install.
+## Configuration Instructions
### Using Delivery API
+To use Contentful's Delivery API, which retrieves published content, follow these steps:
+
+1. **Set Environment Variables**: Create a file named `.env.development` in your project's root with the following variables:
+
+```sh
+# .env.development
+CONTENTFUL_SPACE_ID=your_space_id
+CONTENTFUL_ACCESS_TOKEN=your_delivery_api_access_token
+CONTENTFUL_ENVIRONMENT=master # or your custom environment
+```
+
+Replace `your_space_id` and `your_delivery_api_access_token` with your actual Contentful Space ID and Delivery API access token. To create access tokens, open the Contentful [UI](https://app.contentful.com/), click `Settings` in the top right corner, and select `API keys`. 2. **Production Environment Variables**: Duplicate the `.env.development` file and rename it to `.env.production`. This ensures your Gatsby site connects to Contentful's Delivery API in both development and production builds.
+
+3. **Plugin Configuration**: In your `gatsby-config.js`, add the `gatsby-source-contentful` plugin with the necessary options:
+
```javascript
-// In your gatsby-config.js
+// gatsby-config.js
+require("dotenv").config({
+ path: `.env.${process.env.NODE_ENV}`,
+})
+
module.exports = {
plugins: [
{
resolve: `gatsby-source-contentful`,
options: {
- spaceId: `your_space_id`,
- // Learn about environment variables: https://gatsby.dev/env-vars
+ spaceId: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
+ environment: process.env.CONTENTFUL_ENVIRONMENT,
},
},
`gatsby-plugin-image`,
+ // ... other plugins ...
],
}
```
### Using Preview API
+To configure the plugin for Contentful's Preview API, which allows you to see unpublished content. This will configure the plugin for Contentful's Preview API for development mode, while maintaining the Contentful Content API for production builds.
+
+1. **Update Environment Variables**: In your `.env.development` file, update `CONTENTFUL_ACCESS_TOKEN` to use an access token for the Contentful Preview API.
+
+2. **Adjust Plugin Configuration**: Modify your `gatsby-config.js` to conditionally set the host based on the environment:
+
```javascript
-// In your gatsby-config.js
+// gatsby-config.js
module.exports = {
plugins: [
{
resolve: `gatsby-source-contentful`,
options: {
- spaceId: `your_space_id`,
- // Learn about environment variables: https://gatsby.dev/env-vars
- accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
- host: `preview.contentful.com`,
+ // ... other plugin options ...
+ host:
+ process.env.NODE_ENV === `production`
+ ? `cdn.contentful.com`
+ : `preview.contentful.com`,
},
},
- `gatsby-plugin-image`,
+ // ... other plugins ...
],
}
```
-### Offline
-
-If you don't have internet connection you can add `export GATSBY_CONTENTFUL_OFFLINE=true` to tell the plugin to fallback to the cached data, if there is any.
-
-### Configuration options
-
-**`spaceId`** [string][required]
-
-Contentful space ID
-
-**`accessToken`** [string][required]
-
-Contentful delivery API key, when using the Preview API use your Preview API key
-
-**`host`** [string][optional] [default: `'cdn.contentful.com'`]
-
-The base host for all the API requests, by default it's `'cdn.contentful.com'`, if you want to use the Preview API set it to `'preview.contentful.com'`. You can use your own host for debugging/testing purposes as long as you respect the same Contentful JSON structure.
-
-**`environment`** [string][optional] [default: 'master']
-
-The environment to pull the content from, for more info on environments check out this [Guide](https://www.contentful.com/developers/docs/concepts/multiple-environments/).
-
-**`downloadLocal`** [boolean][optional] [default: `false`]
-
-Downloads and caches `ContentfulAsset`'s to the local filesystem. Allows you to query a `ContentfulAsset`'s `localFile` field, which is not linked to Contentful's CDN. See [Download assets for static distribution](#download-assets-for-static-distribution) for more information on when and how to use this feature.
-
-**`localeFilter`** [function][optional] [default: `() => true`]
-
-Possibility to limit how many locales/nodes are created in GraphQL. This can limit the memory usage by reducing the amount of nodes created. Useful if you have a large space in Contentful and only want to get the data from one selected locale.
-
-For example, to filter locales on only germany `localeFilter: locale => locale.code === 'de-DE'`
-
-List of locales and their codes can be found in Contentful app -> Settings -> Locales
-
-**`proxy`** [object][optional] [default: `undefined`]
-
-Axios proxy configuration. See the [axios request config documentation](https://github.com/mzabriskie/axios#request-config) for further information about the supported values.
-
-**`useNameForId`** [boolean][optional] [default: `true`]
-
-Use the content's `name` when generating the GraphQL schema e.g. a content type called `[Component] Navigation bar` will be named `contentfulComponentNavigationBar`.
-
-When set to `false`, the content's internal ID will be used instead e.g. a content type with the ID `navigationBar` will be called `contentfulNavigationBar`.
-
-Using the ID is a much more stable property to work with as it will change less often. However, in some scenarios, content types' IDs will be auto-generated (e.g. when creating a new content type without specifying an ID) which means the name in the GraphQL schema will be something like `contentfulC6XwpTaSiiI2Ak2Ww0oi6qa`. This won't change and will still function perfectly as a valid field name but it is obviously pretty ugly to work with.
-
-If you are confident your content types will have natural-language IDs (e.g. `blogPost`), then you should set this option to `false`. If you are unable to ensure this, then you should leave this option set to `true` (the default).
-
-**`pageLimit`** [number][optional] [default: `1000`]
-
-Number of entries to retrieve from Contentful at a time. Due to some technical limitations, the response payload should not be greater than 7MB when pulling content from Contentful. If you encounter this issue you can set this param to a lower number than 100, e.g `50`.
-
-**`assetDownloadWorkers`** [number][optional] [default: `50`]
-
-Number of workers to use when downloading Contentful assets. Due to technical limitations, opening too many concurrent requests can cause stalled downloads. If you encounter this issue you can set this param to a lower number than 50, e.g 25.
-
-**`contentfulClientConfig`** [object][optional] [default: `{}`]
-
-Additional config which will get passed to [Contentfuls JS SDK](https://github.com/contentful/contentful.js#configuration).
-
-Use this with caution, you might override values this plugin does set for you to connect to Contentful.
+## Get Started Guide
-**`enableTags`** [boolean][optional] [default: `false`]
+This guide is a straightforward path to see how Gatsby and Contentful can bring your content to life, ideal for those just starting out.
+
+### Step 0: Our Contentful Data Model
+
+Before we jump into Gatsby, let's define our Contentful data model. We're working with a Page content type, which includes the following fields:
+
+- **Title**: The headline of your page.
+- **Slug**: A unique URL segment based on the page's title.
+- **Content**: The primary content or body of your page.
+
+### Step 1: Creating a Gatsby Page with Contentful Data
+
+Begin by fetching data from a single entry of our `Page` content type and displaying it on a Gatsby page.
+
+1. **Create a Page Component**: In your `src/pages` directory, add a new file, such as `contentful-page.js`.
+2. **Load data with GraphQL**: Use Gatsby's page query to retrieve data from Contentful. For a `Page` content type with `title`, `slug`, and `content` fields.
+3. **Render data with Gatsby**: The result of the page query is available through the `data` prop.
+
+ Your file should look like this:
+
+ ```jsx
+ // src/pages/contentful-page.js
+ import React from "react"
+ import { graphql } from "gatsby"
+
+ export default function ContentfulPage({ data }) {
+ return (
+
+
{data.contentfulPage.title}
+
{data.contentfulPage.content.raw}
+
+ )
+ }
+
+ export const pageQuery = graphql`
+ query {
+ contentfulPage(slug: { eq: "your-slug" }) {
+ title
+ content {
+ raw
+ }
+ }
+ }
+ `
+ ```
+
+ **Important:** Replace `"your-slug"` with the actual slug of the page you want to display. At this stage, the content will be displayed as raw text.
+
+### Step 2: Dynamic Page Creation with `gatsby-node.js`
+
+Now, create a subpage for each `Page` content type entry.
+
+1. **Rename and Move Page Component**: Rename `src/pages/contentful-page.js` to `src/templates/contentful-page-template.js`. This file becomes your template for dynamically created pages.
+
+2. **Implement Dynamic Page Creation**: In your `gatsby-node.js`, use the `createPages` API to dynamically generate pages for each entry of the `Page` content type from Contentful.
+
+ ```javascript
+ // gatsby-node.js
+ const path = require("path")
+
+ exports.createPages = async ({ graphql, actions }) => {
+ const { createPage } = actions
+
+ // Query data of all Contentful pages
+ const result = await graphql(`
+ query {
+ allContentfulPage {
+ edges {
+ node {
+ slug
+ }
+ }
+ }
+ }
+ `)
+
+ // Dynamically create a page for each Contentful entry, passing the slug for URL generation
+ result.data.allContentfulPage.edges.forEach(({ node }) => {
+ createPage({
+ path: node.slug,
+ component: path.resolve(`./src/templates/contentful-page-template.js`),
+ context: {
+ slug: node.slug,
+ },
+ })
+ })
+ }
+ ```
+
+ This code will create a new page for each `Page` entry in Contentful, using the slug as the page path.
+
+3. **Update Template with Slug Filter**: In your template file (`src/templates/contentful-page-template.js`), update the GraphQL query to filter by the provided slug.
+
+ ```jsx
+ // src/templates/contentful-page-template.js
+ import React from "react"
+ import { graphql } from "gatsby"
+
+ export default function ContentfulPageTemplate({ data }) {
+ return (
+
+
{data.contentfulPage.title}
+
{data.contentfulPage.content.raw}
+
+ )
+ }
+
+ export const pageQuery = graphql`
+ query contentfulPageQuery($slug: String!) {
+ contentfulPage(slug: { eq: $slug }) {
+ title
+ content {
+ raw
+ }
+ }
+ }
+ `
+ ```
+
+ **Hint:** The `$slug` variable is provided via the context in `gatsby-node.js`.
+
+### Step 3: Using Markdown to Render the Content
+
+> **Note on Markdown**: Markdown is a lightweight markup language that allows you to write content using an easy-to-read, easy-to-write plain text format, which is then converted into HTML. The `gatsby-transformer-remark` plugin enables this transformation within Gatsby. [Learn more about Markdown](https://www.markdownguide.org/getting-started/).
+
+To add Markdown rendering to your Contentful content:
+
+1. **Install `gatsby-transformer-remark`**: Run `npm install gatsby-transformer-remark`.
+
+2. **Update Gatsby Config**: Add the `gatsby-transformer-remark` plugin to your `gatsby-config.js`.
+
+ ```javascript
+ // gatsby-config.js
+ module.exports = {
+ plugins: [
+ // ... other plugins ...
+ `gatsby-transformer-remark`,
+ ],
+ }
+ ```
+
+3. **Adjust the Template for Markdown**: Modify your template (`src/templates/contentful-page-template.js`) to render the markdown content. Update the GraphQL query and rendering logic to handle the transformed markdown.
+
+ ```jsx
+ // src/templates/contentful-page-template.js
+ import React from "react"
+ import { graphql } from "gatsby"
+
+ export default function ContentfulPageTemplate({ data }) {
+ return (
+
+
{data.contentfulPage.title}
+ {/* Here we render the generated HTML */}
+
+
+ )
+ }
+
+ export const pageQuery = graphql`
+ query contentfulPageQuery($slug: String!) {
+ contentfulPage(slug: { eq: $slug }) {
+ title
+ content {
+ # The childMarkdownRemark is created by gatsby-transformer-remark
+ childMarkdownRemark {
+ html
+ }
+ }
+ }
+ }
+ `
+ ```
+
+You now have a foundational grasp of `gatsby-source-contentful` and its potential to power your Gatsby projects with Contentful's dynamic content.
+
+### Further Tutorials and Documentation
+
+For more in-depth exploration and advanced concepts, check out these valuable resources:
+
+- **Contentful Gatsby Starter Guide**: A comprehensive guide to kickstart your Gatsby projects with Contentful. [Explore the guide](https://www.contentful.com/gatsby-starter-guide/).
+- **Contentful Blog Example**: An example Gatsby project integrated with Contentful, ideal for blog-like websites. [View on GitHub](https://github.com/contentful/starter-gatsby-blog).
+- **Contentful & Gatsby in 5 Minutes**: A quick tutorial for setting up a Gatsby site with Contentful. [Start learning](https://www.contentful.com/help/gatsbyjs-and-contentful-in-five-minutes/).
+- **Gatsby's Quick Guide to Contentful**: Gatsby's own guide to integrating Contentful for content management. [Read the guide](https://www.gatsbyjs.com/blog/a-quick-start-guide-to-gatsby-and-contentful/).
+- **Video Tutorial by Khaled and Jason**: Though a bit dated, this informative video remains a great resource for understanding Gatsby and Contentful integration. [Watch on YouTube](https://www.youtube.com/watch?v=T9hLWjIN-pY).
+
+## Advanced configuration
+
+Here's the revised table for the `gatsby-source-contentful` configuration options, including the suggested updates and corrections:
+
+| Option | Type | Default Value | Description |
+| ---------------------------------- | ------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `spaceId` | string | (required) | Your Contentful space ID. |
+| `accessToken` | string | (required) | Access token for the Content Delivery API. Use the Preview API key for the Contentful Preview API. |
+| `host` | string | `cdn.contentful.com` | Base host for API requests. Default is for the Delivery API; use `preview.contentful.com` for the Preview API. Custom hosts can be used for debugging/testing. |
+| `environment` | string | `master` | The environment in Contentful to pull content from. [Guide](https://www.contentful.com/developers/docs/concepts/multiple-environments/) |
+| `downloadLocal` | boolean | `false` | Downloads and caches `ContentfulAsset` to the local filesystem. Use `localFile` to access the local files. See [Download assets for static distribution](#download-assets-for-static-distribution) |
+| `useNameForId` | boolean | `true` | Determines whether the content's `name` or internal ID is used for generating GraphQL schema types. Using `name` can make type names more readable but can be unstable if names change. Using the internal ID ensures stability as IDs are less likely to change, but may result in less readable types, especially when auto-generated. |
+| `enforeRequiredFields` (New in v9) | boolean | `true` | Fields required in Contentful will also be required in Gatsby. If you are using Contentfuls Preview API (CPA), you may want to disable this conditionally. |
+
+| `enableMarkdownDetection` (New in v9) | boolean | `true` | Assumes all long text fields in Contentful are markdown fields. Requires `gatsby-transformer-remark`. Can be a performance issue in large projects. Set to `false` and use `markdownFields` to specify markdown fields. |
+| `markdownFields` (New in v9) | array | `[]` | Specify which fields contain markdown content. Effective only when `enableMarkdownDetection` is `false`. Format: array of pairs (content type ID and array of field IDs). Example: `[["product", ["description", "summary"]], ["otherContentTypeId", ["someMarkdownFieldId"]]]` |
+| `contentTypePrefix` (Renamed in v9) | string | `ContentfulContentType` | Prefix for the generated GraphQL types. Formerly known as `typePrefix`. |
+| `localeFilter` | function | `() => true` | Function to filter which locales/nodes are created in GraphQL, reducing memory usage by limiting nodes. |
+| `contentTypeFilter` | function | `() => true` | Function to filter which contentType/nodes are created in GraphQL, reducing memory usage by limiting nodes. |
+| `pageLimit` | number | `1000` | Number of entries to retrieve from Contentful at a time. Adjust if the payload size exceeds 7MB. |
+| `assetDownloadWorkers` | number | `50` | Number of workers to use when downloading Contentful assets. Adjust to prevent stalled downloads due to too many concurrent requests. |
+| `proxy` | object | (none) | Axios proxy configuration. See the [axios request config documentation](https://github.com/mzabriskie/axios#request-config) for further information about the supported values. |
+| `contentfulClientConfig` | object | `{}` | Additional config passed to Contentful's JS SDK. Use with caution to avoid overriding plugin-set values. |
-Enable the new [tags feature](https://www.contentful.com/blog/2021/04/08/governance-tagging-metadata/). This will disallow the content type name `tags` till the next major version of this plugin.
+### Offline Mode
-Learn how to use them at the [Contentful Tags](#contentful-tags) section.
+For development without internet access, you can enable the Offline Mode of `gatsby-source-contentful`. Simply set the environment variable `GATSBY_CONTENTFUL_OFFLINE`, for example by adding `GATSBY_CONTENTFUL_OFFLINE=true` to your `.env.development`. This mode uses cached data from previous builds.
-**`contentTypeFilter`** [function][optional] [default: () => true]
+**Note:** Changing `gatsby-config.js` or `package.json` will clear the cache, requiring internet access for the next build.
-Possibility to limit how many contentType/nodes are created in GraphQL. This can limit the memory usage by reducing the amount of nodes created. Useful if you have a large space in Contentful and only want to get the data from certain content types.
+## Querying Contentful Data in Gatsby
-For example, to exclude content types starting with "page" `contentTypeFilter: contentType => !contentType.sys.id.startsWith('page')`
+### Overview of Querying Nodes
-**`typePrefix`** [string][optional] [default: `Contentful`]
+In Gatsby, Contentful provides three primary node types for querying: `Asset`, `ContentType`, and `Tag`. These nodes allow you to access various types of content from your Contentful space:
-Prefix for the type names created in GraphQL. This can be used to avoid conflicts with other plugins, or if you want more than one instance of this plugin in your project. For example, if you set this to `Blog`, the type names will be `BlogAsset` and `allBlogAsset`.
-
-## How to query for nodes
-
-Two standard node types are available from Contentful: `Asset` and `ContentType`.
-
-`Asset` nodes will be created in your site's GraphQL schema under `contentfulAsset` and `allContentfulAsset`.
-
-`ContentType` nodes are a little different - their exact name depends on what you called them in your Contentful data models. The nodes will be created in your site's GraphQL schema under `contentful${entryTypeName}` and `allContentful${entryTypeName}`.
-
-In all cases querying for nodes like `contentfulX` will return a single node, and nodes like `allContentfulX` will return all nodes of that type.
-
-### Query for all nodes
-
-You might query for **all** of a type of node:
+- `Asset` nodes are accessible under `contentfulAsset` and `allContentfulAsset`.
+- `ContentType` nodes, following the new naming pattern, are available as `contentfulContentType${entryTypeName}` and `allContentfulContentType${entryTypeName}`.
+- `Tag` nodes are found under `contentfulTag` and `allContentfulTag`.
+
+Querying `contentfulX` retrieves a single node, while `allContentfulX` fetches all nodes of that type.
+
+### Querying All Nodes
+
+To query all nodes of a specific type:
```graphql
{
allContentfulAsset {
nodes {
- contentful_id
+ sys {
+ id
+ }
title
description
}
@@ -211,133 +418,91 @@ You might query for **all** of a type of node:
}
```
-You might do this in your `gatsby-node.js` using Gatsby's [`createPages`](https://gatsbyjs.com/docs/node-apis/#createPages) Node API.
+### Querying Single Nodes
-### Query for a single node
-
-To query for a single `image` asset with the title `'foo'` and a width of 1600px:
-
-```javascript
-export const assetQuery = graphql`
- {
- contentfulAsset(title: { eq: "foo" }) {
- contentful_id
- title
- description
- file {
- fileName
- url
- contentType
- details {
- size
- image {
- height
- width
- }
- }
- }
- }
- }
-`
-```
-
-To query for a single `CaseStudy` node with the short text properties `title` and `subtitle`:
+For querying a single node:
```graphql
- {
- contentfulCaseStudy(filter: { title: { eq: 'bar' } }) {
- title
- subtitle
+{
+ contentfulAsset(title: { eq: "foo" }) {
+ sys {
+ id
}
+ title
+ description
+ // ... other fields ...
}
-```
-
-You might query for a **single** node inside a component in your `src/components` folder, using [Gatsby's `StaticQuery` component](https://www.gatsbyjs.com/docs/static-query/).
-
-#### A note about LongText fields
-
-On Contentful, a "Long text" field uses Markdown by default. The field is exposed as an object, while the raw Markdown is exposed as a child node.
+}
-```graphql
{
- contentfulCaseStudy {
- body {
- body
- }
+ contentfulContentTypeCaseStudy(slug: { eq: "example-slug" }) {
+ title
+ slug
+ // ... other fields ...
}
}
```
-Unless the text is Markdown-free, you cannot use the returned value directly. In order to handle the Markdown content, you must use a transformer plugin such as [`gatsby-transformer-remark`](https://www.gatsbyjs.com/plugins/gatsby-transformer-remark/). The transformer will create a `childMarkdownRemark` on the "Long text" field and expose the generated html as a child node:
+### Handling Long Text Fields
+
+Long text fields often use Markdown. For Markdown processing, use `gatsby-transformer-remark`. See [Step 3 in the Getting Started Guide](#step-3-using-markdown-to-render-the-content).
```graphql
{
- contentfulCaseStudy {
+ contentfulContentTypeCaseStudy {
body {
+ raw # Raw Markdown
childMarkdownRemark {
- html
+ html # HTML from Markdown
}
}
}
}
```
-You can then insert the returned HTML inline in your JSX:
-
-```jsx
-
-```
-
-#### Duplicated entries
+### Addressing Duplicated Entries Caused by Internationalization
-When Contentful pulls the data, all localizations will be pulled. Therefore, if you have a localization active, it will duplicate the entries. Narrow the search by filtering the query with `node_locale` filter:
+Due to Contentful's data retrieval process, each localization creates a new node, potentially leading to duplicates. Include the `node_locale` in queries to avoid this:
```graphql
{
- allContentfulCaseStudy(filter: { node_locale: { eq: "en-US" } }) {
- edges {
- node {
- id
- slug
- title
- subtitle
- body {
- body
- }
- }
- }
+ allContentfulContentTypeCaseStudy(
+ filter: { node_locale: { eq: "en-US" } }
+ ) {
+ # ... query fields ...
}
}
```
-### Query for Assets in ContentType nodes
-
-More typically your `Asset` nodes will be mixed inside of your `ContentType` nodes, so you'll query them together. All the same formatting rules for `Asset` and `ContentType` nodes apply.
+### Querying Assets and Tags within ContentType Nodes
-To get **all** the `CaseStudy` nodes with `ShortText` fields `id`, `slug`, `title`, `subtitle`, `LongText` field `body` and `heroImage` asset field, we use `allContentful${entryTypeName}` to return all instances of that `ContentType`:
+Assets and tags are typically queried alongside ContentType nodes:
```graphql
{
- allContentfulCaseStudy {
+ allContentfulContentTypeCaseStudy {
edges {
node {
- id
+ sys {
+ id
+ }
slug
title
subtitle
body {
body
}
+ # Asset
heroImage {
title
description
gatsbyImageData(layout: CONSTRAINED)
- # Further below in this doc you can learn how to use these response images
+ }
+ # Tags
+ contentfulMetadata {
+ tags {
+ name
+ }
}
}
}
@@ -345,163 +510,156 @@ To get **all** the `CaseStudy` nodes with `ShortText` fields `id`, `slug`, `titl
}
```
-### More on Queries with Contentful and Gatsby
+### Advanced Queries with Contentful and Gatsby
-It is strongly recommended that you take a look at how data flows in a real Contentful and Gatsby application to fully understand how the queries, Node.js functions and React components all come together. Check out the example site at
-[using-contentful.gatsbyjs.org](https://using-contentful.gatsbyjs.org/).
+Explore your GraphQL schema and data with GraphiQL, a browser-based IDE available at http://localhost:8000/\_\_\_graphql during Gatsby development. This tool is essential for experimenting with queries and understanding the Contentful data structure in your project.
-## Displaying responsive image with gatsby-plugin-image
+## Working with Images and Contentful
-To use it:
+Contentful and Gatsby together provide a powerful combination for managing and displaying images. Leveraging Contentful's Images API and Gatsby's image handling capabilities, you can create responsive, optimized images for your site. Here's how to get the most out of this integration:
-1. Install the required plugins:
+Please check out the [Readme](https://www.gatsbyjs.com/plugins/gatsby-plugin-image/) and [Reference Guide](https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-plugin-image/) of `gatsby-plugin-image` for more detailed information about the possibilities of it.
-```shell
-npm install gatsby-plugin-image gatsby-plugin-sharp
-```
+### Displaying Responsive Images with `gatsby-plugin-image`
-2. Add the plugins to your `gatsby-config.js`:
+1. **Install Required Plugins**: If you haven't already, install `gatsby-plugin-image`:
-```javascript
-module.exports = {
- plugins: [
- `gatsby-plugin-sharp`,
- `gatsby-plugin-image`,
- // ...etc
- ],
-}
-```
+ ```shell
+ npm install gatsby-plugin-image
+ ```
-3. You can then query for image data using the new `gatsbyImageData` resolver:
+2. **Configure Gatsby**: If you haven't already, add it to your `gatsby-config.js`:
-```graphql
-{
- allContentfulBlogPost {
- nodes {
- heroImage {
- gatsbyImageData(layout: FULL_WIDTH)
- }
+ ```javascript
+ module.exports = {
+ plugins: [
+ `gatsby-plugin-image`,
+ // ... other plugins ...
+ ],
}
- }
-}
-```
+ ```
-4. Your query will return a dynamic image. Check the [documentation of gatsby-plugin-image](https://www.gatsbyjs.com/plugins/gatsby-plugin-image/#dynamic-images) to learn how to render it on your website.
+3. **Querying Images**: Use the `gatsbyImageData` resolver in your GraphQL queries to fetch image data on any Contentful Asset you have linked in a Contentful Field:
-Check the [Reference Guide of gatsby-plugin-image](https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-plugin-image/) to get a deeper insight on how this works.
+ ```graphql
+ query contentfulPageQuery($slug: String!) {
+ contentfulPage(slug: { eq: $slug }) {
+ # Your other fields
+ heroImage {
+ gatsbyImageData(layout: FULL_WIDTH)
+ }
+ }
+ }
+ ```
+
+4. **Rendering Images**: Use the `` component from `gatsby-plugin-image` to display the images in your components:
+
+ ```jsx
+ export default function ContentfulPageTemplate({ data }) {
+ return (
+
+ {/* Render logic of the rest of your content type */}
+
+
+ )
+ }
+ ```
-### Building images on the fly via `useContentfulImage`
+ Replace `imageData` with the image data fetched from your GraphQL query.
-With `useContentfulImage` and the URL to the image on the Contentful Image API you can create dynamic images on the fly:
+ Certainly! Here's a rephrased version of the fifth step for using images with `gatsby-plugin-image`, aligned with the style and tone of the other steps:
-```js
-import { GatsbyImage } from "gatsby-plugin-image"
-import * as React from "react"
-import { useContentfulImage } from "gatsby-source-contentful/hooks"
-
-const MyComponent = () => {
- const dynamicImage = useContentfulImage({
- image: {
- url: "//images.ctfassets.net/k8iqpp6u0ior/3BSI9CgDdAn1JchXmY5IJi/f97a2185b3395591b98008647ad6fd3c/camylla-battani-AoqgGAqrLpU-unsplash.jpg",
- width: 2000,
- height: 1000,
- },
- })
+5. **Optimize Image Delivery Speed**: To enhance the performance of images served from Contentful's Image CDN, consider adding `preconnect` and `dns-prefetch` metatags to your website. These tags help accelerate the process of connecting to the CDN, leading to faster image rendering.
- return
-}
-```
+ Implement this optimization using Gatsby's [Head API](https://www.gatsbyjs.com/docs/reference/built-in-components/gatsby-head/) in your global layout component:
-#### On-the-fly image options:
+ ```jsx
+ export const Head = () => (
+ <>
+
+
+ >
+ )
+ ```
-This hook accepts the same parameters as the `gatsbyImageData` field in your GraphQL queries. They are automatically translated to the proper Contentful Image API parameters.
+ By incorporating these tags, you ensure a quicker DNS resolution and connection establishment with the Contentful Image CDN.
-Here are the most relevant ones:
+### Building Images on the Fly with `useContentfulImage`
-- width: maximum 4000
-- height: maximum 4000
-- toFormat: defaults to the actual image format
-- jpegProgressive: set to `progressive` to enable
-- quality: between 1 and 100
-- resizingBehavior: https://www.contentful.com/developers/docs/references/images-api/#/reference/resizing-&-cropping/change-the-resizing-behavior
-- cropFocus: https://www.contentful.com/developers/docs/references/images-api/#/reference/resizing-&-cropping/specify-focus-area
-- background: background color in format `rgb:9090ff`
-- cornerRadius: https://www.contentful.com/developers/docs/references/images-api/#/reference/resizing-&-cropping/crop-rounded-corners-&-circle-elipsis
+For more dynamic image handling, `useContentfulImage` allows you to build images on the fly using Contentful's Image API:
-## [Contentful Tags](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/content-tags)
+1. **Use the Hook**: In your component, use the `useContentfulImage` hook to create an image dynamically:
-You need to set the `enableTags` flag to `true` to use this new feature.
+ ```jsx
+ import { useContentfulImage } from "gatsby-source-contentful"
-### List available tags
+ const MyComponent = () => {
+ const dynamicImage = useContentfulImage({
+ image: {
+ url: "URL_of_the_image_on_Contentful",
+ width: 800,
+ height: 600,
+ },
+ })
-This example lists all available tags. The sorting is optional.
+ return
+ }
+ ```
-```graphql
-query TagsQuery {
- allContentfulTag(sort: { fields: contentful_id }) {
- nodes {
- name
- contentful_id
- }
- }
-}
-```
+ Replace `URL_of_the_image_on_Contentful` with the actual URL of the image you want to display. The hook accepts the same parameters as the `gatsbyImageData` field in your GraphQL queries.
-### Filter content by tags
+2. **Customizing Images**: You can customize the images using various options such as `width`, `height`, `format`, `quality`, etc. These options align with [Contentful's Image API parameters](https://www.contentful.com/developers/docs/references/images-api/#/reference).
-This filters content entries that are tagged with the `numberInteger` tag.
+Can you help me to improve this section of my readme for a new version of the gatsby plugin gatsby-source-contentful?
-```graphql
-query FilterByTagsQuery {
- allContentfulNumber(
- sort: { fields: contentful_id }
- filter: {
- metadata: {
- tags: { elemMatch: { contentful_id: { eq: "numberInteger" } } }
- }
- }
- ) {
- nodes {
- title
- integer
- }
- }
-}
-```
+## Leveraging Contentful Rich Text with Gatsby
+
+The integration of Contentful's Rich Text fields in the `gatsby-source-contentful` plugin allows for a dynamic and rich content experience in Gatsby projects. This feature supports a variety of content compositions, including text, assets, and embedded entries, offering a powerful tool for developers and content creators alike.
-## [Contentful Rich Text](https://www.contentful.com/developers/docs/concepts/rich-text/)
+The latest `gatsby-source-contentful` updates enhance this integration, closely aligning with Contentful's GraphQL API and the `@contentful/rich-text-react-renderer`.
-Rich Text feature is supported in this source plugin, you can use the following query to get the JSON output:
+### Querying Rich Text Content
-**Note:** In our example Content Model the field containing the Rich Text data is called `bodyRichText`. Make sure to use your field name within the Query instead of `bodyRichText`
+The Rich Text field in Gatsby reflects the structure provided by Contentful's API.
-### Query Rich Text content and references
+Example GraphQL query for a Rich Text field:
```graphql
-query pageQuery($id: String!) {
- contentfulBlogPost(id: { eq: $id }) {
- title
- slug
- # This is the rich text field, the name depends on your field configuration in Contentful
- bodyRichText {
- raw
- references {
- ... on ContentfulAsset {
- # You'll need to query contentful_id in each reference
- contentful_id
- __typename
- fixed(width: 1600) {
- width
- height
- src
- srcSet
+# This is a extended example, see below for a step-by-step introduction
+{
+ allContentfulContentTypePage {
+ nodes {
+ content {
+ json
+ links {
+ assets {
+ block {
+ sys {
+ id
+ } # Required for link resolution
+ gatsbyImageData(width: 200)
+ }
+ # There is also hyperlink {}
+ }
+ entries {
+ block {
+ # Generic props and fields
+ __typename
+ sys {
+ id
+ type
+ } # Required for link resolution
+ # Content Type specific fields
+ ... on ContentfulContentTypeSomethingElse {
+ body
+ }
+ }
+ # Include inline and hyperlink as needed
}
- }
- ... on ContentfulBlogPost {
- contentful_id
- __typename
- title
- slug
}
}
}
@@ -509,143 +667,215 @@ query pageQuery($id: String!) {
}
```
-### Rendering
+This query retrieves Rich Text JSON content (`json`) along with linked assets and entries, now organized under `assets` and `entries` within the `links` object.
+
+### Basic Rich Text Rendering
+
+To render Rich Text, use `gatsby-source-contentful`'s `renderRichText` function with a custom rendering setup for various embedded entries and assets:
```jsx
+// src/templates/contentful-page-template.js
+import { renderRichText } from "gatsby-source-contentful"
import { BLOCKS, MARKS } from "@contentful/rich-text-types"
-import { renderRichText } from "gatsby-source-contentful/rich-text"
-const Bold = ({ children }) => {children}
-const Text = ({ children }) =>
{children}
-
-const options = {
+// This factory provides a dynamic configuration for each rich text rendering
+const makeOptions = ({ assetBlockMap, entryBlockMap, entryInlineMap }) => ({
renderMark: {
- [MARKS.BOLD]: text => {text},
+ [MARKS.BOLD]: text => {text},
},
renderNode: {
- [BLOCKS.PARAGRAPH]: (node, children) => {children},
- [BLOCKS.EMBEDDED_ASSET]: node => {
- return (
- <>
-