Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time


Build Status NPM version Coverage Status

Utilities for Hexo.

Table of contents


$ npm install hexo-util --save


var util = require('hexo-util');


A simple plain object cache

const cache = new Cache();

// set(key, value)
cache.set('foo', 'bar');

// get(key) => value
// 'bar'

// has(key) => Boolean
// true
// false

// apply(key. value)
cache.apply('baz', () => 123);
// 123
cache.apply('baz', () => 456);
// 123
cache.apply('qux', 456);
// 456
cache.apply('qux', '789');
// 456

// size()
// 3

// dump()
  foo: 'bar',
  baz: 123,
  qux: 456

// del(key)
// false

// flush()
// false
// 0


Caches contents piped to the stream.

var stream = new CacheStream();


stream.on('finish', function(){
  // Read cache piped to the stream

  // Destroy cache

camelCaseKeys(obj, options)

Convert object keys to camelCase. Original keys will be converted to getter/setter and sync to the camelCase keys.

  foo_bar: 'test'
// { fooBar: 'test', foo_bar: 'test' }


return SHA1 hash object. This is the same as calling createHash('utf8') in the node.js native module crypto.

const sha1 = createSha1Hash();
 .on('finish', () => {


Decode encoded URL or path. An alternative to the native decodeURI() function, with added ability to decode punycoded domain.


// http://bá

// /foo/bár/

/* Alternatively, Node 10+ offers native API to decode punycoded domain */
const {format} = require('url')
decodeURI(format(new URL(''), {unicode: true}))
// http://bááz

deepMerge(target, source)

Merges the enumerable properties of two objects deeply. target and source remain untouched.

// Merge deeply
const obj1 = {a: {b: 1, c: 1, d: {e: 1, f: 1}}};
const obj2 = {a: {b: 2, d: {f: 'f'} }};

deepMerge(obj1, obj2);
// {a: {b: 2, c: 1, d: {e: 1, f: 'f'} }}
// Arrays will be combined in the same property, similar to lodash.merge
const obj1 = { 'a': [{ 'b': 2 }, { 'd': 4 }] };
const obj2 = { 'a': [{ 'c': 3 }, { 'e': 5 }] };

deepMerge(obj1, obj2);
// { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] };


Encode URL or path into a safe format.


// /foo/b%C3%A1r/


Escapes diacritic characters in a string.


Escapes HTML entities in a string.

escapeHTML('<p>Hello "world".</p>')
// &lt;p&gt;Hello &quot;world&quot;.&lt;&#x2F;p&gt;

/* support escaped characters */
// &lt;foo&gt;bar&lt;&#x2F;foo&gt;


Escapes special characters in a regular expression.


Returns a url with the config.url prefixed. Output is encoded automatically. Requires bind(hexo).

url: # example

gravatar(str, [options])

Returns the gravatar image url from an email.

If you didn't specify the [options] parameter, the default options will apply. Otherwise, you can set it to a number which will then be passed on as the size parameter to Gravatar. Finally, if you set it to an object, it will be converted into a query string of parameters for Gravatar.

Option Description Default
s Output image size 80
d Default image
f Force default
r Rating

More info: Gravatar

gravatar('', 40)
gravatar('' {s: 40, d: ''})


Generates SHA1 hash.

// <Buffer 7c 4a 8d 09 ca 37 62 af 61 e5 95 20 94 3d c2 64 94 f8 94 1b>

highlight(str, [options])

Syntax highlighting for a code block.

Option Description Default
gutter Whether to show line numbers true
wrap Whether to wrap the code block in <table> true
firstLine First line number 1
hljs Whether to use the hljs-* prefix for CSS classes false
lang Language
caption Caption
tab Replace tabs
autoDetect Detect language automatically (warning: slow)
Sublanguage highlight requires autoDetect to be enabled and lang to be unset
mark Line highlight specific line(s)
languageAttr Output code language into data-language attr false

htmlTag(tag, attrs, text, escape)

Creates a html tag.

Option Description Default
tag Tag / element name
attrs Attribute(s) and its value.
Value is always escaped, URL is always encoded.
text Text, the value is always escaped
(except for <style> tag)
escape Whether to escape the text true
htmlTag('img', {src: 'example.png'})
// <img src="example.png">

htmlTag('a', {href: ''}, 'Hexo')
// <a href="">Hexo</a>

htmlTag('link', {href: ''}, '<a>bar</a>')
// <a href="">&lt;bar&gt;</a>

htmlTag('a', {href: ''}, '<b>bold</b>', false)
// <a href=""><b>bold</b></a>

/* text value of <style> won't be escaped, url is still encoded */
htmlTag('style', {}, 'p { content: "<"; background: url("bár.jpg"); }')
// <style>p { content: "<"; background: url("b%C3%A1r.jpg"); }</style>

/* support script tag with async/defer */
htmlTag('script', {src: '/foo.js', async: true}, '')
// <script src="/foo.js" async></script>

isExternalLink(url, sitehost, [exclude])

Option Description Default
url The input URL.
sitehost The hostname / url of website. You can also pass hexo.config.url.
exclude Exclude hostnames. Specific subdomain is required when applicable, including www. []

Returns if a given url is external link relative to given sitehost and [exclude].

// 'sitehost' can be a domain or url
isExternalLink('', '');
// false
isExternalLink('', '');
// false
isExternalLink('', '//');
// false
isExternalLink('/archives/foo.html', '');
// false
isExternalLink('', '');
// true
isExternalLink('', '', ['', '']);
// false
isExternalLink('', '', ['', '']);
// false
isExternalLink('', '', ['', '']);
// true


Parses the string and tests if the string matches the rule. rule can be a string, a regular expression or a function.

var pattern = new Pattern('posts/:id');

// {0: 'posts/89', 1: '89', id: '89'}
var pattern = new Pattern('posts/*path');

// {0: 'posts/2013/hello-world', 1: '2013/hello-world', path: '2013/hello-world'}

Permalink(rule, [options])

Parses a permalink.

Option Description
segments Customize the rule of a segment in the permalink
var permalink = new Permalink(':year/:month/:day/:title', {
  segments: {
    year: /(\d{4})/,
    month: /(\d{2})/,
    day: /(\d{2})/

// {year: '2014', month: '01', day: '31', title: 'test'}

// true

permalink.stringify({year: '2014', month: '01', day: '31', title: 'test'})
// 2014/01/31/test

prettyUrls(url, [options])

Rewrite urls to pretty URLs.

Option Description Default
trailing_index /about/index.html -> /about/ when false true
trailing_html /about.html -> /about when false true

Note: trailing_html ignores any link with a trailing index.html. (will not be rewritten to index).

// /foo/bar.html
// /foo/bar/index.html

prettyUrls('/foo/bar.html', { trailing_index: false });
// /foo/bar.html
prettyUrls('/foo/bar/index.html', { trailing_index: false });
// /foo/bar/

prettyUrls('/foo/bar.html', { trailing_html: false });
// /foo/bar
prettyUrls('/foo/bar/index.html', { trailing_html: false });
// /foo/bar/index.html

prettyUrls('/foo/bar.html', { trailing_index: false, trailing_html: false });
// /foo/bar
prettyUrls('/foo/bar/index.html', { trailing_index: false, trailing_html: false });
// /foo/bar/

prismHighlight(str, [options])

Syntax highlighting for a code block using PrismJS.

Option Description Default
lineNumber Whether to show line numbers true
lang Language 'none'
tab Replace tabs
isPreprocess Enable preprocess or not true
mark Highlight specific line
firstLine First line number
caption Caption

When isPreprocess is enabled, prismHighlight() will return PrismJS processed HTML snippet. Otherwise str will only be escaped and prismHighlight() will return the HTML snippet that is suitable for prism.js working in the Browser.

mark and firstLine options will have effect only when isPreprocess is disabled.

relative_url(from, to)

Returns the relative URL from from to to. Output is encoded automatically. Requires bind(hexo).

relative_url('foo/bar/', 'css/style.css')
// ../../css/style.css

slugize(str, [options])

Transforms a string into a clean URL-friendly string.

Option Description Default
separator Separator -
transform Transform the string into lower case (1) or upper case (2)
slugize('Hello World') = 'Hello-World'
slugize('Hellô Wòrld') = 'Hello-World'
slugize('Hello World', {separator: '_'}) = 'Hello_World'
slugize('Hello World', {transform: 1}) = 'hello-world'
slugize('Hello World', {transform: 2}) = 'HELLO-WORLD'

spawn(command, [args], [options])

Launches a new process with the given command. This method returns a promise.

Option Description Default
cwd Current working directory of the child process
env Environment key-value pairs
stdio Child's stdio configuration pipe
detached The child will be a process group leader
uid Sets the user identity of the process
gid Sets the group identity of the process
verbose Display messages on the console false
encoding Sets the encoding of the output string utf8

More info: child_process.spawn()

spawn('cat', 'test.txt').then((content) => {

// $ cd "/target/folder"
// $ cat "foo.txt" "bar.txt"
spawn('cat', ['foo.txt', 'bar.txt'], { cwd: '/target/folder' }).then((content) => {


Removes HTML tags in a string.


Strip leading whitespace from each line in a string. The line with the least number of leading whitespace, ignoring empty lines, determines the number to remove. Useful for removing redundant indentation.

wordWrap(str, [options])

Wraps the string no longer than line width. This method breaks on the first whitespace character that does not exceed line width.

Option Description Default
width Line width 80
wordWrap('Once upon a time')
// Once upon a time

wordWrap('Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding a successor to the throne turned out to be more trouble than anyone could have imagined...')
// Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\na successor to the throne turned out to be more trouble than anyone could have\nimagined...

wordWrap('Once upon a time', {width: 8})
// Once\nupon a\ntime

wordWrap('Once upon a time', {width: 1})
// Once\nupon\na\ntime

tocObj(str, [options])

Generate a table of contents in JSON format based on the given html string. Headings with attribute data-toc-unnumbered="true" will be marked as unnumbered.

Option Description Default
min_depth The minimum level of TOC 1
max_depth The maximum level of TOC 6
const html = [
  '<h1 id="title_1">Title 1</h1>',
  '<div id="title_1_1"><h2>Title 1.1</h2></div>',
  '<h3 id="title_1_1_1">Title 1.1.1</h3>',
  '<h2 id="title_1_2">Title 1.2</h2>',
  '<h2 id="title_1_3">Title 1.3</h2>',
  '<h3 id="title_1_3_1">Title 1.3.1</h3>',
  '<h1 id="title_2">Title 2</h1>',
  '<h2 id="title_2_1">Title 2.1</h2>'

  { text: 'Title 1', id: 'title_1', level: 1 },
  { text: 'Title 1.1', id: 'title_1_1', level: 2 },
  { text: 'Title 1.1.1', id: 'title_1_1_1', level: 3 },
  { text: 'Title 1.2', id: 'title_1_2', level: 2 },
  { text: 'Title 1.3', id: 'title_1_3', level: 2 },
  { text: 'Title 1.3.1', id: 'title_1_3_1', level: 3 },
  { text: 'Title 2', id: 'title_2', level: 1 },
  { text: 'Title 2.1', id: 'title_2_1', level: 2 },

tocObj(html, { min_depth: 2 });
  { text: 'Title 1.1', id: 'title_1_1', level: 2 },
  { text: 'Title 1.1.1', id: 'title_1_1_1', level: 3 },
  { text: 'Title 1.2', id: 'title_1_2', level: 2 },
  { text: 'Title 1.3', id: 'title_1_3', level: 2 },
  { text: 'Title 1.3.1', id: 'title_1_3_1', level: 3 },
  { text: 'Title 2.1', id: 'title_2_1', level: 2 },

tocObj(html, { max_depth: 2 });
  { text: 'Title 1', id: 'title_1', level: 1 },
  { text: 'Title 1.1', id: 'title_1_1', level: 2 },
  { text: 'Title 1.2', id: 'title_1_2', level: 2 },
  { text: 'Title 1.3', id: 'title_1_3', level: 2 },
  { text: 'Title 2', id: 'title_2', level: 1 },
  { text: 'Title 2.1', id: 'title_2_1', level: 2 },

tocObj('<h1 id="reference" data-toc-unnumbered="true">Reference</h1>')
  { text: 'Reference', id: 'reference', level: 1, unnumbered: true }

truncate(str, [options])

Truncates a given text after a given length if text is longer than length. The last characters will be replaced with the omission option for a total length not exceeding length.

Option Description Default
length Max length of the string 30
omission Omission text ...
separator truncate text at a natural break
truncate('Once upon a time in a world far far away')
// "Once upon a time in a world..."

truncate('Once upon a time in a world far far away', {length: 17})
// "Once upon a ti..."

truncate('Once upon a time in a world far far away', {length: 17, separator: ' '})
// "Once upon a..."

truncate('And they found that many people were sleeping better.', {length: 25, omission: '... (continued)'})
// "And they f... (continued)"


Unescapes HTML entities in a string.

unescapeHTML('&lt;p&gt;Hello &quot;world&quot;.&lt;&#x2F;p&gt;')
// <p>Hello "world".</p>

url_for(path, [option])

Returns a url with the root path prefixed. Output is encoded automatically. Requires bind(hexo).

Option Description Default
relative Output relative link Value of config.relative_link
root: /blog/ # example
// /blog/a/path

Relative link, follows relative_link option by default e.g. post/page path is '/foo/bar/index.html'

relative_link: true
// ../../css/style.css

/* Override option
 * you could also disable it to output a non-relative link,
 * even when `relative_link` is enabled and vice versa.
url_for('/css/style.css', {relative: false})
// /css/style.css


Following utilities require bind(hexo) / bind(this) / call(hexo, input) / call(this, input) to parse the user config when initializing:

Below examples demonstrate different approaches to creating a helper (each example is separated by /******/),

// Single function
const url_for = require('hexo-util').url_for.bind(hexo);

hexo.extend.helper.register('test_url', (str) => {
  return url_for(str);

// Multiple functions
const url_for = require('hexo-util').url_for.bind(hexo)

function testurlHelper(str) {
  return url_for(str);

hexo.extend.helper.register('test_url', testurlHelper);

// Functions separated into different files.
// test_url.js
module.exports = function(str) {
  const url_for = require('hexo-util').url_for.bind(this);
  return url_for(str);

// index.js
hexo.extend.helper.register('test_url', require('./test_url'));

// approach also works
const {url_for} = require('hexo-util');
module.exports = function(str) {
  return, str);

hexo.extend.helper.register('test_url', require('./test_url'));

// Separating functions into individual files
// Each file has multiple functions
// test_url.js
function testurlHelper(str) {
  const url_for = require('hexo-util').url_for.bind(this);
  return url_for(str);

module.exports =  {
  testurlHelper: testurlHelper

// index.js
hexo.extend.helper.register('test_url', require('./test_url').testurlHelper);