🚧 The reference section is under development and should in no way, shape, or form, be considered to be exhaustive at the moment.


Kitten is a little kit (“kitten”, get it?) optimised for people (not corporations) who make the new everyday things for themselves and other people – not because they want to become billionaires but because they want to contribute to the common good by helping nurture the Small Web while adhering to the Small Technology Principles.

Kitten features intuitive file system-based routing, HTML and accessibility validation, a built-in object database, WebSockets, zero-code authentication, public-key cryptography, Markdown support, syntax highlighting via highlight.js, semantic styles via Water CSS, and more.

System requirements

💡 macOS comes with an ancient version of Bash. To upgrade it to a modern one, run brew install bash using Homebrew.

💡For production servers, only Linux with systemd is supported.


Linux and macOS


💡If you’re running an “immutable” Linux distribution like Fedora Silverblue, please install Kitten from your host account (instead of from within a container) at least once so Kitten can set unprivileged ports to start from 80 (so it can run without elevated privileges).


🙀 Windows is an ad-infested and surveillance-ridden dumpster fire of an operating system and you are putting both yourself and others at risk by using it.

If you are not forced to use it for reasons outside your control, please do not use it.

The only reason Windows is currently supported is because I used to be on Windows myself for many years (I started with DOS at age 7) and if it hadn’t been for free and open applications supporting Windows I would have had a much harder time eventually migrating off of it to safer platforms (first macOS and then Linux). That said, Microsoft’s latest attempt to create the Torment Nexus, Recall, has me reevaluating this decision.

Don’t be surprised if Windows support is dropped in the future.

Kitten runs on Windows 10 and 11 under WSL 2.

The installation process, however, is not as seamless as it is on Linux and macOS. You must first install WSL 2 and you must manually add Kitten’s local development-time certificate authority to the trust stores of your Windows browsers to avoid certificate errors.

For step-by-step instructions, please see:

Install Kitten on Windows under WSL 2.


You can update Kitten in a variety of ways:


Kitten has a semi-forgiving HTML parser.

Request and response helpers

Kitten has a number of request and response helpers defined to make your life easier.

💡 You use two of them, response.forbidden() and response.get() in the authentication tutorial.



Markdown support

Kitten’s Markdown support comes from markdown-it.

Kitten supports a very rich set of Markdown features including:

You can use Markdown in your Kitten apps in a variety of ways:

Markdown pages ( files).

You can create whole pages using Markdown by placing your Markdown in files. These pages are transpiled into JavaScript before being transpiled into HTML just like regular JavaScript pages (page.js) files.

Your Markdown pages can have front matter in YAML format.

💡Kitten’s YAML parser is based on the Node yaml module and you can use it directly in your own Kitten apps by referencing from kitten.yaml.

Front matter

Kitten’s YAML front matter supports some special properties. Specifically:

layout (string)

Specifies the layout template to use for the Markdown page.

The value for the layout property is a string that contains the relative path to the layout template (.layout.js file) you want to import and use.

If the layout template is called MySpecial.layout.js, you can use it like this:

<${MySpecial} someProp=someValue someOtherProp=someOtherValue>
import (list)

You can import components and fragments and use them in your Markdown pages just as you would in regular Kitten pages.

🪤 The current limitation is that they cannot contain child content of their own – so no slots for the time being.

The import list contains JavaScript import statements in the following form:

  - import ComponentName from '../path/to/ComponentName.component.js
  - import FragmentName from '../path/to/FragmentName.fragment.js'

Once imported as shown above, you can use then using them in your Markdown page as you would normally:

# Components and fragments

This is a component:

<${ComponentName} prop=someValue anotherProp=someOtherValue />

And this is a fragment:

<${FragmentName} prop=someValue anotherProp=someOtherValue />

Custom properties in front matter

Any properties outside of the special ones listed above are passed to your page’s layout template (if it has one) as props.

Markdown fragments

You can place static pieces of Markdown into Markdown fragment files (

Unlike Markdown pages, Markdown fragments do not support front matter or template substitution. They are purely for static content.

Markdown in Kitten HTML tagged template strings

Finally, you can embed Markdown directly in your Kitten HTML tagged template string by surrounding your Markdown content between <markdown>…</markdown> tags.


export default () => kitten.html`
<h1>This is HTML</h1>
  - _This_
  - is
  - __Markdown!__
  <p>This is more HTML</p>

💡 Unlike HTML, Markdown is whitespace-sensitive so make sure you indent your code properly or it will get parsed incorrectly.

You can see some of these features demonstrated in the examples/components and examples/markdown folders of the Kitten examples, as well as in the source for the Kitten web site itself.

Deploying a Kitten application

To deploy your app as a service, you use the deploy command:

kitten deploy <httpsGitCloneURL>

For example, the following will create and run a production server of the Persisted Kitten Chat example, as hosted on my personal Codeberg account at my hostname.

kitten deploy

Updating a deployed Kitten application

There are a number of ways you may wish to update your deployed Kitten application, depending on who the audience for your app or site is.


You can set up a webhook on your remote Git source code repository that triggers on git push events and causes your deployed app to update itself to the latest commit in the main branch.

You can find your webhook URL and webhook secret on the App Settings page of your Kitten site (/💕/settings/app/).

Your Kitten site expects the webhook secret in either the Authorization header or in the body of a POST request in a field called secret (the body should be application/x-www-form-urlencoded).

This is a feature that will be useful to developers who are deploying their own sites and apps for their own use as opposed to developers making apps for use by everyday people who use technology as an everyday thing. For the latter use case, please see Versioning, below.


💡 If you want to quickly test out a Kitten app that doesn’t have versioning information, use the kitten run command instead.

You version your Small Web apps using git tags:

Tag your Small Web apps in the following format to version them:

<Kitten API Version>.<App Version>


To tag your app, simply use the git tag command as usual, e.g.,

git tag -s 1.3

The above tag will label your current commit as being compatible with Kitten API Version 1 and having an app version of 3.

If a deployed version of your app is running version 1.2, it will not be updated when you push commits to your main branch, etc. It will also not update the installed version of Kitten if, say, a new Kitten package is released that has an API Version of 2. Kitten will only be upgraded to the latest version when you release a version of your app that’s tagged with support for Kitten API Version 2. e.g.,

git tag -s 2.1

Automatic updates of Small Web places and of Kitten itself follows one of the core tenets of the Small Web which is that Small Web places should not require technical knowledge to maintain.

As such, updates should never a break a Small Web place and require technical intervention to fix.

This means that your Small Web apps must never break backwards compatibility. So, for example, if you have schema changes in your database, you must write migration code to give existing data the shape that the latest version of your code requires, etc.

💡 Kitten productions servers periodically and automatically check for updates to both Kitten itself and to the Kitten app that is being served. Whenever there is a breaking change to the Kitten API (e.g., an existing method is removed or how it’s used is changed in such a manner that it could break existing code), the change is documented in the change log and the Kitten API version, which is a monotonically increasing integer, is incremented by one.

To see how it works, imagine that you deploy a new server running a Kitten version that has its API version set to 1. You specify this API version in your git tag as shown above. Whenever Kitten checks for Kitten updates, it ensures that the API version is still 1 before updating Kitten. Say that after three Kitten updates, Kitten notices that the latest version of Kitten has an API version of 2 but that your app (which Kitten is also periodically checking for updates for from its git repository) is still at API version 1. At that point, Kitten does not install the latest version of Kitten but rather waits for you to test your app with Kitten API version 2 and specify that it is supported by creating a new git tag accordingly.

If there are any other Kitten packages released that support Kitten API version 1 (e.g., bug/security fixes that are backported from the latest versions of Kitten), Kitten will continue to update itself to those versions.

Once Kitten sees that you’ve updated your app to Kitten API version 2, it will go ahead and update itself to the latest package supported by that version.

So, all this to say that Kitten tries its hardest to ensure that its auto-updates system does not break your servers.

🪤 If you are using a database in your app, please remember that you are responsible for ensuring that schema changes do not break your app between versions. In other words, you are responsible for implementing migrations. To make this easier on yourself, you might want to ensure that the collections and individual data items you persist are custom classes that abstract away the actual objects that are persisted. That way, you can implement granular migrations in your model classes. Otherwise, you can, of course, also implement global, one-shot migrations in the traditional manner.

💡️ Production servers require systemd.

💡️ Kitten will not automatically create an alias for www for you, pass --aliases=www.<hostname> (or the shorthand, --aliases=www) if you want that. You can, of course, also list any other subdomains other that www that you want Kitten to serve your site/app on in addition to the main domain.

😻 If your app uses node modules, Kitten will intelligently call npm install on your project, as well as on any app modules your project might have.

🌲 Evergreen Web (404 → 307)

Kitten has built-in support for the Evergreen Web (404 to 307) technique.

To implement 404 to 307 redirection for your Small Web place in Kitten, simply set domain you want to forward to in your Small Web Settings page which you can reach in your browser at /💕/settings/.

Let’s say you are deploying your new Small Web place at your own domain (e.g., but you already have a personal web site or a blog there. You will already have content that other people might have linked to and/or rely on. It would be a shame for all those links to break when you deploy your new site.

The Evergreen Web (404 to 307) technique is very simple:

On your new site, issue a 307 redirect for any paths that result in a 404 (not found) error to the previous version of your site. That way, if the path existed in the previous version of your site, it will be found there and, if it did not, it will result in a 404 error there.

💡 Of course, you’re not limited to one level of indirection. If the previous version of your site replaced an even earlier version, there’s no reason why you cannot add a 404 to 307 rule there also and have a chain of redirects going all the way back to the earliest version of your site.

One of the great advantages of this technique is that you can just leave older versions of your site running (perhaps at different subdomains) and they can all continue to exist using whatever technologies they were using at the time. (You don’t have to take static exports, etc., of the data.)

By using 404 to 307, you will contribute to an evergreen web by not breaking URLs.

To see it in action, run the evergreen-web example.

For more information, see

Valid file types

Kitten doesn’t force you to put different types of routes into predefined folders. Instead, it uses file extensions to know how to handle different routes and other code and assets.

Here is a list of the main file types Kitten handles and how it handles them:

.page.jsKitten page (JavaScript)JavaScript route that is compiled into HTML and served in response to a HTTP GET request for the specified path.
.page.mdKitten page (Markdown)Markdown route with optional front matter that supports JavaScript imports and the use of Kitten components and fragments in Markdown that is compiled into HTML and served in response to a HTTP GET request for the specified path.
.post.js, .get.js, .put.js, .head.js, .patch.js, .options.js, .connect.js, .delete.js, .trace.jsHTTP routeServed in response to an HTTP request for the specified method and path.
.socket.jsWebSocket routeServed in response to a WebSocket request for the specified path. Only file type to keep part of its file extension in its route pattern (e.g., the file /my.socket.js exists at the URL fragment /my.socket.)
.component.jsA component file, returns HTMLIgnored by router.
.layout.jsLayout component, returns HTMLIgnored by router.
.fragment.jsA fragment file, returns HTMLIgnored by router.
.fragment.htmlAn HTML fragment fileIgnored by router.
.fragment.cssA CSS fragment fileIgnored by router.
.fragment.mdA Markdown fragment fileIgnored by router.
.script.jsServer-side script fileIgnored by router. Useful for including server-side JavaScript modules. File is not accessible from the client.
.styles.jsServer-side styles fileIgnored by router. Useful for including server-side CSS (in JS). File is not accessible from the client.
Other (.html, .css, .js, .jpg, .gif, etc.)Static filesAny other files in your project apart from the ones listed above are served as static files.

HTTP routes

HTTP data routes are served in response to an HTTP request for the specified method and path.

All HTTP request methods are supported.

You create an HTTP route by create a JavaScript file named with the HTTP request method you want to respond to.

For example, to respond to GET requests at /books, you would create a file named books.get.js in the root of your source folder.

The content of HTTP routes is an ESM module that exports a standard Node route request handler that takes http.IncomingMessage and http.ServerResponse arguments.

For example, your books.get.js route might look like this:

export default ({ request, response }) => {
  const books = kitten.db.books.get()

URL normalisation

Kitten automatically normalises URLs that do not have a trailing slash when they should to add one.

It does so using a permanent 308 redirect that preserves the method of the request.

This means, for example, that a request to _ will be forwarded to _

This is both to help implement canonical paths for your sites/apps and to avoid the unexpected situation of a relative include failing from your page if a slash is not provided in the path. (e.g., if you have an index.js file and an index.html file in a folder called hello and you include the JavaScript file using <script src='./index.js'></script>, that will succeed if the page is hit as /hello/ but fail if it is hit as /hello.)


Kitten has an integrated JSDB database that’s available from all your routes as db.

JSDB is a transparent, in-memory, streaming write-on-update JavaScript database for the Small Web that persists to a JavaScript transaction log.

You can find the databases for your projects in the ~/ folder. Each project gets its own folder in there with a name based on the absolute path to your project on your disk (e.g., if a Kitten project is stored in /var/home/aral/projects/my-project, its database will be in a folder named in the main database folder.)

Tables in JSDB are simply JavaScript objects or arrays and JSDB writes to plain old JavaScript files.

You can get information about the database and specific tables, delete the whole database and specific tables, and tail specific tables using the command-line interface via the db info [tableName] (or just db [tableName], which is a convenience alias), db delete [tableName] and db tail <tableName> commands.

Learn more about JSDB.

Route parameters

You can include route parameters in your route paths by separating them with underscores and surrounding the parameter names in square brackets.

For example:


Will create a WebSocket endpoint at:


You can also intersperse path fragments with parameters:


Will compile the Kitten page and make it available for HTTP GET requests at:


So you can access the route via, say, _

You can also specify the same routes using folder structures. For example, the following directory structure will result in the same route as above:

  ╰ books
     ╰ [id]
         ╰ pages
             ╰ [page].page.js

Note that you could also have set the name of the page to index[page].page.js_. Using just [page].page.js for a parameterised index page is a shorthand.

You can decide which strategy to follow based on the structure of your app. If, for example, you could access not just the pages but the references and images of a book, it might make sense to use a folder structure:

  ╰ books
     ╰ [id]
         ├ pages
         │   ╰ [page].page.js
         ├ references
         │   ╰ [reference].page.js
         ╰ images
             ╰ [image].page.js

You may, or may not find that easier to manage than:

  ├ books_[id]_pages_[page].page.js
  ├ books_[id]_references_[reference].page.js
  ╰ books_[id]_images_[image].page.js

Kitten leaves the decision up to you.

Optional parameters

To create an optional parameter, prefix your parameter name with optional- inside the square brackets.

For example, the following route:

  ╰ index_[subdomain]_[optional-return-page].page.js

Will match both of the following paths:

The wildcard parameter

Finally, you can also use the special wildcard parameter to match any string:

 ╰ books_[any].page.js

The about route will match any path that begins with /books/.

💡 Under the hood, Kitten uses Polka for its routing and translates Kitten’s file system-based naming syntax to Polka’s routing syntax.

App Modules

When working on larger projects, you might end up with pages at varying levels within the site, all of which need to use the same global resource (for example, a layout template or a utility class).

In these situations, your import statements might start to get unruly.

Take the following example:

  ├ site.layout.js 
  ╰ books
     ╰ the-handmaids-tail
         ╰ pages

Let’s say your uses the site layout template.

Here’s what its import statement would look like:

import Site from '../../../site.layout.js'

export default () => kitten.html`

That’s both cumbersome to write and error-prone. (Especially when you consider that different pages at different levels of the hierarchy may want to use the same file. You don’t want to spend your day counting dots.)

Enter App Modules.

App Modules are special local Node modules that exist only in your app (not on npm). They live in the special app_modules folder in your project and Kitten knows to ignore them when calculating its routes from the file structure of your project.

App Modules, like all Node modules, need a package.json file but they can get away with specifying a subset of the information contained in ones for npm packages. And, like all Node modules, they need to be npm installed into your project (but from a local file path instead of from

Finally, if you have type checking enabled in your projects, you should add an index.d.ts file to your App Modules so your editor doesn’t complain when using the TypeScript Language Server (LSP).

Here’s how you’d make your site layout into an App Module:

  1. Create the app_modules directory and then a directory for your module:

    mkdir -p app_modules/site-layout
  2. Add the files your module will need:

      ├ app_modules
      │  ╰ site-layout 
      │     ├ site.layout.js
      │     ├ index.d.ts
      │     ╰ package.json
      ╰ books
         ╰ the-handmaids-tail
             ╰ pages
  3. In your package.json file, you need to specify three properties: name, type, and main:

      "name": "@app/site-layout",
      "type": "module",
      "main": "site.layout.js"
  4. Finally, if you have type checking enabled and you want to avoid type errors in your editor, your index.d.ts file should look like this:

    import SiteLayout from './site.layout.js'
    export default SiteLayout

And that’s it.

All you need to do then is to install the App Module.

From the root of your project, install your module using its local path:

npm install ./app_modules/site-layout

🪤 You should keep App Modules as simple as possible and they should not have their own dependencies. (We’re using them to simplify authoring, not to create a monorepo.) That said, if you do end up having dependencies for your App Modules, remember to npm install them separately from within their own folders as npm will not automagically do that for you.

Finally, from (and any other page, anywhere in your project) you can now import your layout using:

import Site from '@app/site-layout'

That’s much better, isn’t it?

For a real world example, see the database app module in Domain that wraps Kitten’s global JSDB instance with type information and some utility methods.

Static files

You do not have to do anything special to server static files with Kitten. It is, first and foremost, a web server, after all. Any file that’s in your project folder that isn’t a hidden file or in a hidden folder and isn’t a special Kitten file (e.g., .page.js, .socket.js, .post.js, etc.), will be served as a static file.

Reserved routes

Kitten tries to be as minimally invasive into your site or app’s own namespace as possible. That said, there are certain routes that it does reserve. Some of these are part of the Small Web protocol and others are Kitten-specific.

Reserved Small Web protocol routes

The following routes are reserved as part of the Small Web protocol, which is namespaced by the Small Web emoji/logo (💕).

They are used to implement public-key authentication and manage the communication between Small Web instances and the Domain hosts that host them:

Reserved Kitten-specific routes

Any other private Kitten-specific routes are kept in the 🐱 namespace so as not to pollute your app/site’s own URI space.

Kitten client-side libraries

Kitten comes with several client-side libraries which are automatically served at runtime that you can use in your sites/apps. Some of these have high-level interfaces for including them (see the page tag) but you can include any of them in your sites/apps manually by knowning their paths:

In /🐱/library/:

Library nameFile nameVersionNotes
Kitten cryptography modulecrypto-1.js^1
htmxhtmx-2.js^2Own fork.
htmx WebSockethtmx-ws-2.js^2
htmx idiomorph extensionhtmx-ideomorph-0.3.js^0.3.0
Prism (CSS)prism-1.css^1Used internally to syntax highlight Kitten error messages. Also see built-in Highlight.js syntax highlighter.
Water (CSS)water-2.css^2

The libraries also have minified and gzipped version (.min.gz) that are automatically used in production.

💡 The -N notation specifies the major version of the library. Since Kitten sites automatically update, if there is a major version update to a library, it will be adding alongside previous versions so as not to break existing sites. So if htmx 2.x.x comes out, for example, with breaking changes and we start supporting it, it will be added as htmx-2.js. All minor and patch updates will be automatically updated without changing the name of the include.

Command-line interface (CLI)

💡 Use kitten help to access command information from your terminal.

To get detailed help on a specific command, use kitten help <command name>. For example, kitten help serve.

You can also pass the --help or -h option to any command to get more detailed information about it. So kitten serve --help is equivalent to kitten help serve.

Default command


kitten [path to serve] [options]
kitten serve [path to serve] [options]

Serves a Kitten place.

💡 If do not specify a path to serve, the default directory (./) is assumed.


💡 When running multiple Kitten instances on different ports to test peer-to-peer functionality during development, remember to use different domains for each instance (for example, localhost for one and for the other) to ensure that sessions work correctly. This is because Kitten sessions make sure of cookies and cookies do not provide isolation by port. There is currently an issue open on Auto Encrypt Localhost to add IPs - to generated certificates so you can test up to four peers via the - loopback addresses (five, if you add localhost). If you have a need for more – perhaps for some sort of automated testing – please open an issue here, explain your use case, and we’ll consider adding more.

Also, please note that if you’re running an app at a random port, it will also get a random data directory assigned to it that’s valid for the time that the app is active. The next time it is run from a random port, it will have a different data directory so the data from the first run will not carry over. Only run apps at random ports if persistence of data between runs is not important to you. Small Web apps are not meant to be run from random ports and are meant to always be deployed in live environments from port 443 on a VPS or a dedicated device (like a single-board computer from home, for example). The random port feature exists to enable making local web apps (the equivalent of command-line applications or native apps) that run locally on a device. For an example of this, see the Markdown Preview utility in the examples folder.

Database commands

db (alias for db info)

kitten db [table name]
kitten db info [table name]

Shows database and table information.

db delete

kitten db delete [table name]

Deletes either the whole database or the table with the specified table name after asking you for confirmation.

db tail

kitten db tail <table name>

Shows a summary of the head (start) and tail (end) of your database table (remember that database tables in JSDB are append-only JavaScript logs) and starts to follow additions to it.

Press Ctrl C to exit as you would a regular shell tail command.

Deployment of Kitten applications


kitten deploy <git HTTPS clone URL> [options]

Creates a Kitten place by deploying a Kitten project from a git repository as a systemd service.



kitten run <git HTTPS clone URL> [options]

Is the little brother of the deploy command. Runs a Kitten project from a git repository locally as a regular process.

It’s options are the same as that of the default serve command.

Kitten daemon (systemd service) commands


kitten status

Shows Kitten systemd service status.


kitten logs [options]

Tails (follows) the Kitten systemd service logs using journald.



kitten start

Starts the Kitten systemd service.


kitten stop

Stops the Kitten systemd service.


kitten enable

Enables the Kitten systemd service (so it auto starts on boot and if the process exits)


kitten disable

Disables the Kitten systemd service (so it no longer auto starts on boot, etc.)

General commands

version (aliases: --version and -v)

kitten version
kitten --version
kitten -v

Displays the Kitten version, made up of:

🚧 Eventually, you will be able to tell Kitten what API version your app is written for and it will only auto-update to the latest release in that API version.

help (aliases: --help and -h)

kitten help
kitten help <command name>
kitten <command name> --help
kitten <command name> -h

Use kitten help to access command information from your terminal.

To get detailed help on a specific command, use kitten help <command name>. For example, kitten help serve.

You can also pass the --help or -h option to any command to get more detailed information about it. So kitten serve --help is equivalent to kitten help serve.


kitten update
kitten update <API version>

Use kitten update to update (upgrade or downgrade) your currently installed version of Kitten.

When you run the command without supplying the optional API version argument, Kitten will attempt to update to the more recent publicly-available Kitten package from If your version of Kitten is more recent than the most recent publicly-available Kitten package, Kitten will prompt you to ask if you want to downgrade your installation.

If you supply the optional API version argument, the update will check for the latest version for the given API version. If such an API version does not exist, you will get an error. If your version of Kitten is already on that API version but is a more recent build, you will be prompted whether you want to downgrade your installation.

💡 The only time the downgrade prompt should show is if you’re running a local development build that is more recent that the most recent publicly-available Kitten package. This is useful if you want to quickly test a publicly-available package during development. You can, of course, always install the latest development version by running the ./install script as usual.


kitten uninstall

Uninstalls Kittens and removes all Kitten data after asking for confirmation.

Install Kitten on Windows under WSL 2

Kitten runs on Windows under WSL with the following caveats:

  1. Requires WSL 2 (will not work with WSL 1).
  2. You must manually install Kitten’s local development-time certificate authority in your Windows browsers.

If you haven’t installed Kitten on Windows or used WSL 2 before, the instructions, below, take you through the whole process.


  1. Open up a Windows Powershell tab in Terminal App.

    🪤 Windows comes with a couple of terminal apps and shells. Make sure you use the exact pair mentioned above.

  2. Install WSL 2:

    wsl --install

    This will install WSL 2 and Ubuntu. It will ask you to choose an account name and set a password and then you will be running Ubuntu under WSL2.

    Kitten only works with WSL 2 using Windows Terminal and Windows Powershell (the sysctl command used by the installer fails on systems with WSL 1).

    To make sure your Linux container is running under WSL 2 (and not 1) before continuing, run:

    uname -a

    On my system, that gives me the following system information:

    Linux aral-win11 #1 SMP Fri Jan 27 02:56:13 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

    Note that the string WSL2 is contained in the output. If you don’t see that, you’ll have to either create a new container using WSL 2 or update your current container to use WSL 2.

    💡 For best results, please make sure you’re running the latest Ubuntu version that’s installed automatically by wsl when you run the wsl --install command.

    (If you’re running a very old version of Ubuntu, it may not have the new Let’s Encrypt certificate authority in its trust store and the Kitten installer may fail with a certificate error. If this happens, please either upgrade your version of Ubuntu or try tunning sudo dpkg-reconfigure ca-certificates to download the latest list of trusted certificate authorities into the system store.)

  3. Install Kitten:

    wget -qO- | bash

    (Enter the password you set up for your Ubuntu installation under WSL 2 when asked.)

  4. Source your .profile to add Kitten’s binary to your path:

    source .profile

    (Kitten is now installed properly but Ubuntu doesn’t add the ~/.local/bin folder that Kitten puts its binary into to your system path until that path exists on your computer. Instead of sourcing the .profile, you can also log out and log back in.)

  5. Test the Kitten binary.

    kitten --version

    You should see Kitten launch and the version get displayed.

    💡If Kitten fails to launch, please open an issue and try to give as much information about what went wrong as you can. Copy and paste any error messages you see. Also run uname -a and lsb_release --all and paste the output from those commands into your issue.

  6. Create your first project and run Kitten.

    ❣️ Don’t skip this step! The first time Kitten runs, it creates your local development-time certificate authority (CA) and TLS certificate. You will need the CA to install in your Windows browsers in the next section.

    mkdir hello-kitten
    cd hello-kitten
    echo 'Hello, Kitten!' > index.html

    Kitten should launch and start serving your site.

    However, if you open a web browser under Windows (e.g., the default Edge browser), you will see a NET::ERR_CERT_AUTHORITY_INVALID security error warning you that your connection isn’t private.

    This is because Kitten is running under Ubuntu and your browser is running under Windows. While Kitten can (and does) update your Linux system trust store to accept its local development certificates, it cannot do that for your Windows machine because it doesn’t even know that it exists.

    So, on Windows, you have to manually install Kitten’s certificate authority in your Windows browsers.

    The next section takes you through doing that.

Manually install the certificate authority in your Windows browsers.

For browsers you have installed under Windows to accept the TLS certificates created by Kitten, you must install its certificate authority into your browsers. (Remember that Kitten is running under Linux and doesn’t know anything about your Windows environment so it can’t do it for you.)

For example, if you’re using Microsoft Edge:

  1. Go to edge://settings/privacy, scroll down to the Security section, and select “Manage certificates.”

    Screenshot of Settings page in Microsoft Edge.
  2. In the resulting “Certificates” modal, select the Trusted Root Certificate Authorities tab and press the Import… button.

    Screenshot of the Certificates modal showing the empty Personal tab selected
  3. In the resulting Certificate Import Wizard, press Next to pass from the welcome screen to the File to Import step and press the Browse… button.

    Screenshot of the File to Import stage of the Certificate Import Wizard with an empty File name textbox and the Browse… button mentioned in the text.
  4. Make sure the file extensions drop-down is set to show all extensions and select Linux → Ubuntu → home → (your home directory) → .local → share → → kitten → tls → local → auto-encrypt-localhost-CA.pem.

    Screenshot of the file dialogue showing the auto-encrypt-localhost-CA.pem certificate selected.
  5. In the next step, make sure “Place all certificates in the following store” is selected with “Trusted Root Certification Authorities” set.

    Screenshot showing the Certificate Import Wizard with the settings mentioned in the step applied.
  6. Press Next and, on the final screen, press Finish.

    Screenshot of Certification Import Wizard: Completing the Certificate Import Wizard. The certificate will be imported after you click Finish. (Contains a summary of the selected certificate settings.)
  7. After the wizard closes, you will see a Security Warning telling you that you are about to install a root certificate. Select Yes to continue.

    Screenshot of Security Warning modal: “You are about to install a certificate from a certification authority (CA) claiming to represent: Localhost Certificate Authority for aral at aral-win11. Windows cannot validate that the certificate is actually from …”

Finally, restart Edge and you should be able to hit https://localhost without certificate errors.

Now that you’ve successfully installed Kitten on Windows under WSL 2, you can continue learning about Kitten by following the Getting Started guide and tutorials.


Here are some edge cases you might encounter (because others have encountered them) and what you can do about it:

Linux: Untrusted localhost TLS certificates in Chrom(ium)

If at some point Chrom(ium) starts complaining that your development-time localhost certificates are untrusted under Linux, check that you haven’t accidentally installed a copy of ca-certificates and/or p11-kit in your unprivileged account using a third-party package manager like Homebrew (brew).

If this is the issue, you should:

  1. Remove the ca-certificates and p11-kit packages from Homebrew (you will also have to remove any packages that depend on them. e.g., python, OpenSSL, etc.)
  2. Restart your machine.
  3. Run sudo update-ca-trust extract
  4. Uninstall Kitten (kitten uninstall)
  5. Reinstall Kitten via the one-line installation command or from your working copy of the source code (./install)

That should do it.

To check if your system trust store is set up correctly, you can run the following command:

trust list --filter=ca-anchors | grep Localhost

And you should see output that resembles the following:

label: Localhost Certificate Authority for aral at

Building Kitten

While developing Kitten, it’s best practice to run the install script and use the kitten command to run your installed build.


A typical run of the install commands takes about half a second on a modern computer so it should not impact your development velocity negatively.

💡 In order to keep the development build/install process as quick as possible, dependencies are not updated unless you specifically request an npm install by passing the --npm flag:

./install --npm

(However, an npm install will be carried out if this is the first time you’re building/installing Kitten locally.)

There is a separate build command (called internally by the install script) and if you use that, you will find the distribution under the dist/ folder.

To run Kitten from the distribution folder, use the following syntax:

dist/kitten [path to serve]

💡 Kitten’s build + install process (what happens when you run the install script) takes less than half a second on a modern computer and has the additional benefit of informing you of compile-time errors. It’s highly recommended you don’t run build by itself or run from the dist folder directly unless you have specific reason to.


To run Kitten with the Node debugger active (equivalent of launching Node.js using node --inspect), start Kitten using:

INSPECT=true kitten

💡 If you use VSCodium, you can add breakpoints in your code and attach to the process using the Attach command in the Run and Debug panel.

💡 In Chromium, you can use the Node debugger (enter chrome://inspect in address bar → select ‘Open dedicated DevTools for Node’).


You can see profiling information provided by Kitten’s console mixin methods profileTime() and profileTimeEnd() by passing the PROFILE=true environment variable:


PROFILE=true kitten examples/streamiverse

Flame Graphs

To narrow down performance issues by time spent on the stack, you can have Kitten generate a flame graph by setting the FLAME_GRAPH environment variable to true.


FLAME_GRAPH=true kitten examples/streamiverse

This will start your project in production mode and globally install and use the 0x node module to capture profiling information and automatically launch a browser when the process exits to show you the flame graph.

💡 Flame graphs can only be generated in production mode as the 0x module is not compatible with Node’s cluster module and the latter is what we use to implement Kitten’s process manager in development mode.


Tests are written in Tape With Promises, run using ESM Tape Runner, and displayed using Tap Monkey.

Coverage is provided by c8.

Run tests:

npm -s test

Run coverage:

npm run -s coverage

💡️ The -s just silences the npm logs for cleaner output.

🚧 Tests are in the process of being ported from NodeKit to Kitten.

Deployment (of Kitten itself)

To build and deploy a new Kitten package to,run the deploy script:


💡You will need to add the secret deployment token to your system in order to deploy. If you have deployment rights for Kitten, follow the instructions available at

Technical design

Kitten is an ESM-only project for Node.js and relies on (the currently experimental) ES Module Loaders (follow the latest work) functionality.

Additionally, Kitten relies on a number of core dependencies for its essential features.

Core dependencies

@small-tech/httpsDrop-in replacement for Node’s native https module with automatic TLS for development and production using @small-tech/auto-encrypt and @small-tech/auto-encrypt-localhost.
@small-tech/jsdbZero-dependency, transparent, in-memory, streaming write-on-update JavaScript database that persists to JavaScript transaction logs.
Polka@nextNative HTTP server with added support for routing, middleware, and sub-applications. Polka uses Trouter as its router.
tinywsWebSocket middleware for Node.js based on ws.
xhtmXHTM is alternative implementation of HTM without HTM-specific limitations. Low-level machinery is rejected in favor of readability and better HTML support.
hyperscript-to-html-stringRender xhtm/Hyperscript to HTML strings, without virtual DOM.
sanitize-htmlClean up untrusted HTML, preserving whitelisted elements and whitelisted attributes on a per-element basis. Built on htmlparser2 for speed and tolerance
node-git-serverGit server for hosting your source code. Used in deployments.
isomorphic-gitGit client used in deployments on development and for handling auto-updates on production.
sadeA small command-line interface (CLI) framework that uses mri for its argument parsing.
noble-ed25519Cryptography: ed25519 key generation.
ed25519-keygenCryptography: SSH key generation from ed25519 key material.

Kitten’s renderer is built upon xhtm and vhtml although both of those libraries have been inlined and extended.

For an automatically-generated full list of modules and contributors, please see

Cryptographical properties

(The functionality described in this section is currently being developed both in Domain and Kitten.)


The security (and privacy) of Domain/Kitten are based on a 32-byte cryptographically random secret string that only the person who owns/controls a domain knows.

(This is basically an ed25519 secret key.)

When setting up a Small Web app via Domain, this key is generated in the person’s browser, on their own computer, and is never communicated to either the Domain instance or the Kitten app being installed. Instead the ed25519 public key is sent to both and signed token authentication is used when the server needs to verify the owner’s identity (e.g., before allowing access to the administration area).

The expected/encouraged behaviour is for the person to store this secret in their password manager of choice.

From this key material, we derive SSH keys and the person’s server is set up to allow SSH access via this key.

Kitten running on a development machine can also recreate these SSH keys and configure the person’s SSH access to their server when the secret key is provided.

The person’s ed25519 public key is used as their identity within the system and enables their Small Web place (Kitten app) to communicate with the Domain instance for administrative reasons (e.g., to cancel hosting, etc.)

Threat model

The audience for Domain/Kitten and the Small Web in general is everyday people who use technology as an everyday thing and want privacy by default in the systems they use.

If you are an activist, etc., who might be specifically targetted by nation states, etc., please do not use this system as it does not protect against, for example, infiltration of hosting providers by nation state actors.

Given that the secret key material is generated in a web app, you must trust that the code being served by the server is what you expect it to be. Currently, there is no validation mechanism for this, although this is on the roadmap going forward (via, for example, a browser extension). This is not a problem unique to web apps, given that native apps are commonly dynamically built by app stores (at which point a nation state actor could inject malicious code) and given the lack of verifiable builds in mainstream supply chains.

You can, however, overcome this limitation by hosting Domain yourself, on your own hardware (e.g., a single-board computer like a Raspberry Pi).

The other thing to be aware of is that the security of the system is based on your secret key remaining secret and, initially at least, there will not be a way to change this secret key. (On the longer roadmap, it would be nice to provide a means of changing it but this is not a trivial process, especially if encryption keys have been derived from it and encrypted messages exist within a Kitten app).


Contact Aral on the fediverse.