
Security News
Meet Socket at Black Hat Europe and BSides London 2025
Socket is heading to London! Stop by our booth or schedule a meeting to see what we've been working on.
An elegant and easy-to-use, file-based content management system for static pages
cms provides great flexibility without having to handle complicated installation steps or fighting with databases. It provides you all essentials, so you can focus on your contents. Regardless of whether you want to publish articles, galleries, simple pages, or big sized pages.
npm install --save-dev cms
const path = require('path');
const cms = require('cms');
cms({
paths: {
output: path.resolve(__dirname, 'build')
}
}).render().then(() => {
console.log('Done.');
}).catch((error) => {
console.error(error);
});
cms(…).render()Renders the page(s) based on your configuration
Returns: Promise
cms(…).get()Returns the genesis page based on your configuration for further processing (e.g. headless use)
Returns: Page
cms(…).config()Returns the merged configuration (= defaults + your custom configuration)
Returns: Object
npm install -g cms
cms
Content creation can be done very quick and easy. For each page, a folder containing a text file is placed within the content folder. While the folder names form the URLs, the name of the text file determines which template is used by the system. Using numbers as prefixes in front of the folder name, you’re able to order/sort the pages. In addition, those prefixes decide whether a page is 'visible' or 'invisible' – this may be used to control which sub pages are listed in menus, for example (see 'Visible and invisible pages' below for more details).
All your site’s content is located in the content folder. The structure of your site will be identical to the structure inside this folder. So, if you have a projects folder inside your content folder, your site will automatically have a http://example.com/projects page.
The content text files are divisible by a YAML-ish syntax into any number of fields. Those fields allow you an unlimited use of the API in templates to display or to control the output of the content. The data structure is modifiable at any time. In addition, the flexibility of the data structure allows creating pages that require a variety of different content types and templates.
You can put as many subfolders inside of folders as you like to build the structure of your site.
You may recognize that some of the folders in the content folder have numbers prepended to their names…
1-projects2-about-us3-contact…while others don’t…
imprinterrorThe idea behind this: folders with numbers are 'visible' pages, folders without numbers are 'invisible' pages. This may sound a bit weird at first glance, but the difference between those page types is pretty easy: only 'visible' pages will appear in your site’s menu later, while you can still link to 'invisible' pages (but they won’t appear in your menu).
In addition, those numbers in front of visible pages are used by cms to sort pages. This makes it easy to setup a site’s menu at the same time as setting up the general structure. All numbers are automatically stripped in URLs, thus the folder 1-projects will nevertheless have the URL http://example.com/projects in the end.
Each folder inside the content folder has a text file in it, which holds all the content for that page. This file may be called page.md (or post.txt or …). Those text files are very easy to read/edit and still offer amazing possibilities to add content. Have a look at the following example (i.e. an example for a blog post).
Title: Hello world
-----
Text: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
-----
Date: 2017-04-01T13:37:00
As you can see, each field has a name (which needs to consist of characters from A-Z and 0-9 without whitespaces or other fancy characters as it will be casted to camelCase anyways) followed by its content. You have to add five dashes after each field and that’s it.
To structure things a bit more clear, you may want to use additional line breaks (any line breaks at the start and/or end will be trimmed automatically anyways).
Title: Hello world
----
Text:
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
----
Date: 2017-04-01T13:37:00
By the way: if you want to do so, you can change the default file’s name as well as the separator characters (colon, five dashes) in your site’s configuration. See 'Configuration' below for details.
To cut a long story short: You should always make sure to enter your text as UTF-8. This makes everything a lot easier and every decent text editor has support for UTF-8.
cms makes it super easy to add images, videos, sounds, or documents to your pages. Simply drop them into the folder for each page.
(You can sort files by prepending those numbers, just like pages.)
Just like page content files, you may also create file content files, containing stuff like titles or captions. Simply add a text file for each file matching the full name of the file, followed by your content file extension (i.e. test.jpg.md for the file test.jpg). Inside those text files you can define your own fields and content, just like regular content files.
Title: Very nice image
----
Caption: This is a very nice image with loads of colors and stuff.
(cms will automatically fetch this data from the matching text files and add them to your file object, which you can access in the templates later.)
The following object dump represents the properties that are passed into the template engine next to globals and addons for every page.
Page {
file: '/Users/johndoe/Repositories/my-fancy-website/content/home.md',
parent: undefined,
genesis: Page,
index: 0,
visible: false,
invisible: true,
identifier: '',
url: '/',
output: '/Users/johndoe/Repositories/my-fancy-website/build/index.html',
template: 'home',
children: [
Page,
Page,
…
],
hasChildren: true,
files: [
File,
File,
…
],
hasFiles: true,
images: [
Image,
Image,
…
],
hasImages: true,
videos: [
File,
File,
…
],
hasVideos: true,
sounds: [
File,
File,
…
],
hasSounds: true,
documents: [
File,
File,
…
],
hasDocuments: true,
title: 'Lorem ipsum dolor sit amet',
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.'
}
parent may include a reference to the current page’s parent page. This may be useful for stuff like breadcrumb navigations. In case the page is a first level page, parent is simply undefined.
genesis may include a reference to the root page. This may be useful for stuff like navigations (i.e. show all first level pages). In case the page is the first level page itself, genesis is simply a reference to this (= the page instance itself).
children may contain any direct child pages of the current page. This may be useful for stuff like navigations. In case the page has no children, children will be an empty array.
images, videos, sounds, and documents may contain any matching files of the current page. files contains all files combined. In case the page does not contain any files, these properties will be empty arrays.
title and text are exemplary custom properties taken from the content file.
Using virtual pages, you can combine the file-based architecture with content from any other data source like APIs or databases. Any page that doesn’t exist in the content folder is called a »virtual page«. You can add virtual pages to any existing page (even the genesis page) using the addVirtualPage method which accepts properties containing all necessarry page properties.
The following properties are expected by the system and can be extended using any amount of custom properties (e.g. title, text, …).
index (Default: 0)While number prefixes in front of folder names are used for ordering/sorting pages and defining their visibility for file-based pages, you may want to define the index explicitly for virtual pages.
identifier (required)While the nested folder names of its text file determine the identifier of a page (and hence its URL), you have to define the full identifier explicitly for virtual pages.
template (required)While the name of its text file determines which template is used by the system for file-based pages, you have to define the template name explicitly for virtual pages.
const instance = cms({
…
});
const genesis = instance.get();
const projects = genesis.findPageByUrl('/projects');
const virtualProject = projects.addVirtualPage({
identifier: 'projects/a',
template: 'project',
title: 'This is a virtual project',
text: 'Lorem ipsum',
});
const somethingElse = virtualProject.addVirtualPage({
identifier: 'projects/a/a',
template: 'project',
title: 'This is a virtual project within another virtual project',
someWeirdFieldName: 'Test'
});
basepath(url)Callback function which prepends the base option (see below) to the specified url. This may be useful if you’re planning to run your static site within a sub directory.
Example:
<link href="<%= basepath('/css/style.css') %>" rel="stylesheet">
has(key)Returns a boolean indicating whether the current page has the specified property. This may be useful if you’re working with dynamic page properties.
Example:
<% if (has('description')) { %>
<p><%= description %></p>
<% } %>
get(key, defaultValue)Returns the specified property (or the specified default value if it does not exist) for the current page. This may be useful if you’re working with dynamic page properties.
Example:
<% ['title', 'description'].forEach((prop) => { %>
<p><%= get(prop, 'Something') %></p>
<% }) %>
findPageByUrl(url, context)Searches the page tree (starting at context, which equals the genesis page by default) recursively for the the page that has the URL url. This may be useful if you need to use properties of other pages located somewhere else in the page tree.
Example:
<% const contactPage = findPageByUrl('/contact') %>
<a href="<%= contactPage.url %>"><%= contactPage.title %></a>
Shortcodes let you do nifty things with very little effort by allowing you to create macros to be used in your page contents. A trivial shortcode example may look like this.
(youtube: jNQXAC9IVRw width: 480 height: 360)
The example above shows a basic shortcode to embed a YouTube video. The actual embedment is done by an appropiate handler, called everytime the shortcode is used.
{
youtube: (attrs) => {
return `<iframe src="https://www.youtube.com/embed/${attrs.youtube}"${attrs.width ? ` width="${attrs.width}"`: ''}${attrs.height ? ` height="${attrs.height}"`: ''}></iframe>`;
}
}
In addition, the page object of the current page (which includes/invokes the shortcode) gets passed into the shortcode handler function as its (optional) second parameter. This allows shortcodes to interact with the page context.
{
photo: (attrs, page) => {
const photo = page.images.find((item) => item.identifier === attrs.photo);
if (photo) {
return `
<figure>
<img src="${photo.url}" alt="${photo.title}">
${photo.caption ? `<figcaption>${photo.caption}</figcaption>` : ''}
</figure>
`;
}
return '';
}
}
CMS uses a sane configuration by default that should cover most use cases. However, if you would like to adjust/extend the configuration, you can either create a file called cms.js within the root directory (where you’re running cms in) which exports the configuration object or pass the configuration object into your cms function call. In both cases, cms expects to receive a proper JavaScript object containing some of the following properties.
templateFunction which is called using the parameters file (i.e. the path of the template) and data (i.e. locals) and which is supposed to return a Promise that resolves with the compiled template. This is the place where you would implement your preferred/custom template engine.
(file, data) => Promise.resolve(template(fs.readFileSync(file, 'utf8'))(data))
You may want to use consolidate, a template engine consolidation library.
Default: A Promise-ified version of Lodash’s template function
permalinkCallback function which is called using the parameters permalink (i.e. the plain page permalink) and which is supposed to decorate and return the permalink for a page. This may be useful in case you’re not able to rewrite URLs.
Default:
(permalink) => `${permalink}`
Example:
(permalink) => `${permalink}/index.html`
basePrefix that is prepended to all links (e.g. pages, files, …). This may be useful if you’re planning to run your static site within a sub directory.
Default: ∅
Example: /wiki
paths.contentPath to the directory containing all your content.
Default: path.resolve(process.cwd(), 'content')
paths.templatesPath to the directory containing all your templates.
Default: path.resolve(process.cwd(), 'templates')
paths.outputPath to the directory where cms is supposed to save the static build to.
Default: path.resolve(process.cwd(), 'output')
separators.linePattern that is used to separate lines/blocks within your content files.
Default: -----
separators.valuesPattern that is used to separate keys and values within your content files blocks.
Default: :
extensions.contentArray of extensions your content files may use.
Default:
[
'md'
]
extensions.templatesArray of extensions your template files may use. In case you’re using a custom template engine, you most certainly will need to set the appropriate extensions here.
Default:
[
'tpl'
]
extensions.imagesArray of extensions, your images within page content directories may use. These extensions are used to find matching images for each page (i.e. the images property).
Default:
[
'jpg',
'jpeg',
'gif',
'png',
'webp'
]
extensions.videosArray of extensions, your videos within page content directories may use. These extensions are used to find matching videos for each page (i.e. the videos property).
Default:
[
'mpg',
'mpeg',
'mp4',
'mov',
'avi',
'flv',
'ogv',
'webm'
]
extensions.soundsArray of extensions, your sounds within page content directories may use. These extensions are used to find matching sounds for each page (i.e. the sounds property).
Default:
[
'mp3',
'wav',
'm4a',
'ogg',
'oga'
]
extensions.documentsArray of extensions, your documents within page content directories may use. These extensions are used to find matching documents for each page (i.e. the documents property).
Default:
[
'pdf',
'doc',
'xls',
'ppt',
'docx',
'xlsx',
'pptx'
]
extensions.outputExtension of static output files.
Default: html
globalsObject containing globals that will be passed into the template function next to the regular page data. This may be useful if you need to make data available to all pages. The object is deep-merged into the regular page data.
Default: {}
addonsObject containing globals that will be passed into the template function next to the regular page data. This shall be used for all custom functions you would like to provide within your templates.
Default: {}
Example:
{
markdown: (input) => marked(input),
reverse: (input) => input.split('').reverse().join('')
}
shortcodesObject containing shortcode handlers that will be applied to page data. This shall be used to register any custom shortcode you would like to provide within your content.
Default: {}
Example:
{
youtube: (attrs) => {
return `<iframe src="https://www.youtube.com/embed/${attrs.youtube}"${attrs.width ? ` width="${attrs.width}"`: ''}${attrs.height ? ` height="${attrs.height}"`: ''}></iframe>`;
}
}
Currently I’m working on the following for v2.
has and getrender method and add get/config methods to allow headless usecontext parameter for shortcode helperbase prefix and permalink callback to findPageByUrl helper queries automaticallybasepath to only prepend the base path to an URL in case it isn’t there yetbasepath and findPageByUrlgenesis is now a reference to this (= the page instance itself) instead of undefinedSpecial thanks to Thom Blake for handing over the cms package name on npm to me. Please check out thomblake/cms in case you’re looking for the code of versions <1.0.0.
Copyright (c) 2025 Thomas Rasshofer
Licensed under the MIT license.
See LICENSE for more info.
FAQs
An elegant and easy-to-use, file-based content management system for static pages
The npm package cms receives a total of 434 weekly downloads. As such, cms popularity was classified as not popular.
We found that cms demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

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.

Security News
Socket is heading to London! Stop by our booth or schedule a meeting to see what we've been working on.

Security News
OWASP’s 2025 Top 10 introduces Software Supply Chain Failures as a new category, reflecting rising concern over dependency and build system risks.

Research
/Security News
Socket researchers discovered nine malicious NuGet packages that use time-delayed payloads to crash applications and corrupt industrial control systems.