GitHub - lucianofedericopereira/linknim: Linknim - a bookmark server manager written in Nim. Stores everything in plain CSV + INI files.

4 min read Original article ↗

Linknim

A bookmark server manager written in Nim. Stores everything in plain CSV + INI files.

Copyright (C) 2026 Luciano Federico Pereira Licensed under the GNU Lesser General Public License v2.1

Compatible with linkding. Linknim reimplements the linkding REST API and UI concepts in Nim with a CSV/INI backend. No linkding source code was copied or derived.


Features

  • 1:1 linkding API — existing browser extensions (Firefox, Chrome) work without modification
  • CSV + INI storage — human-readable, version-controllable, trivially backed up
  • Web UI — search, add, edit, archive, bulk actions, tag tree, bundle saved searches
  • TUI — full terminal interface, no browser needed
  • CLI — add/list/edit/delete/archive from the shell, scriptable with --format json
  • Import — Netscape bookmark HTML (exported by every browser)
  • Export — Netscape HTML and JSON
  • RSS feed — shared bookmarks at /feed.xml
  • Bookmarklet — save any page with one click, no extension required
  • Keyboard-driven — configurable shortcuts in bookmarks.ini
  • Favicons — fetched async, cached locally in data/favicons/
  • Dark / light / auto theme — set in ini or toggled per-session

Install

# Install Nim >= 2.0: https://nim-lang.org/install.html
# Install Nimble (comes with Nim)

git clone <this-repo> linknim
cd linknim
nimble install illwill   # TUI dependency
nim c -d:release linknim.nim

Quick start

# Create default config with a random token
./linknim init

# Start the server
./linknim serve

# Open http://127.0.0.1:8888 in your browser

Configure the browser extension (linkding-extension for Firefox/Chrome):

  • Server URL: http://127.0.0.1:8888
  • Token: from bookmarks.ini [auth] token

Configuration

All settings live in bookmarks.ini next to the binary.

[server]
host = 127.0.0.1
port = 8888

[auth]
token = your-secret-token

[profile]
theme = auto          # auto | light | dark
font  =               # CSS font-family, empty = system default
web_archive_integration = disabled   # disabled | enabled

[shortcuts]
add          = n
search       = /
sidebar      = b
settings     = ,
nav_all      = ga
nav_unread   = gu
nav_archived = gr
prev_page    = [
next_page    = ]

[tag.work]
date_added = 2024-01-01T00:00:00.000000Z

[tag.work/nim]
date_added = 2024-01-15T00:00:00.000000Z

Tags use slash-convention for hierarchy: work/nim renders as nim under work in the UI.


Storage

File Contents
bookmarks.ini Config, auth token, tag registry, shortcuts
bookmarks.csv Bookmark data (named headers, RFC 4180)
bundles.csv Saved searches
data/favicons/ Cached favicon PNGs

Writes are atomic (temp file + rename) — safe against crashes.


CLI

# Server
linknim serve
linknim serve --port 9000 --open --quiet
linknim serve --config /path/to/other.ini

# Setup
linknim init              # create bookmarks.ini with random token
linknim stats             # show counts
linknim check             # validate CSV integrity

# Bookmarks
linknim add https://example.com --title "Example" --tags "work,nim" --desc "useful"
linknim list
linknim list --tag work --limit 50 --format json
linknim list --search "nim async" --format csv
linknim list --archived
linknim list --unread
linknim get 42
linknim get 42 --format json
linknim edit 42 --title "New title" --tags "work,updated"
linknim delete 42
linknim archive 42
linknim unarchive 42

# Tags
linknim tags
linknim tags --format json
linknim tag-add work/nim
linknim tag-rename work/nim nim
linknim tag-delete oldtag

# TUI
linknim tui

--format accepts table (default), json, csv.


TUI

┌─ Linknim ─── Search: █ ──────────── [N]ew [/]Search [Q]uit ┐
│ All (142)    ├──────────────────────────────────────────────│
│ Unread  (12) │ ID    TITLE                   TAGS      DATE │
│ Archived (8) │▶42    Nim async deep dive      nim,async ... │
│              │ 41    linkding source           tools    ... │
│ TAGS         │ 40    Tailwind v4 guide         css      ... │
│ nim          │                                              │
│ work/nim     ├──────────────────────────────────────────────│
│ personal     │  Nim async deep dive                         │
│              │  https://nim-lang.org/...                    │
│              │  Tags: nim  async                            │
│ J/K:move  Enter:open  E:edit  N:new  A:archive  D:del  T:tag│
└─────────────────────────────────────────────────────────────┘
Key Action
J / K Navigate list
Enter Open URL in browser
N New bookmark
E Edit focused bookmark
A Archive / unarchive
D Delete (with confirm)
T Tag filter picker
/ Search (live)
1 / 2 / 3 All / Unread / Archived view
G Jump to top
Q / Esc Clear filter → quit

Web UI

Available at http://127.0.0.1:8888 when the server is running.

Bookmarklet — drag from the 🔖 button to your bookmarks bar. Click it on any page to save instantly without the browser extension.


API

Full linkding REST API — see linkding API docs.

Linknim-specific extras:

Endpoint Description
GET /feed.xml RSS feed of shared bookmarks
GET /api/export.json Download all bookmarks as JSON
GET /api/export.html Download as Netscape bookmark HTML
PATCH /api/tags/<id>/ Rename a tag (updates all bookmarks)
DELETE /api/tags/<id>/ Delete a tag (removes from all bookmarks)

Compatibility

Tested against the linkding-extension for Firefox and Chrome. The extension calls: /api/bookmarks/check/, POST /api/bookmarks/, GET /api/tags/, GET /api/user/profile/. All return the exact linkding response shapes.