The Rules
Five simple rules you can count on when making templates for a Harp Application.
Rather than offering a complex feature set, Harp has simple rules on how it works. Harp is a katana, not a swiss army knife. By understanding the rules, you'll know how to use Harp effectively.
Convention over Configuration.
Harp will function with as little as a single
index.html(orindex.ejs, orindex.md) file and doesn't require any configuration to get going. To add more routes, just add more files. All of Harp's features are based on conventions you'll discover by learning the rest of these rules.Harp is about offering a sane development framework that follows established best practices. Harp is not designed to be everything to everyone, but what it does, it does perfectly.
Design Rationale
By using convention over configuration, Harp is easier to learn, which makes you more productive.
Anatomy of a typical Harp application
mysite/ <-- the directory you point harp at |- _harp.json <-- optional config; globals, basic auth, env-var substitution |- _layout.ejs <-- optional layout, wraps your pages |- index.ejs <-- must have an index file (.html, .ejs, .md, or .jade) |- about.md <-- prose pages can be Markdown |- _partials/ <-- arbitrary directory for shared partials | `- _nav.ejs <-- partial for navigation `- articles/ <-- nested directory; pages here have "/articles/" in URL |- _data.json <-- per-directory metadata `- hello-world.md <-- a content pageRun with
harp ./mysiteto serve locally, orharp ./mysite ./wwwto compile to a directory of static files. See the CLI reference for more.The directory you serve is the root.
Harp is a web server. Whatever directory you point it at becomes the served root — its name doesn't matter (
public/,src/,mysite/, anything works). The simplest possible Harp application is a singleindex.htmlin any folder:mysite/ `- index.html <-- will be served at /Drop a
_harp.jsoninto that same directory when you want site-wide globals, HTTP Basic Auth, or$ENV_VARsubstitution. Because the filename starts with_, the config file isn't itself served (see Rule 3).Framework Style (legacy)
Older Harp applications use a parent directory containing a
harp.jsonfile alongside apublic/subdirectory. Whenharp.jsonis present at the top level, Harp serves thepublic/subdirectory rather than the project root, and assets outsidepublic/aren't served.project-root/ |- harp.json <-- legacy config file |- README.md <-- won't be served (outside public/) |- secrets.txt <-- won't be served `- public/ <-- served root `- index.htmlBoth styles still work. New projects should prefer the simpler form above: a single source directory, with an optional
_harp.jsondirectly inside it.Ignore those which start with underscore.
Any files or directories that begin with an underscore are ignored by the server and excluded from the compiled output. This is how Harp marks layouts, partials, metadata, and configuration as internal.
Design Rationale
By having a simple convention, it's easy to specify and identify which assets will not be served to the end user.
Example
mysite/ |- _harp.json <-- won't be served (config) |- _layout.ejs <-- won't be served (layout) |- index.ejs <-- will be served at / |- about.md <-- will be served at /about `- _partials/ <-- won't be served (entire directory) `- _nav.ejsDead simple asset pipeline.
Harp compiles preprocessor source files to standard web output on the fly. Just add the source extension to your filename — no configuration, no build step.
myfile.ejs -> myfile.html myfile.md -> myfile.html myfile.jade -> myfile.html myfile.scss -> myfile.css myfile.sass -> myfile.css myfile.less -> myfile.css myfile.styl -> myfile.css myfile.coffee -> myfile.js myfile.cjs -> myfile.js myfile.jsx -> myfile.js.cjsand.jsxgo through esbuild, so modern JavaScript and JSX work without any configuration.If you like, you may explicitly specify the output extension by prefixing the source extension with the desired one:
myfile.jade -> myfile.html myfile.xml.ejs -> myfile.xmlThis is optional; every source extension has a default output type. Both of the following serve at
myfile.css:myfile.less -> myfile.css myfile.css.less -> myfile.cssFlexible metadata.
Files named
_data.jsonare special and make data available to templates in the same directory.mysite/ |- index.ejs `- articles/ |- _data.json <-- articles metadata |- hello-world.md <-- hello world article `- hello-brazil.md <-- hello brazil articleYour
_data.jsonfile may contain something like this:{ "hello-world": { "title": "Hello World", "date": "2026-02-28" }, "hello-brazil": { "title": "Hello Brazil", "date": "2026-03-04" } }Because the
hello-worldkey matches the filenamehello-world.md, those variables are attached to that page’s render context. They’re reachable from the wrapping_layout.ejsand from any other template viapublic.articles._data. (For.ejsand.jadepages, the values are also usable directly inside the page body as<%= title %>; markdown bodies don’t interpolate variables — see Metadata for details.)The whole metadata object is also available globally as
public.articles._data. That's how an index page enumerates the entries — for example, in EJS:<% for (var slug in public.articles._data) { %> <% var article = public.articles._data[slug] %> <a href="/articles/<%= slug %>"> <h2><%= article.title %></h2> </a> <% } %>