Gesttalt Language Bindings: Use It From Any Stack
One of Gesttalt's design goals is to be more than just a CLI tool. The core is written in Zig and exposes a C ABI, which means you can call it from virtually any language, and today I'm sharing the official bindings that make this easy, including Node.js (N-API, and it also works with Bun and Deno), a browser WASM build with localStorage persistence, Elixir via NIFs and elixir_make, Ruby as a C extension, and Python through ctypes FFI. The CLI is great for manual operations, but programmatic access helps you build custom workflows like CI-driven publishing, integrate content into existing dashboards, run Gesttalt inside a browser editor, and automate batch operations, scheduled publishing, or migrations without gluing together a pile of shell scripts.
If you want the Node.js binding (which also works with Bun and Deno), install it from the bindings directory and then use it to create snippets, list posts, and write notes, like this:
cd bindings/nodejs
npm installconst gesttalt = require('gesttalt');
// Create a snippet
const path = gesttalt.createSnippet(
'.', // project directory
Date.now() / 1000 | 0, // unix timestamp
'Quick sort in Zig', // description
sortCode, // code body
'sort.zig' // filename
);
// List all posts
const posts = gesttalt.listPosts('.');
console.log(posts);
// Create a note
gesttalt.createNote('.', Date.now() / 1000 | 0, {
body: 'Just shipped a new feature!'
});
Elixir is backed by NIFs compiled with elixir_make, so it’s as fast as native code; install the deps and call the API to create posts, list snippets, and update notes:
cd bindings/elixir
mix deps.get# Create a post
Gesttalt.Posts.create(".", "my-post", "My Post Title", "A description", %{
tags: ["elixir", "web"],
date: "2026-01-14"
})
# List snippets
snippets = Gesttalt.Snippets.list(".")
# Update a note
Gesttalt.Notes.update(".", "1768414540", %{body: "Updated content"})
Ruby ships as a C extension, so you install and compile it, then use the same API surface you’d expect for snippets, posts, and notes:
cd bindings/ruby
bundle install
bundle exec rake compilerequire 'gesttalt'
# Create a snippet
Gesttalt::Snippets.create(
".",
Time.now.to_i,
"Hello world in Ruby",
"puts 'Hello, world!'",
"hello.rb"
)
# Read a post
post = Gesttalt::Posts.read(".", "my-post")
puts post["title"]
# Delete a note
Gesttalt::Notes.delete(".", "1768414540")
Python uses ctypes, so after a local editable install you can list posts, create notes, and update snippets just as you would in other bindings:
cd bindings/python
pip install -e .from gesttalt import posts, notes, snippets
import time
# List all posts
all_posts = posts.list(".")
for post in all_posts:
print(f"{post['date']}: {post['title']}")
# Create a note
notes.create(".", int(time.time()), body="Thought of the day")
# Update a snippet
snippets.update(".", 1768414540, description="Updated description")
The Node.js package also includes a browser WASM build for frontend usage, which defaults to in-memory storage but can sync to localStorage for persistence, making it a good fit for browser-based editors or playgrounds:
<script type="module">
import { createNote, listNotes } from 'gesttalt/browser';
// Uses in-memory storage by default
// Can persist to localStorage
createNote('.', Date.now() / 1000 | 0, {
body: 'Note from the browser!'
});
const notes = listNotes('.');
console.log(notes);
</script>
All bindings intentionally share the same API surface for snippets, posts, and notes with create, list, read, update, and delete, and they return native data structures in each language (objects in JS, maps in Elixir, hashes in Ruby, dicts in Python). To get started, build the Zig library from the project root with zig build -Doptimize=ReleaseFast, navigate to your preferred binding directory for the setup steps, and check out the tests in each binding for more examples; everything is MIT licensed, and contributions are welcome.
If you want to kick the tires with the CLI, install Gesttalt and scaffold a fresh site in one go:
mise use -g github:pepicrft/gesttalt
gesttalt init