Getting started

← Docs overview

Introduction

What is Metalsmith?

Metalsmith is an extremely simple, pluggable static-site generator for NodeJS. Let us break that down:

Metalsmith is a static site generator

The task of a static site generator is to produce static files that can be deployed to a web server. For a static site generator this means:

  1. take a source directory, read the source files and extract their information
  2. manipulate the information with plugins
  3. write the manipulated information to files into a destination directory (NPM is )

Metalsmith is built on this reasoning. It takes the information from the files in a source directory and it writes the manipulated information to files into a destination directory.

Metalsmith is extremely simple

Metalsmith is pluggable

Metalsmith leaves all manipulations exclusively to plugins.

Manipulations could be anything: translating templates, transpiling code, replacing variables, wrapping layouts around content, grouping files, moving and removing files and so on. This is why we say Everything is a Plugin. Manipulations can be applied one after another, or chained or piped, if you prefer. Obviously, in a chain plugin order matters.

Splitting metalsmith in a minimal, rock-solid core and multiple plugins reduces complexity. It gives you the freedom to use exactly and only those plugins you need and it distributes the honor and the burden of maintaining the Metalsmith ecosystem.

Metalsmith is more

Depending on your desired use-case, Metalsmith can be much more than a static site generator:

Thanks to its simple in-memory abstraction, metalsmith builds and plugins are easy to unit-test without the need for extensive mocks.
Have a look at the Metalsmith tests to get an idea.

Prerequisites

Metalsmith runs on all NodeJS Long-Term Support versions that are not end-of-life and some more. Download & install NodeJs and NPM (NPM comes pre-bundled with Node): https://nodejs.org/en/download/.
If you plan to publish plugins, it is recommended that you use nvm or nvs (for Windows)

Add node_modules/.bin to the PATH environment variable if you'd like to use the Metalsmith CLI:

Metalsmith builds are tested on both Linux & Windows systems and all officially supported NodeJS LTS releases.

Installation

First create a project folder somewhere and navigate to it:

mkdir ~/Documents/metalsmith-website && cd ~/Documents/metalsmith-website
mkdir %userProfile%\Documents\metalsmith-website && cd %userProfile%\Documents\metalsmith-website

Then install Metalsmith with any NodeJS package manager:

npm install metalsmith
yarn add metalsmith
pnpm add metalsmith

It is likely that you will want to install some plugins too:

npm install @metalsmith/collections @metalsmith/markdown @metalsmith/permalinks @metalsmith/layouts
yarn add @metalsmith/collections @metalsmith/markdown @metalsmith/permalinks @metalsmith/layouts
pnpm add @metalsmith/collections @metalsmith/markdown @metalsmith/permalinks @metalsmith/layouts

If you would like to use Typescript in your build file, be sure to install @types/metalsmith as well.

Concepts

There are only a few core concepts you need to be familiar with to understand how to work with Metalsmith, and if you've worked with another static site generator or run some commands in a terminal like git add or ls -la, you are probably already familiar with most of them.

Files

Everyone has worked with files before. They have a name, a path, an extension, contents and metadata (like the last modified date). Metalsmith represents every file in the source directory as a javascript File object. For instance,

src/post/my-file.md:

---
title: A Catchy Title
draft: false
---

An unfinished article...

...becomes

{
  'post/my-file.md': {
    title: 'A Catchy Title',
    draft: false,
    contents: 'An unfinished article...',
    mode: '0664',
    stats: {
      /* keys with information on the file */
    }    
  }
}

...where the content of the file is always mapped to the property value of contents. For illustration purposes only we display the value of contents as a string. Technically, the property value of contents is mapped as a NodeJS Buffer , which can also handle binary data (for images, PDF's, etc). mode contains the file permission bit and stats has more technical information on the file such as size or birthtime. The file is also parsed for YAML front matter, which is merged into the File object. Thus, we finally have a javascript Files object of objects.

{
  'relative_to_sourcepath/file1.md': {
    title: 'A Catchy Title',
    draft: false,
    contents: 'An unfinished article...',
    mode: '0664',
    stats: {
      /* keys with information on the file */
    }    
  },
  'relative_to_sourcepath/file2.md': {
    title: 'An Even Better Title',
    draft: false,
    contents: 'One more unfinished article...',
    mode: '0664',
    stats: {
      /* keys with information on the file */
    }    
  }
}

Plugins can then manipulate the javascript File objects representing the original files however they want, and writing a plugin is super simple.

Front matter

To attach metadata to a JS File object, metalsmith reads front matter. Front matter is a term borrowed from the publishing industry meaning metadata about a written work. In Metalsmith this is a YAML document section (delineated by ---) containing metadata (matter) at the top (front) of a file (commonly, markdown). Metalsmith will recognize and read the front matter of a file and add it as metadata to the JS file representation when you run the build. Here is a typical example of an index.md file with YAML front-matter. If you don't like the YAML syntax you can use JSON front matter as well

index.md
---
title: Hello World
keywords:
  - hello
  - world
draft: false
---
Welcome to my blog
index.md
---
{
  "title": "Hello World",
  "keywords": ["hello","world"],
  "draft": false
}
---
Welcome to my blog

The front-matter will be parsed by Metalsmith as:

{
  title: 'Hello World',
  keywords: ['hello','world'],
  draft: false,
  contents: <Buffer>,
  mode: '0644',
  stats: { ... }
}

When the front matter is read into javascript, we refer to it as file metadata.

Multi-line strings

A common requirement is to write multi-line strings in YAML, either for readability or for output. There are a lot of ways to write multiline strings in YAML. Examples of the two most common ones are shown here:

Glob patterns

Metalsmith and its plugins make extensive use of glob patterns to target specific files (usually through the pattern option). A glob is a type of string pattern syntax that is commonly and conveniently used to match files by path with support for globstar wildcards *. Chances are you are already using glob patterns in .gitignore files, with the Linux/Mac or git terminal commands. Here are a few examples of how you can match with glob patterns:

You can always use DigitalOcean's handy Glob tool or globster.xyz to test your glob patterns.

The plugin chain

We believe that understanding the internal representation of files as JavaScript objects is really key to fully grasp the concept of Metalsmith. To understand this better, we follow the evolution of a file at each step of the build process (between each use statement). We are also using the writemetadata() plugin, which writes the {key: value} pairs excerpted from the File objects representing the files, to the filesystem as .json files. You can then view the .json files to find out how files are represented internally in Metalsmith.

metalsmith.js
Metalsmith(__dirname)            
  .source('src')      
  .destination('build')   
  .use(markdown())          
  .use(layouts())
  .use(writemetadata({            // write the JS object
    pattern: ['**/*'],            // for each file into .json
    ignorekeys: ['next', 'previous'],
    bufferencoding: 'utf8'        // also put 'content' into .json
  }))
  .build(function(err) {         
    if (err) throw err;          
  });
metalsmith.json
{
  "source": "src",
  "destination": "build",
  "plugins": [
    { "markdown": true },
    { "layouts": true },
    { "writemetadata": {
      "pattern": ["**/*"],
      "ignorekeys": ["next", "previous"],
      "bufferencoding": "utf8"
    } }
  ]
}

In the example above, after applying .use(markdown()) the initial representation of my-file.md becomes my-file.html. The markdown plugin changes the file extension and converts the contents to HTML.

{
  'relative_to_sourcepath/my-file.html': {
    title: 'A Catchy Title',
    draft: false,
    contents: '<p>An unfinished article...</p>',
    ...
  }
}

After applying .use(permalinks()) the file is renamed to original-name/index.html and a path property is added to the file's metadata:

{
  'relative_to_sourcepath/my-file/index.html': {
    title: 'A Catchy Title',
    draft: false,
    contents: '<p>An unfinished article...</p>',
    path: 'myfile',
    ...
  }
}

Assuming we have defined a very simple nunjucks layout file in a separate layouts folder...

./layouts/layout.njk
<!doctype html>
<html>
<head>
  <title>{{ title }}</title>
</head>
<body>
  {{ contents | safe }}
</body>
</html>

... after applying .use(layouts()) in our Metalsmith chain our JavaScript object becomes:

{
  'relative_to_sourcepath/my-file/index.html': {
    title: 'A Catchy Title',
    draft: false,
    contents: `<!doctype html><html><head>
               <title>A Catchy Title</title></head><body>
               <p>An unfinished article...</p>
               </body></html>`,
    path: 'myfile',
    ...      
  }
}

Finally when the .build(function(err)) is performed our JavaScript object is written to relative_to_destpath/myfile/index.html.

Quickstart

You want to build a website or blog with a static site generator. Well, here is our elevator pitch. It's as easy as that:

metalsmith.mjs
import { fileURLToPath } from 'node:url'
import { dirname } from 'path'
import Metalsmith from 'metalsmith'
import collections from '@metalsmith/collections'
import layouts from '@metalsmith/layouts'
import markdown from '@metalsmith/markdown'
import permalinks from '@metalsmith/permalinks'

const __dirname = dirname(fileURLToPath(import.meta.url))
const t1 = performance.now()

Metalsmith(__dirname)         // parent directory of this file
  .source('./src')            // source directory
  .destination('./build')     // destination directory
  .clean(true)                // clean destination before
  .env({                      // pass NODE_ENV & other environment variables
    DEBUG: process.env.DEBUG,
    NODE_ENV: process.env.NODE_ENV
  })           
  .metadata({                 // add any variable you want & use them in layout-files
    sitename: "My Static Site & Blog",
    siteurl: "https://example.com/",
    description: "It's about saying »Hello« to the world.",
    generatorname: "Metalsmith",
    generatorurl: "https://metalsmith.io/"
  })
  .use(collections({          // group all blog posts by internally
    posts: 'posts/*.md'       // adding key 'collections':'posts'
  }))                         // use `collections.posts` in layouts
  .use(markdown())            // transpile all md into html
  .use(permalinks())          // change URLs to permalink URLs))
  .use(layouts({              // wrap layouts around html
    pattern: '**/*.html'
  }))
  .build((err) => {           // build process
    if (err) throw err        // error handling is required
    console.log(`Build success in ${((performance.now() - t1) / 1000).toFixed(1)}s`)
  });
metalsmith.cjs
const Metalsmith  = require('metalsmith')
const collections = require('@metalsmith/collections')
const layouts     = require('@metalsmith/layouts')
const markdown    = require('@metalsmith/markdown')
const permalinks  = require('@metalsmith/permalinks')

const t1 = performance.now()

Metalsmith(__dirname)         // parent directory of this file
  .source('./src')            // source directory
  .destination('./build')     // destination directory
  .clean(true)                // clean destination before
  .env({                      // pass NODE_ENV & other environment variables
    DEBUG: process.env.DEBUG,
    NODE_ENV: process.env.NODE_ENV
  })           
  .metadata({                 // add any variable you want & use them in layout-files
    sitename: "My Static Site & Blog",
    siteurl: "https://example.com/",
    description: "It's about saying »Hello« to the world.",
    generatorname: "Metalsmith",
    generatorurl: "https://metalsmith.io/"
  })
  .use(collections({          // group all blog posts by internally
    posts: 'posts/*.md'       // adding key 'collections':'posts'
  }))                         // use `collections.posts` in layouts
  .use(markdown())            // transpile all md into html
  .use(permalinks())          // change URLs to permalink URLs))
  .use(layouts({              // wrap layouts around html
    pattern: '**/*.html'
  }))
  .build((err) => {           // build process
    if (err) throw err        // error handling is required
    console.log(`Build success in ${((performance.now() - t1) / 1000).toFixed(1)}s`)
  })
metalsmith.json
{
  "source": "src",
  "destination": "build",
  "clean": true,
  "env": {
    "DEBUG": "$DEBUG",
    "NODE_ENV": "$NODE_ENV"
  },
  "metadata": {
    "sitename": "My Static Site & Blog",
    "siteurl": "https://example.com/",
    "description": "It's about saying »Hello« to the world.",
    "generatorname": "Metalsmith",
    "generatorurl": "https://metalsmith.io/"
  },
  "plugins": [
    { "@metalsmith/collections": { "posts": "posts/*.md" }},
    { "@metalsmith/markdown": {}},
    { "@metalsmith/permalinks": {}},
    { "@metalsmith/layouts": { "pattern": "**/*.html" }},
  ]
}

Directory structure

A typical directory structure for a metalsmith (static-site) project looks more or less like this:

repo
├── metalsmith.js
├── package.json
├── node_modules
├── build
├── layouts
│   ├── default.hbs
│   └── post.hbs
├── lib
│   └── sass
│   │   └── style.scss
│   ├── data
│   │   └── nav.json
│   └── plugins
│       └── local-metalsmith-plugin.js
└── src
    ├── about.md
    ├── index.md
    └── posts
        ├── first-post.md
        └── second-post.md

where:

...but Metalsmith gives you total freedom about how you want to structure your project, so feel free to restructure things as you see fit.

Starter projects

The community has built a few interesting starter projects:

There is also a one-click Netlify CMS starter.

Github search for other metalsmith starters

× This website may use local storage for purely functional purposes (for example to remember preferences), and anonymous cookies to gather information about how visitors use the site. By continuing to browse this site, you agree to its use of cookies and local storage.