Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

contentful-hugo

Package Overview
Dependencies
Maintainers
1
Versions
106
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

contentful-hugo

Node module that pulls data from Contentful and turns it into markdown files for Hugo. Can be used with other Static Site Generators, but has some Hugo specific features.

  • 1.14.1
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
201
increased by50%
Maintainers
1
Weekly downloads
 
Created
Source

Contentful Hugo

Codacy Badge

This is a simple Node.js CLI tool that pulls data from Contentful CMS and turns it into Markdown or YAML files for use with a static site generator. It can be used with any static site generator that uses Markdown with YAML frontmatter, but it has some features that are specific to Hugo. It also includes a simple Express server that can can recieve webhooks from Contentful to retrigger get and delete commands (useful when running a preview environment).

Features

  • Markdown and YAML output
  • Singleton support
  • Rich text field support
  • Multilingual Support
  • Default shortcodes for rich text content
  • Asset field resolution
  • Customizable linked entry resolution
  • Content Filters
  • Supports the Content Preview API
  • Field name and field value overrides
  • Server mode to recieve webhook triggers from Contentful (BETA)

Table of Contents

Prerequisites

Install Node.js

Installation

with NPM

npm install contentful-hugo

with Yarn

yarn add contentful-hugo

Usage

Terminal Commands

Complete configuration then run the following command(s) in the terminal

When Installed Globally
## initialize the directory
contentful-hugo --init

## fetch content from contentful
contentful-hugo [flags]
When Installed Locally
npx contentful-hugo --init
npx contentful-hugo [flags]

Flags

flagaliasesdescription
--initInitialize the directory. Generates a config file and default shortcodes for Contentful rich text fields
--preview-PRuns in preview mode, which pulls both published and unpublished entries from Contentful
--wait-WWait for the specified number of milliseconds before pulling data from Contentful.
--config-CSpecify the path to a config file.
--server-SRun in server mode to recieve webhooks from Contentful (BETA)
--portSpecify port for server mode (Default 1414)
--helpShow help
--versionShow version number
Multiple Flags Example
contentful-hugo --wait=2000 --preview --config="my_custom_config.js"

# or

contentful-hugo --wait 2000 --preview --config my_custom_config.js

Example Package.json

{
  "name": "my-hugo-project",
  "scripts": {
    "prestart": "contentful-hugo",
    "start": "hugo server",
    "prebuild": "contentful-hugo",
    "build": "hugo --minify"
  }
}

In this example when you run npm start it will first use contentful-hugo to pull Contentful data then start hugo server. In the same way when you do the command npm run build it will first use contentful-hugo to pull Contentful data then run hugo --minify to build a minified version of your hugo site.

Error Messages

Trying to use this package before completing configuration will return an error in the console

Error: There is an error in your config file, or it does't exits.
Check your config for errors or run "contentful-hugo --init" to create a config file.

Configuration

Environment Variables

By default this library will look for the following environment variables. You can also override these values with the config file. (See config)

  • CONTENTFUL_SPACE
  • CONTENTFUL_TOKEN
  • CONTENTFUL_PREVIEW_TOKEN

.env File:

To declare these environment variables create a .env file in the root directory of your project.

CONTENTFUL_SPACE = '<space-id>'
CONTENTFUL_TOKEN = '<content-accessToken>'

# optional but required for preview mode
CONTENTFUL_PREVIEW_TOKEN = '<preview-accessToken>'

You can also declare the environment variables in the command line

Powershell:

$env:CONTENTFUL_SPACE="<contentful_space_id>"
$env:CONTENTFUL_TOKEN="<contentful_acessToken>"
$env:CONTENTFUL_PREVIEW_TOKEN="<contentful_preview_accessToken>"

Bash:

export CONTENTFUL_SPACE="<contentful_space_id>"
export CONTENTFUL_TOKEN="<contentful_accessToken>"
export CONTENTFUL_PREVIEW_TOKEN="<contentful_preview_accessToken>"

Config File

Before getting started, you will need to create a config file in the root of your repository. Contentful-hugo by default will search for the following files as a config.

  • contentful-hugo.config.js
  • contentful-hugo.config.yaml
  • contentful-hugo.yaml
  • contentful-settings.yaml

You can also specify a custom config file using the --config flag. (Javascript or YAML config files are the only currently accepted filetypes)

Example Javascript Config
// contentful-hugo.config.js

module.exports = {
    // fetches from default locale if left blank
    locales: ['en-US', 'fr-FR'],

    contentful: {
        // defaults to CONTENTFUL_SPACE env variable
        space: 'space-id',
        // defaults to CONTENTFUL_TOKEN env variable
        token: 'content-deliver-token',
        // defaults to CONTENTFUL_PREVIEW_TOKEN env variable
        previewToken: 'content-preview-token',
        // defaults to "master"
        environment: 'master',
    },

    singleTypes: [
        {
            id: 'homepage',
            directory: 'content',
            fileName: '_index',
            fileExtension: 'md',
        },
        {
            id: 'siteSettings',
            directory: 'data',
            fileName: 'settings',
            fileExtension: 'yaml',
        },
    ],

    repeatableTypes: [
        {
            id: 'posts',
            directory: 'content/posts',
            fileExtension: 'md',
            mainContent: 'content',
            resolveEntries: [
                {
                    field: 'categories',
                    resolveTo: 'fields.slug',
                },
                {
                    field: 'author',
                    resolveTo: 'fields.name',
                },
                {
                    field: 'relatedPosts',
                    resolveTo: 'sys.id',
                },
            ],
        },
        {
            id: 'seoFields',
            isHeadless: true,
            directory: 'content/seo-fields',
        },
        {
            id: 'reviews',
            directory: 'content/reviews',
            mainContent: 'reviewBody',
        },
        {
            id: 'category',
            directory: 'content/categories',
            isTaxonomy: true, // Experimental Feature
        },
    ],
};
Example YAML Config
# contentful-hugo.config.yaml

locales: # fetches from default locale if left blank
    - en-US
    - fr-FR

contentful:
    space: 'space-id' # defaults to CONTENTFUL_SPACE env variable
    token: 'content-deliver-token' # defaults to  CONTENTFUL_TOKEN env variable
    previewToken: 'content-preview-token' # defaults to  CONTENTFUL_PREVIEW_TOKEN env variable
    environment: 'master' # defaults to "master"

singleTypes:
    # fetches only the most recently updated entry in a particular content type
    # Generated file will be named after the fileName setting

    - id: homepage
      directory: content
      fileName: _index
      fileExtension: md

    - id: siteSettings
      directory: data
      fileName: settings
      fileExtension: yaml

repeatableTypes:
    # fetches all the entries of a content type and places them in a directory.
    # Generated files will be named after their Entry ID in Contentful.

    - id: posts
      directory: content/posts
      fileExtension: md
      mainContent: content
      resolveEntries: # resolves a reference or asset field to a specific property
          - field: categories
            resolveTo: fields.slug
          - field: author
            resolveTo: fields.name
          - field: relatedPosts
            resolveTo: sys.id

    - id: seoFields
      isHeadless: true
      directory: content/seo-fields

    - id: reviews
      directory: content/reviews
      mainContent: reviewBody

    - id: staff
      isHeadless: true
      directory: content/staff

    - id: category
      directory: content/categories
      isTaxonomy: true # Experimental Feature
Config Fields
Contentful Options
fieldrequireddescription
spaceoptionalContentful Space ID (Defaults to CONTENTFUL_SPACE environment variable if not set)
tokenoptionalContent delivery token (Defaults to CONTENTFUL_TOKEN environment variable if not set)
previewTokenoptionalContent preview token (Defaults to CONTENTFUL_PREVIEW_TOKEN environment variable if not set)
environmentoptionalContentful environment ID (Defaults to "master" if not set)
Single Type Options
fieldrequireddescription
idrequiredContentful content type ID
directoryrequiredDirectory where you want the file(s) to be generated
fileNamerequiredName of the file generated
fileExtensionoptionalCan be "md", "yml", or "yaml" (defaults to "md")
mainContentoptionalField ID for field you want to be the main Markdown content. (Can be a markdown, richtext, or string field)
typeoptionalManually set value for "type" field in the frontmatter (see hugo docs)
resolveEntriesoptionalResolve the specified reference fields and/or asset fields to one of it's properties specified with the resolveTo parameter
overridesoptionalDo custom overrides for field values or field names
filtersoptionalAccepts an object of Contentful search parameters to filter results. See Contentful docs
ignoreLocalesoptionalIgnore localization settings and only pull from the default locale (defaults to false)
Repeatable Type Options
fieldrequireddescription
idrequiredContentful content type ID
directoryrequiredDirectory where you want the files to be generated
fileExtensionoptionalCan be "md", "yml", or "yaml" (defaults to "md")
isHeadlessoptionalTurns all entries in a content type into headless leaf bundles (see hugo docs). Cannot be set to true when isTaxonomy is set to true.
isTaxonomy (Experimental)optionalOrganize entries in file structure allowing for custom taxonomy metadata (see hugo docs). Cannot be set to true when isHeadless is set to true.
mainContentoptionalField ID for field you want to be the main markdown content. (Can be a markdown, richtext, or string field)
typeoptionalManually set value for "type" field in the frontmatter (see hugo docs)
resolveEntriesoptionalResolve the specified reference fields and/or asset fields to one of it's properties specified with the resolveTo parameter
overridesoptionalDo custom overrides for field values or field names
filtersoptionalAccepts an object of Contentful search parameters to filter results. See Contentful docs
ignoreLocalesoptionalIgnore localization settings and only pull from the default locale (defaults to false)
Localization Options

The config also has a locales field that allows you to specify what locales you want to pull from. This field can take an array of strings, an array of objects, or a combination.

By default locale specific file extensions will be used for multiple translations.

// produce en-us.md and fr-fr.md files
module.exports = {
    locales: ['en-US', 'fr-FR'];
    // rest of config
}

// produce en.md and fr.md files
module.exports = {
    locales: [
        {
            code: 'en-US',
            mapTo: 'en'
        },
        {
            code: 'fr-FR',
            mapTo: 'fr'
        }
    ]
    // rest of config
}

// produce en-us.md files and fr.md files
module.exports = {
    locales: [
        'en-US',
        {
            code: 'fr-FR',
            mapTo: 'fr'
        }
    ]
    // rest of config
}

After configuring locales in Contentful Hugo you will need to update your Hugo config to account for these locales. Consult the Hugo docs for more details.

# config.toml

[languages]
    [languages.en-us]
    #language settings
    [languages.fr-fr]
    #language settings
Locale Specific Directories

There are sometimes cases where you will want to place content in a directory based on it's locale rather than using a file extension based translation. In order to do this you simple include [locale] inside your directory file path.

When using locale specific directories the locale specific file extensions (i.e. en.md or fr.md) get dropped

module.exports = {
    locales: ['en', 'fr']
    singleTypes: [
        {
            id: 'settings',
            fileName: 'settings',
            fileExtension: 'yaml',
            directory: 'data/[locale]'
            /*
                produces:
                - data/en/settings.yaml
                - data/fr/settings.yaml
            */
        }
    ]
    repeatableTypes: [
        {
            id: 'post',
            directory: 'content/[locale]/post',
            /*
                produces:
                - content/en/post/[entryId].md
                - content/fr/post/[entryId].md
            */
        },
    ],
};
Advanced Config Examples
Dynmically Changing Tokens

Here is an example of dynamically change the token, previewToken, and environment options depending on any arbitrary condition.

// contentful-hugo.config.js

require('dotenv').config(); // assuming you have "dotenv" in your dependencies

const myMasterToken = process.env.CONTENTFUL_MASTER_TOKEN;
const myMasterPreviewToken = process.env.CONTENTFUL_MASTER_PREVIEW_TOKEN;
const myStagingToken = process.env.CONTENTFUL_STAGING_TOKEN;
const myStagingPreviewToken = process.env.CONTENTFUL_STAGING_PREVIEW_TOKEN;

// set some condition
const isStaging = true || false;

module.exports = {
    contentful: {
        space: 'my-space-id',
        token: isStaging ? myStagingToken : myMasterToken,
        preview: isStaging ? myStagingPreviewToken : myMasterPreviewToken,
        environment: isStaging ? 'staging' : 'master',
    },
    // rest of config
};
Overriding Fields and Field Values
// contentful-hugo.config.js

module.exports = {
    repeatableTypes: [
        {
            id: "trips",
            directory: "content/trips"
            overrides: [{
                field: "url",
                options: {
                    // change the url field name to "slug" in frontmatter
                    fieldName: "slug"
                }
            },
            {
                field: "distanceInKilometers",
                options: {
                    // rename "distanceInKilometers" to "distanceInMiles"
                    fieldName: "distanceInMiles",
                    // convert distance to miles and output the result in frontmatter
                    valueTransformer: (val) => {
                        if(typeof val === 'number') {
                            return val * 0.621371
                        }
                        return 0
                    }
                }
            }]
        }
    ]
}
Config File Autocomplete

For JS config files you can import a ContentfulHugoConfig type which will enable autocomplete in text editors that support Typescript typings. (Tested in Visual Studio Code.)

/**
 * @type {import('contentful-hugo').ContentfulHugoConfig}
 */
module.exports = {
    // rest of config
};

Expected Output

Files will be generated in the directory specified in the config file. Front matter will be in YAML format. Files of single types will be named after fileName specified in the config file. Files of repeatable types will be named after their entry ID in Contenful, which makes it easy to link files together.

Default Metadata Fields and Date Field

The following fields will always appear in your frontmatter:

date: # defaults to sys.createdAt unless you have a field with the id "date" then it get's overwritten
sys:
    id: # the entry id
    updatedAt: # the last time this entry was updated in Contentful
    createdAt: # when the entry was created in Contentful
    revision: # the revision number
    space: # the space id
    contentType: # the content type id

# the following fields are depreciated and will be removed in a future version
# migrate to using the sys.updatedAt and sys.createdAt iterations

updated: # the last time the entry was updated in Contentful
createdAt: # when the entry was created in Contentful

Asset Information

Assets like images and videos come with some extra information that makes it easy to implement things like alt text or layouts that rely on knowing the image dimensions. The fields are as follows:

assetFieldName:
    assetType: # indicates the asset type such as "image" "video" "audio" ect.
    url: # url of the asset
    title: # title of the asset written in Contentful
    description: # description of the asset written in Contentful
    width: # width of the asset (images only)
    height: # height of the asset (images only )

If you're using Hugo you can access the information like below:

<img
    src="{{ .Params.assetFieldName.url }}"
    width="{{ .Params.assetFieldName.width }}"
/>

This same information will also appear in asset arrays like a gallery:

myGallery:
    - assetType: 'image/jpg'
      url: '//link-to-image.jpg'
      title: 'Image 1'
      description: 'Image 1 Description'
      width: 500
      height: 500
    - assetType: 'image/jpg'
      url: '//link-to-image-2.jpg'
      title: 'Image 2'
      description: 'Image 2 Description'
      width: 1920
      height: 1080

Entries

Linked entries will include fields for it's id and it's content type id.

linkedEntry:
    id: <contentful-entry-id>
    typeId: <content-type-ID>

#example with array of linked entries

relatedArticles:
    - id: '41UFfIhszbS1kh95bomMj7'
      typeId: 'articles'
    - id: '85UFfIhsacS1kh71bpqMj7'
      typeId: 'articles'
Accessing Linked Entry Data

All files are named after their entry id in Contentful making it easy to retrieve it using .Site.GetPage in Hugo

// if you have access to the "Page" object
{{ with .Site.GetPage "<path-to-file>/<entry-id>" }}
    {{ .Title }}
{{ end }}

// if you don't have access to the "Page" object
// for example in a nested partial
{{ with site.GetPage "<path-to-file>/<entry-id>" }}
    {{ .Title }}
{{ end }}

Relevant Documentation:

Rich Text As Main Content

A rich text field that is set as the "mainContent" for a content type will be rendered as markdown for Hugo.

Dynamic content such as embedded-entry-blocks are rendered as shortcodes with parameters included that can be used to fetch the necessary data.

<!-- example embedded entry -->
<!-- you can use the id, contentType, and parentContentType parameters to fetch the desired data -->

{{< contentful-hugo/embedded-entry id="nTLo2ffSJJp5QrnrO5IU9" contentType="gallery" parentContentType="post" >}}

Before fetching rich text data make sure you have run contentful-hugo --init so that you will have all the rich text shortcodes. Once you have these shortcodes you can extend and modify them to suit your needs.

The list of rich text short codes includes:

  • contentful-hugo/asset-hyperlink.html
  • contentful-hugo/embedded-asset.html
  • contentful-hugo/embedded-entry.html
  • contentful-hugo/entry-hyperlink.html
  • contentful-hugo/inline-entry.html

By default the richtext short codes will show a notification for an unconfigured item.

Unconfigured Embedded Entry Block

You can customize them by navigating to layouts/shortcodes/contentful-hugo/{shortcode-name}.html

Rich Text In FrontMatter

A Rich text field will produce nested arrays mirroring the JSON structure that they have in the API. Each node will need to be looped through and produce HTML depending on the nodeType field.

richTextField:
    - nodeType: 'paragraph'
      data: {}
      content:
          - data: {}
            marks: []
            value: 'This is a simple paragraph.'
            nodeType: 'text'
    - nodeType: 'paragraph'
      data: {}
      content:
          - data: {}
            marks: []
            value: 'This is a paragraph with '
            nodeType: 'text'
          - data: {}
            marks:
                - type: 'italic'
            value: 'italicized text.'
            nodeType: 'text'
    - nodeType: 'embedded-asset-block'
      data:
          assetType: 'image/jpeg'
          url: '//images.ctfassets.net/some-image-url.jpg'
          title: 'Image title will appear here'
          description: 'Image description will appear here'
          width: 1920
          height: 1080
      content: []

In addition a plaintext version of the field will be generated using the field ID appended with "_plaintext". This allows you to quickly fetch the text by itself without any of the other data. A simple use case would be using the plaintext output to automatically generate a meta description for a webpage.

richTextField_plaintext: 'This is a simple paragraph. This is a paragraph with italicized text.'

The Resolve Entries Parameter

The resolve entries option let's you specify a property from a referenced entry or asset to resolve that field value to. For example say you have a category content type that is referenced in posts. Normally contentful-hugo will give the following result

category:
    id: some-entry-id
    contentType: category

While this makes it easy to find the category, this format does not allow you to use Hugo's built in taxonomy features. With the resolveEntries parameter you can remedy this.

// from the config file
module.exports = {
    repeatableTypes: [
        {
            id: 'post',
            directory: 'content/posts',
            resolveEntries: [
                {
                    field: 'category',
                    resolveTo: 'fields.slug',
                },
            ],
        },
    ],
};

Now the category field will only display the slug as the value.

category: my-category-slug

The resolve entries feature works with both reference fields and asset fields, as well as multiple reference and multiple asset fields.

The Overrides Parameter

Overrides can be used to modify field names and field values.

Here's a simple example of changing a field name from "url" to "videoUrl"

repeatableTypes: [
    {
        id: 'youtubeVideo',
        directory: 'content/_youtubeVideo',
        isHeadless: true,
        overrides: [
            {
                field: 'url',
                options: {
                    // set new field name in frontmatter
                    fieldName: 'videoUrl',
                },
            },
        ],
    },
];

overrides also has a valueTransformer options that allows you to manipulate the field data that will appear in frontmatter. valueTransformer takes a method that has the field value as a parameter and then returns the final result that will appear in the frontmatter. (Be aware that since valueTransformer must be a method this option will only work in javascript config files)

Here's an example where we change the field name from "url" to "videoId" and then we use the valueTransformer to extract the video id from the url and then place it in the frontmatter.

repeatableTypes: [
    {
        id: 'youtubeVideo',
        directory: 'content/_youtubeVideo',
        isHeadless: true,
        overrides: [
            {
                field: 'url',
                options: {
                    fieldName: 'videoId',
                    // "value" is whatever value is currently saved in the field.
                    // in this case it's a url for a youtube video
                    valueTransformer: (value) => {
                        const url = new URL(value);
                        // extract the video id from the url and return it
                        return url.searchParams.get('v');
                    },
                },
            },
        ],
    },
];

When using the valueTransformer option on fields that contain arrays make sure to loop through the value when manipulating it.

repeatabledTypes: [
    {
        id: 'post',
        directory: 'content/posts',
        overrides: [
            {
                // the author field is a multi-reference field
                field: 'authors',
                options: {
                    valueTransformer: (authorRefs) => {
                        const authors = [];
                        for (const ref of authorRefs) {
                            // get the name, photo, and bio of the author
                            // and add it to the array
                            authors.push({
                                name: ref.fields.name,
                                photo: ref.fields.photo.fields.file.url,
                                bio: ref.fields.bio,
                            });
                        }
                        return authors;
                    },
                },
            },
        ],
    },
];

Now the authors field will look like this:

authors:
    - name: Some Name
      photo: //images.cfassets.net/path-to-photo.jpg
      bio: some bio text
    - name: Some other name
      photo: //images.cfassets.net/path-to-photo.jpg
      bio: some other bio text

As you can see this can be used to produce similar results to the resolveEntries parameter, but resolveEntries can only return one property while with overrides you can do whatever you want with the field values.

The Filters Parameter

You can use to filters option to enter search parameters allowing you to filter entries based on some of their properties. For more info on Contentful search parameters visit their docs.

Be aware that the following search parameters will be ignored content_type, skip, order, limit

Examples:
module.exports = {
    singleTypes: [
        // get a homepage with a specific entryId
        {
            id: 'homepage',
            directory: 'content',
            fileName: '_index',
            filters: {
                'sys.id': 'my-homepace-id'
            }
        }
    ]
    repeatableTypes: [
        // only get events that start after 01/01/2020
        {
            id: 'events',
            directory: 'content/events',
            filters: {
                'fields.startDate[gte]': '2020-01-01T00:00:00Z',
            },
        },
        // get posts where author is "John Doe" and contains the tag "flowers"
        {
            id: 'posts',
            directory: 'content/posts',
            filters: {
                'fields.author': 'John Doe',
                'fields.tags': 'flowers'
            },
        },
    ];

}

Guides

Known Issues

These are some known issues.

  • Date & Time Field w/o Timezone: Date fields that include time but do not have a specified timezone will have a timezone set based on whatever machine the script is run on. So using a date field in contentful with this setting could lead to unexpected results when formatting dates. Date fields that don't include time (ex: YYYY-MM-DD) are not effected by this.
  • Fetching Data Before Contentful CDN Updates: Sometimes when triggering a build from a webhook, it won't always get the latest data. This is because it sometimes takes a couple seconds for the latest data to get distrubuted across Contentful's CDN. If you run into this issue add teh the --wait flag to your script. Here's an example where we wait an additional 6 seconds contentful-hugo --wait=6000.
  • Hugo --server Rendering Issues: If you have fields that where multiple different files are referenced such as a rich text field that references other entries Hugo's default server mode may not rerender everything. To fix this run hugo server --disableFastRender

Keywords

FAQs

Package last updated on 10 Mar 2021

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc