state-machine-cat: state charts as text you can keep in version control
write beautiful state charts :scream_cat:
At a glance
- What is it?
- state-machine-cat turns a small text dialect into state charts, either through a CLI, an online interpreter or a render function, with a dozen output formats and a SCXML reader and writer. The reason it exists is not that GraphViz is bad, but that writing GraphViz by hand is not what people want to do.
- Who is it for?
- Adopt state-machine-cat if you write state charts that live next to the code they describe, since a text source diffs cleanly, needs no diagram editor and can be rendered in CI. Do not adopt it if your team works visually, because the tool removes the canvas rather than adding to it, and the desugar transform that handles pseudo states is marked experimental.
- 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 last received commits 1 day ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Why not just write GraphViz by hand
The README explains the motivation in the author's own voice, and it is more revealing than a feature list. The goal is to be able to make state charts that look good, with the least effort possible, without having to interact with drag and drop tools, because entering text is fine but doing one's own layout is not.
The last point is the sharpest. GraphViz is called cool, but it was not designed to write and maintain conceptual documents in, and the author notes you will know what he means if you have ever tried to get it to draw nested nodes, or edges between those. So this is not a GraphViz replacement in the sense of a competitor. It is a front end that produces GraphViz dot and lets the layout engine do what it is good at, while you write something that looks more like a state chart than like a graph description.
The result is a small text dialect. The README shows the same chart written as an initial state, a doing state with entry, do and exit activities, a final state, and the transitions between them with labels such as item adds most value. The states are declared automatically, and initial and final states are recognised by name and rendered accordingly. You can also declare a state explicitly when it needs more than a name, for example to attach activities to it.
The audience is developers and technical writers who keep conceptual documentation next to the code, and who would rather review a chart as a text diff in a pull request than as a binary image attachment. The project is MIT licensed, written in TypeScript, published on npm as state-machine-cat, and its homepage is the online interpreter at state-machine-cat.js.org.
The dialect follows UML notation without enforcing it
The language section of the README walks through the syntax in small steps, and the most interesting part is its relationship to UML.
UML prescribes placing conditions after events, placing conditions within squares and placing actions after a slash, in the form from => to: event [conditions]/ actions, for example on => off: switch flicked [not an emergency]/ light off. The README is explicit that you are free to write it that way, but smcat does not check for it. Internally, though, it takes the notation into account, and if you export to json, scxml or scjson you will see the event, the condition and the action split out properly.
That is a deliberate design choice. The tool accepts sloppy input, renders it, and keeps the structure internally so that a machine-readable export is still correct. For a documentation tool that is the right trade, since the person writing the chart is usually thinking about the transitions rather than about notation.
The rest of the syntax is small. Notes are written as comments and are rendered next to the chart, and multiple notes become multiple lines. Transitions can carry a label after a colon, and a state can declare activities such as entry and exit triggers after a colon of its own. which is how the README's light switch chart expresses the actions. Language support outside the chart itself is handled by two separate syntax highlighting grammars. There is a tree-sitter grammar for editors that support tree sitter, and a Vim syntax file maintained by another author, ambagasdowa. That is the practical way to find out how mature the language is: two independent highlighting implementations exist because people actually write in it.
One command, twelve output formats, six layout engines
The CLI is installed globally with npm and then invoked as smcat:
npm install --global state-machine-catThe help output is worth reading, because it shows the whole surface. The output type option accepts svg, eps, ps, ps2, dot, smcat, json, ast, scxml, oldsvg, scjson, pdf and png, with svg as the default. The input type accepts smcat, json or scxml, so the tool can read back what it wrote. The engine option picks the GraphViz layout engine, with dot as the default and circo, fdp, neato, osage and twopi as the alternatives. The direction option sets the flow, top-down, bottom-top, left-right or right-left, again top-down by default. There is an output file option where a dash means standard output, a version flag, a license flag, and a help flag.
The basic use is one argument:
smcat docs/sample.smcatAnd the README is candid about the defaults: leaving the options at their default settings usually delivers the best results already, so if they bewilder you, do not worry. That is a useful attitude for a tool whose option list is longer than its user base will ever need.
The direction default is also worth understanding, because the README points out a chart that rendered top down instead of left to right even though it was written horizontally. The layout is not implied by the order you write the transitions in; it is chosen by the direction setting, and you can change it on the command line.
The tool also works in the browser, at state-machine-cat.js.org, described as a no-frills interpreter, and it can be embedded in a page with script tags of type text/x-smcat, documented on the inpage page of the same site.
The dot pipeline and the attribute escape hatch
For anyone who already has a GraphViz workflow, the tool does not hide the dot output. You can ask for dot explicitly and pipe it through dot yourself:
smcat -T dot docs/sample.smcat -o - | dot -T svg -odoc/sample.svgThat is a normal Unix pipeline, and it means the smcat step can be dropped into an existing pipeline that already renders dot files with custom flags.
The same idea appears in the advanced options, which are documented separately from the help output. There are three of them, and each passes a string through to the dot engine: --dot-graph-attrs for graph attributes, --dot-node-attrs for node attributes and --dot-edge-attrs for edge attributes. The README's example is the case people hit most often, a transparent background with straight edges instead of splines:
smcat --dot-graph-attrs "bgcolor=transparent splines=line" docs/sample.smcatThere is one more transformation to be careful with. The --desugar switch transforms some pseudo states into transitions before rendering, and the README marks it as experimental with an exclamation mark in the option description. The details are in the de-sugaring state machines document in the repository, so the behaviour is documented, but the label is a warning that the transformation is not finished.
The combination of a text format, a scriptable CLI and pass-through attributes is what makes the tool usable in a documentation pipeline rather than only on a whiteboard.
SCXML in both directions, and a render function for your own code
The most serious feature is the interoperability with SCXML, the W3C state chart XML. The README says the tool can write and read valid core constructs of scxml documents, and points to a dedicated document, State Machine Cat and SCXML, for the details.
Reading SCXML as input is what makes the input type option meaningful, since the tool is not a one-way converter but a document model with a text syntax on one side and XML on the other. A team that already maintains state machines in XML can render them, and a team that writes in the text dialect can export XML for a tool that expects it.
For programmatic use, the package exports a render function. The shape is simple: import render from the package, await it with the chart source and an options object, and you get the rendered output as a string. The example in the README sets the output type to svg and logs the result, and wraps the call in a try and catch so that a parse error can be handled rather than crashing the caller. The API details live in docs/api.md in the repository.
The call itself is short, and the example in the README shows the shape:
import { render } from "state-machine-cat";
try {
const lSVGInAString = await render(
`
initial => backlog;
backlog => doing;
doing => test;
`,
{
outputType: "svg",
},
);The distinction between the online interpreter and this function matters for a specific reason. The interpreter is for trying a chart out, and the function is for generating a chart as part of a build or a service, for example a documentation site that renders the state chart it finds next to a module. The npm package version is 15.0.10, the package is an ES module, and the exported types are shipped alongside the build, so a TypeScript consumer gets the same types the project uses.
The build generates the parser, the validator and the diagrams
The package.json scripts and the Makefile show how the project is put together, and they are unusual enough to be worth a look.
The parser is generated, not hand-written. The Makefile declares a PEGGY grammar compiler and an ESBUILD binary, and rules that turn a .peggy grammar file into a parser module, including one for the smcat language and a second for the command line attributes parser. The version module is generated from package.json, and the AST schema is generated from a JSON schema in the tools directory, after which a validator is generated from that schema and then minified and tree-shaken with esbuild. The build script itself runs make clean, dist, pages and cli-build, removes the dist directory, compiles with tsc, copies the validator, formats the output with prettier and makes the CLI entry point executable.
The check script is where the quality gates live. It runs dependency-cruiser, lint and the coverage tests in sequence, and dependency-cruiser is configured with a config file in the config/dependency-cruiser directory that checks the source, test, types and tools trees against each other. The project also generates its own architecture documentation from that analysis, rendering the dependency graph with dot into SVG and HTML files in the docs directory, in several shapes: an architecture graph, a dependency graph and a flat dependencies graph.
That last point is the one to take away. A tool whose purpose is to make conceptual diagrams readable has an automated check that its own module structure stays legible, and it publishes the result as a diagram. Around that there is a husky directory for git hooks, a lint-staged configuration, an oxlint configuration, a mocha configuration, a c8 coverage configuration and an editorconfig, so the repository runs a fairly complete quality setup for a project of this size.
A version 15 project, released often
state-machine-cat is at version 15.0.10 in the package manifest, and the release history in the repository shows v15.0.8 on 2026-08-02, v15.0.9 on 2026-09-16 and v15.0.10 on 2026-10-07. The last push was on 2026-10-07 as well.
A version number in the teens with a patch release every few weeks says something specific: this is a project that has been in use long enough to have accumulated compatibility baggage, and that still moves. The MIT licence keeps that straightforward for anyone embedding it, and the changelog at the root is where the breaking changes are recorded, which matters for a tool whose input format and output types are the interface.
The project is also organised in a way that suggests long-term care rather than a single author's weekend. There is a SECURITY.md and a CODE_OF_CONDUCT.md, a CHANGELOG.md, a Makefile with generated sources tracked in it, a GitLab CI configuration alongside the GitHub workflow badge, and a docs directory that holds the grammar documentation, the API documentation, the SCXML notes and the de-sugaring notes.
The practical note for a user is about formats rather than versions. Before you standardise on the tool, check that the output you need is in the --output-type list, and be careful with the experimental --desugar switch. The rest of the default behaviour is deliberately conservative.
Editorial conclusion
Adopt state-machine-cat if you write state charts that live next to the code they describe, since a text source diffs cleanly, needs no diagram editor and can be rendered in CI. Do not adopt it if your team works visually, because the tool removes the canvas rather than adding to it, and the desugar transform that handles pseudo states is marked experimental. Before you standardise on it, check the output format you need against the --output-type list, and remember that the default GraphViz dot engine is the only one most people ever need, with the other engines there for special layouts.
Frequently asked questions
How do I install and run state-machine-cat?
The README gives npm install --global state-machine-cat, after which you run smcat. The default output is an SVG file, and passing a source file such as smcat docs/sample.smcat produces the chart. There is also an online interpreter at state-machine-cat.js.org.
Which output formats does state-machine-cat support?
The --output-type option accepts svg, eps, ps, ps2, dot, smcat, json, ast, scxml, oldsvg, scjson, pdf and png, with svg as the default. The --input-type option accepts smcat, json or scxml, and the --engine option selects the GraphViz layout engine, dot by default, with circo, fdp, neato, osage and twopi as alternatives.
Can state-machine-cat read and write SCXML?
Yes. The README states that the tool can write and read valid core constructs of scxml documents, and the details are in the State Machine Cat and SCXML document in the repository. The input type scxml makes XML charts a valid source, and scxml is also one of the output types.
How do I use state-machine-cat in a build script or a service?
The package exports a render function. You import { render } from "state-machine-cat", await it with the chart source and an options object such as { outputType: "svg" }, and receive the rendered output as a string, with the API details in docs/api.md.
What is the --desugar option for?
It transforms some pseudo states into transitions before rendering, and the README marks it as experimental. The behaviour is described in the de-sugaring state machines document in the repository, and the advanced options --dot-graph-attrs, --dot-node-attrs and --dot-edge-attrs let you pass attributes straight to the dot render engine.
Official sources
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.
[](https://hysenlabs.com/projects/sverweij-state-machine-cat)Was this useful?