Permalinks
A Metalsmith plugin for permalinks.
Install / Use
/learn @metalsmith/PermalinksREADME
@metalsmith/permalinks
A Metalsmith plugin that applies a custom permalink pattern to files, and renames them so that they're nested properly for static sites (converting about.html into about/index.html).
[![metalsmith: core plugin][metalsmith-badge]][metalsmith-url] [![npm: version][npm-badge]][npm-url] [![ci: build][ci-badge]][ci-url] [![code coverage][codecov-badge]][codecov-url] [![license: MIT][license-badge]][license-url]
Installation
NPM:
npm install @metalsmith/permalinks
Yarn:
yarn add @metalsmith/permalinks
Usage
By default @metalsmith/permalinks moves all HTML source files at :dirname?/:basename to the build as :dirname/:basename/index.html and adds a customizable permalink property to te file metadata. You can tweak which files to match, set fixed permalinks, use a permalink pattern with :placeholder's that will be read from the file's metadata, and finetune how that metadata and the final permalink are formatted as a string through the directoryIndex, slug, date and trailingSlash options.
Fixed permalinks or permalink patterns can be defined in file front-matter, or for a set of files through plugin options. Permalink patterns defined in file front-matter take precedence over plugin options.
import { dirname } from 'path'
import { fileURLToPath } from 'url'
import Metalsmith from 'metalsmith'
import permalinks from '@metalsmith/permalinks'
const __dirname = dirname(fileURLToPath(import.meta.url))
// defaults
Metalsmith(__dirname).use(permalinks())
// explicit defaults
Metalsmith(__dirname).use(
permalinks({
// files to target
match: '**/*.html',
// permalink pattern with placeholders
pattern: ':dirname?/:basename',
// how to format Date values when substituting pattern parts
date: {
format: 'YYYY/MM/DD',
locale: 'en-US' // only relevant if you use textual date part formats
},
// how to postprocess a resolved permalink in a URL (and filesystem)-friendly way
slug: {
lowercase: true,
remove: /[<>:"\'|?*]|[^\\w\\s$_+~.()!\\-@\\/]+/g,
extend: { ':': '-', '|': '-', '/': '-', '<': '', '>': '' }
},
trailingSlash: false,
directoryIndex: 'index.html',
// throw an error when 2 files have the same target permalink
duplicates: 'error',
// additional linksets
linksets: []
})
)
Every permalinks() instantiation supports the following options:
- directoryIndex - traditionally
index.html, but servers could be configured with alternatives. See Overriding the default index.html file - trailingSlash - whether to add a trailing
/so that the permalink becomesblog/post/instead ofblog/post. Useful to avoid redirects on servers which do not have a built-in rewrite module enabled. - duplicates - what to do when 2 files have the same target destination. See Ensure files have unique URI's
- linksets, see Defining linksets
Placeholder substitution will mostly toString the value. For example, when you have an :array placeholder and a file with front-matter array: ['one','two'], it will substitute into 'onetwo', but you can refer to the n<sup>th</sup> value with a dot-delimited keypath (eg :array.0).
A boolean false will result in an error unless the placeholder is optional (it would then be an empty string = omitted), but a boolean true will see the placeholder substituted with its key name (eg :placeholder for a file with front-matter placeholder: true will become placeholder).
Matching files
The match option can be 1 or multiple glob patterns, or an object with key-value pairs that will be matched on either... or... basis.
// only match non-root html files
metalsmith.use(permalinks({ match: '*/**/*.html' }))
// match templates so you can use the permalink property in @metalsmith/layouts later
metalsmith.use(permalinks({ match: '**/*.hbs' }))
// match files that are either primary:false or have id:1
metalsmith.use(permalinks({ match: { primary: false, id: 1 } }))
If a match object property targets an array in file metadata, it will be matched if the array contains the value in the match object.
Defining linksets
Whereas the default match option globally defines which files are permalinked, additional linksets can be defined with their own match, pattern, date and slug options.
metalsmith.use(
permalinks({
// original options act as the keys of a `default` linkset,
pattern: ':dirname?/:basename',
date: 'YYYY',
// each linkset defines a match, and any other desired option
linksets: [
{
match: { collection: 'blogposts' },
pattern: 'blog/:date/:title',
date: 'MM-DD-YYYY'
},
{
match: { collection: 'pages' },
pattern: 'pages/:title'
}
]
})
)
Every matched file is only permalinked once, even if it is matched by multiple linksets. The linksets defined in linksets take precedence over the default match, and the first linkset in linksets takes precedence over the next. In the example above, a file which has collection: ['pages','blogposts'] would be permalinked to blog/:date/:title.
Fixed permalinks
You can declare a fixed permalink in file front-matter:
---
# src/topic_metalsmith.html
permalink: topics/static-site/metalsmith
---
@metalsmith/permalinks will move the source file topic_metalsmith.html to the build path topics/static-site/metalsmith/index.html and add permalink: 'topics/static-site/metalsmith' to the file metadata.
Setting an explicit front-matter permalink overrides any other match that also matched the file from plugin options.
Typical use case: SEO-sensitive links that should be preserved, even if you moved or renamed the file or updated its front-matter.
Computed permalinks
File permalinks can be computed from other (own) file metadata properties.
---
# src/topic_metalsmith.html
topic: static-site
subtopic: metalsmith
permalink: topics/:topic/:subtopic
---
Just like the previous example, this will also move the source file topic_metalsmith.html to the build path topics/static-site/metalsmith/index.html and add permalink: 'topics/static-site/metalsmith' to the file metadata.
Placeholders can also refer to a keypath within the front-matter permalink or plugin option linkset, e.g. permalink: blog/:postData.html.slug.
Skipping permalinks
An otherwise linkset-matched file can be excluded from permalinking by setting permalink: false in its front-matter:
---
title: error
permalink: false
---
Explicitly disabling a permalink in front-matter overrides any other pattern that also matched the file from plugin options.
Typical use case: hosting static sites on third-party providers with specific conventions, e.g. on AWS S3 there must be a top level error.html file and not an error/index.html file.
Customizing permalinks
The pattern can contain a reference to any piece of metadata associated with the file by using the :PROPERTY syntax for placeholders.
By default, all files get a :dirname?/:basename (+ directoryIndex = /index.html) pattern, i.e. the original filepath blog/post1.html becomes blog/post1/index.html. The dirname and basename values are automatically made available by @metalsmith/permalinks for the purpose of generating the permalink.
If you want to tweak how the characters in the permalink are transformed (for example to handle unicode & non-ascii characters),see slug options.
The pattern can also be set as such:
metalsmith.use(
permalinks({
// original options act as the keys of a `default` linkset,
pattern: ':title',
date: 'YYYY',
// each linkset defines a match, and any other desired option
linksets: [
{
match: { collection: 'blogposts' },
pattern: 'blog/:date/:title',
date: 'MM-DD-YYYY'
},
{
match: { collection: 'pages' },
pattern: 'pages/:title'
}
]
})
)
Optional permalink pattern parts
The permalink example in Computed permalinks would result in an error if subtopic or topic were not defined. To allow this add a question mark to the placeholder like :topic/:subtopic?. If the property is not defined in a file's metadata, it will be replaced with an empty string ''. For example the pattern :category?/:title applied to a source directory with 2 files:
would generate the file tree:
build
├── category1/with-category/index.html
└── no-category/index.html
Date formatting
By default any date will be converted to a YYYY/MM/DD format when using in a permalink pattern, but you can change the conversion by passing a date option:
metalsmith.use(
permalinks({
pattern: ':date/:title',
date: 'YYYY'
})
)
Starting from v3 @metalsmith/permalinks no longer uses moment.js. A subset of date-formatting tokens relevant to site URI's are made available that are largely compatible with those defined at moment.js:
| Token | Description | Examples | | ----- | --------------------------- | -------------------------- | | D | Date numeric | 1 2 ... 30 31 | | DD | Date numeric zero-padded | 01 02 ... 30 31 | | d | Day of week numeric | 0 1 ... 5 | | dd | Day of week 2-letter (*) | Su Mo ... Sa | | ddd | Day of
Related Skills
node-connect
353.3kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
111.7kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
353.3kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
353.3kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
