Open-source project
janet-lang/janet avatar
janet-lang/janet

Janet: a small bytecode VM for scripting and embedding in C

A dynamic language and bytecode vm

4,430 stars281 forksCMIT

At a glance

What is it?
Janet is a Lisp-flavoured dynamic language with a bytecode VM, a 600+ function core library, and a distribution model built around two files, janet.c and janet.h. It fits system scripting and embedding; it does not fit teams that need a large package ecosystem.
Who is it for?
Adopt Janet when you need a small embeddable scripting layer in a C or C++ program, or a scripting language with built-in sockets, threads and PEG parsing without pulling in a runtime the size of Python. Do not adopt it if your project depends on a large third-party package ecosystem, or if you need the language to be stable without pinning a tagged release, since the README states that most development happens on master and master is not necessarily stable.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What Janet is for, and who it is not for

The README states Janet is "a programming language for system scripting, expressive automation, and extending programs written in C or C++ with user scripting capabilities." That sentence names two audiences. The first is someone writing glue code: a script that walks a directory tree, opens sockets, spawns subprocesses, or reformats data. The second is a C or C++ developer who wants users to be able to script their application without shipping a Python or Guile runtime.

The comparison the README makes is explicit. Janet is "like Lua and GNU Guile in that regard," with more built-in functionality and a richer core language than Lua, but smaller than GNU Guile or Python, and "much easier to embed and port than Python or Guile." That is the trade the project is selling: a smaller surface area in exchange for a language you can drop into a host program.

The people who should look elsewhere are those who need a package registry with thousands of libraries, or who need a language whose semantics are frozen by a standards body. Janet's optional ecosystem is Spork and JPM, and the README describes Spork's modules as "less stable than the interfaces in core Janet." That is a deliberate position, not an oversight, but it means the surrounding tooling carries more risk than the core language.

The bytecode VM, the core library, and what ships in the box

Janet compiles to bytecode and runs it on a VM. The README lists the runtime features that come with that VM rather than through libraries: "600+ functions and macros in the core library," built-in socket networking, threading, subprocesses and file system functions, a Parsing Expression Grammar engine described as "a more robust Regex alternative," macros and compile-time computation, and a per-thread event loop using epoll, IOCP or kqueue depending on platform.

The concurrency model is worth separating out. Janet has first-class green threads implemented as continuations, OS threads as a separate facility, and supervision trees in the Erlang style that integrate with the event loop. Those are three different answers to three different problems, and the README presents them as coexisting rather than as one abstraction stretched over all of them.

Data representation is split along mutability lines. Arrays and tuples are the mutable and immutable array types; tables and structs are the mutable and immutable hashtables; buffers and strings are the mutable and immutable strings. Having both forms in the core means a function signature can state whether it will modify its argument, which is a real design decision rather than an accident of implementation.

The embedding story is the distribution format. Janet is "distributed as janet.c and janet.h for embedding into a larger program," and the client program with the REPL and script runner is separate from the core runtime. Two files to drop into a build is a materially different proposition from a runtime with a package directory and a boot sequence.

Building Janet from source and running a first script

The README gives a specific instruction for building from source: "for stability, please use the latest tagged release," and it shows the command to check out that tag after cloning but before building. The repository has a Makefile at the top level, and the Makefile sets PREFIX to /usr/local by default, with BINDIR, LIBDIR and JANET_PATH derived from it. The Makefile also defines the build artifacts: build/janet for the client, build/libjanet.so and build/libjanet.a for the shared and static libraries, and build/janet.lib and build/libjanet.lib for the Windows import libraries.

The README's own note about master is the reason for the checkout step. It states that the master branch is "not-necessarily stable as most Janet development happens directly on the master branch." If you clone and build without checking out a tag, you are building whatever was merged most recently.

bash
git clone https://github.com/janet-lang/janet
cd janet
git checkout $(git describe --tags --abbrev=0)
make

After make completes, the client binary is at build/janet. The repository also contains meson.build and meson_options.txt, so a Meson build is available as an alternative to the Makefile, and build_win.bat covers the Windows path.

Once you have a binary, the README's documented way to explore the language without leaving the REPL is the doc macro. Running (doc apply) prints the API documentation for apply, and (all-bindings) lists every binding in the default environment. Calling (doc) with no arguments in the REPL shows the bound symbols.

janet
(doc apply)
(all-bindings)

For a first real program, the repository's examples directory is the reference. examples/hello.janet is the smallest starting point, and the README's TCP echo server example shows the shape of an event-loop program: a handler function that takes a stream, a defer form that closes the stream, and a call to net/server binding 127.0.0.1 on port 8000 with that handler. That example is short enough to paste into a file and run directly, and it exercises sockets, the event loop and the buffer type in about twenty lines.

The FFI removes the need for a C compiler in some cases

The README's Windows FFI example is the clearest demonstration of how far the dynamic binding goes. It calls ffi/context on user32.dll, then ffi/defbind to declare MessageBoxA with an :int return type and :ptr, :string, :string, :int parameters, then calls the function. No C compiler, no build step, no generated bindings.

That matters for the embedding audience. The README describes three tiers: native bindings loaded as dynamically loaded plugins, the built-in C FFI for "when the native bindings are too much work," and plain C extension. The FFI tier means a host application can expose a C function to scripts by writing a declaration in Janet rather than writing and compiling a native module.

The cost is that the FFI is a runtime binding, so the compiler cannot check it. A wrong argument type or a mistyped symbol name fails when the call executes, not when the script loads. For a scripting layer inside an application, that is usually acceptable. For a library you are distributing to users who will not read your source, it is a debugging burden you are handing to them.

Spork and JPM: two package managers, one of them winding down

The README is unusually direct about the state of the two companion projects. Spork is a collection of utility modules and packaged scripts, including janet-format for code formatting, janet-netrepl for a socket-based REPL, and janet-pm as a project manager. Spork requires a C compiler to build and install extension components such as miniz and JSON utilities. On POSIX systems the scripts install to $JANET_PATH/bin/ by default, which the README notes likely needs to be added to PATH.

JPM is described as "the older, more opinionated, project manager tool," which "does not require a C compiler to build and install, but is less flexible and is not receiving many changes and improvements going forward." The README adds that JPM "may also be harder to configure correctly on new systems." JPM installs to /usr/local/bin/ on POSIX by default, which may or may not be on PATH.

So the newer tool needs a C compiler and the older tool is not receiving many changes. Neither is a drop-in replacement for the other, and the README does not present a migration path between them. If you are starting a project that needs dependency management beyond the core library, that choice is the first thing to resolve, and the README leaves it to you.

The mitigating detail is that Spork sub-modules are not all-or-nothing. The README states that many sub-modules, naming spork/path as an example, "are independent and can be manually vendored in programmer projects without fully installing spork." For a small project, copying the one module you need may be less work than installing the whole thing.

Where Janet is the wrong tool

The clearest limitation is stability on master. The README states plainly that master is not necessarily stable, and the build instructions tell you to check out a tag for stability. A team that installs Janet from a distribution package that tracks master, or that builds from a cloned HEAD, is running code the project itself declines to call stable. That is not a flaw in the language; it is a mismatch between how the project develops and how some consumers install software.

The second limitation is the ecosystem. Spork's modules are described as less stable than core interfaces, and the README says the project tries to prevent breaking changes to existing modules "with a preference to add new modules and functions." That is a reasonable policy, but it means the stability guarantee covers core Janet, not the utilities around it. If your project's value depends on third-party libraries, Janet is a poor fit, and the README does not claim otherwise.

The third is the FFI's runtime-only checking, described above. If you are exposing an API to untrusted or non-technical script authors, a misdeclared binding produces a crash rather than a compile error.

Finally, the README does not document a rollback or version-pinning mechanism for scripts themselves. The build instructions cover pinning the interpreter, and the CHANGELOG.md file exists at the top level, but nothing in the README describes how a script declares which Janet version it requires. Verify that separately before you depend on it.

How Janet differs from Lua, and when to pick the other one

The README names Lua as the closest comparison, so the difference is worth stating precisely. Lua is smaller and its embedding surface is narrower; Janet ships more in the core, including socket networking, threading, subprocesses, a PEG engine, and an event loop. The README's own framing is that Janet has "more built-in functionality and a richer core language than Lua, but smaller than GNU Guile or Python."

The practical consequence is what you do not have to add. In Lua, an event loop, a pattern engine beyond the built-in patterns, and a networking layer typically come from outside the language. In Janet they are in the core library, which is why the README's TCP echo server example is a single net/server call with a handler function rather than a dependency list.

The trade runs the other way too. Lua has a much larger body of existing bindings and a longer history of being embedded in shipping products, and its core is small enough to audit in an afternoon. If your embedding needs are minimal, Lua's smaller surface may be the better fit, and Janet's extra core is weight you are not using. If your embedding needs sockets or an event loop, Janet's core saves you from assembling those yourself.

A second comparison point is the build. Janet's Makefile produces both a client binary and shared and static libraries from the same tree, and the README states the project is distributed as janet.c and janet.h for embedding. That is a simpler integration story than a language that expects you to link against a system-installed runtime.

Licence and the cost of upgrading

Janet is MIT licensed. The Makefile carries the full MIT text in its header, and the repository has a LICENSE file at the top level. For embedding, MIT is permissive: you can ship Janet inside a closed-source application provided you include the copyright notice and permission notice. That is a summary of what the licence text says, not legal advice, and if your situation is unusual you should read the LICENSE file and consult someone qualified.

The upgrade cost has two parts. The interpreter itself is a tagged release you can pin, and the README's build instructions are built around doing exactly that. The surrounding tooling is where the cost sits: Spork modules carry no stability promise comparable to core, and JPM is described as not receiving many changes and improvements going forward. A dependency on a Spork module is a dependency you may eventually need to vendor or replace.

There is also a maintenance question the repository answers directly. The last push to the default branch was on 2026-09-19, and the most recent tagged release, v1.42.1, is dated 2026-09-11. Two releases landed in the weeks before that, v1.42.0 on 2026-08-31 and v1.41.2 on 2026-02-18. The repository is not archived. Whatever the pace of change on master, tagged releases are the thing to track, and the README tells you so.

Editorial conclusion

Adopt Janet when you need a small embeddable scripting layer in a C or C++ program, or a scripting language with built-in sockets, threads and PEG parsing without pulling in a runtime the size of Python. Do not adopt it if your project depends on a large third-party package ecosystem, or if you need the language to be stable without pinning a tagged release, since the README states that most development happens on master and master is not necessarily stable. Before committing, verify that the tagged release you intend to use builds on your platform with your toolchain, and check whether the optional Spork or JPM tooling is required for your workflow, because Spork needs a C compiler for its extension components while JPM does not.

Frequently asked questions

How do I use Janet for the first time?

Build the client from a tagged release with make, then run the resulting build/janet binary to get a REPL. Inside the REPL, (doc apply) prints the API documentation for a symbol and (all-bindings) lists everything in the default environment.

Can Janet be embedded in a C or C++ program?

Yes. The README states Janet is distributed as janet.c and janet.h for embedding into a larger program, and that the client program with the REPL and script runner is separate from the core runtime.

Does Janet have a package manager?

There are two companion projects. Spork is a collection of utility modules and scripts that requires a C compiler for its extension components, and JPM is the older project manager that does not need a C compiler but is described in the README as not receiving many changes going forward.

Is the Janet master branch stable?

The README states the master branch is not necessarily stable, because most Janet development happens directly on master. The build instructions say to check out the latest tagged release for stability.

What licence does Janet use?

Janet is MIT licensed. The repository has a LICENSE file at the top level, and the Makefile carries the MIT text in its header.

Official sources

  1. janet-lang/janet on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/janet-lang-janet.svg)](https://hysenlabs.com/projects/janet-lang-janet)