Command-line client for Linkwarden, built on Bun
Find a file
KaKi87 b16610cb83 Move git clone instructions to the development section
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-10 17:11:58 +01:00
src Right-align numeric columns and document npm install 2026-11-10 17:09:29 +01:00
test Right-align numeric columns and document npm install 2026-11-10 17:09:29 +01:00
.gitignore Add Linkwarden CLI 2026-11-10 16:54:19 +01:00
bun.lock Add Linkwarden CLI 2026-11-10 16:54:19 +01:00
LICENSE Add Linkwarden CLI 2026-11-10 16:54:19 +01:00
package.json Add Linkwarden CLI 2026-11-10 16:54:19 +01:00
README.md Move git clone instructions to the development section 2026-11-10 17:11:58 +01:00
tsconfig.json Add Linkwarden CLI 2026-11-10 16:54:19 +01:00

linkwarden-cli

A command-line client for Linkwarden, the self-hosted bookmark manager. It runs on Bun, has no runtime dependencies, and covers the Linkwarden REST API: links, collections, tags, highlights, RSS subscriptions, access tokens, account settings, import and export.

It was written from the Linkwarden server source (API routes and validation schemas, v2.16) and checked against a real v2.16.3 server.

Install

Bun 1.2 or later is required. Install from npm:

bun install -g linkwarden-cli

Or install straight from the Git repository:

bun install -g git+https://git.kaki87.net/VibedByKaKi/linkwarden-cli.git

Both put the linkwarden and lw commands on your PATH.

To build a standalone binary that does not need Bun on the target machine:

bun run build     # writes dist/linkwarden

Log in

linkwarden login --url https://links.example.com

The command asks for a username and password, creates a session on the server, and saves the token. For accounts that sign in with SSO, create an access token in Settings > Access Tokens and run:

linkwarden login --url https://links.example.com --token <token>

For scripts, use --password-stdin, or skip the config file and set LINKWARDEN_URL and LINKWARDEN_TOKEN.

The config file is ~/.config/linkwarden/config.json ($XDG_CONFIG_HOME and $LINKWARDEN_CONFIG are respected) and is created with mode 0600. Each login can use its own profile:

linkwarden login --profile work --url https://links.work.example --token <token>
linkwarden profile list
linkwarden profile use work
linkwarden --profile home ls

Credentials come from, in order, the --url and --token flags, the LINKWARDEN_URL and LINKWARDEN_TOKEN variables, and the selected profile (--profile, LINKWARDEN_PROFILE, or the profile set with profile use).

Everyday use

linkwarden add https://example.com -c Reading -t article,todo   # save a link
linkwarden add -f paper.pdf -c Papers                           # upload a PDF or image
cat urls.txt | linkwarden add -c Inbox                          # bulk add from stdin
linkwarden ls                                                   # recent links
linkwarden search "docker compose" -c Dev                       # search
linkwarden ls -t todo --pinned                                  # filter by tag and pin
linkwarden links get 42                                         # details and stored copies
linkwarden links update 42 -n "New title" --add-tag later -c Archive
linkwarden links pin 42
linkwarden open 42                                              # open in the browser
linkwarden links download 42 -f pdf                             # save the stored PDF
linkwarden links delete 42 -y

Run linkwarden --help for the command list and linkwarden <command> --help for options and examples.

Collections can be named or numbered, and nested ones use a path such as Dev/Rust. The CLI looks names up on the server, so add -c "Dev/Rust" never creates a collection by accident. Pass --create-collection to create one on purpose.

Search syntax

The query matches name, URL, description and tags. When the server runs Meilisearch it also accepts filters, and a leading ! negates one:

tag:<name>  collection:<name>  type:<url|pdf|image>  url:<text>  name:<text>
description:<text>  pinned:true  public:true  before:<date>  after:<date>
linkwarden search 'tag:rust !collection:Archive after:2025-01-01'

Commands

Command What it does
login, logout, whoami, profile Authentication and saved profiles
add, ls, search, open Shortcuts for the most common links commands
links list, search, get, add, update, delete, pin, unpin, open, refresh, clear-archives, download
collections list, get, create, update, delete, share, unshare
tags list, create, rename, delete, merge
highlights list, add, delete
rss list, add, delete
tokens list, create, revoke
settings get, set for archive formats, duplicate prevention, link click behavior and AI tagging
dashboard Pinned and recent links
export, import Linkwarden JSON backup, browser HTML, Wallabag, Omnivore and Pocket files
public Read public collections without a token
info Server configuration
api Call any endpoint directly, for example linkwarden api GET /search --query searchQueryString=rust
completion Shell completion for bash, zsh and fish

Scripting

Every command accepts --json and prints the API objects unchanged. With -q, commands print only ids, one per line:

linkwarden ls -t todo -q | xargs linkwarden links update --add-tag reviewed
linkwarden ls --all --json | jq -r '.links[].url'

Prompts and progress go to stderr, so stdout stays clean for pipes. Deleting commands refuse to run without --yes when stdin is not a terminal.

Exit codes: 0 success, 1 failure (including API errors), 2 usage error, 3 authentication or configuration error. Set NO_COLOR or pass --no-color to turn off colors.

Behavior worth knowing

  • The server returns 50 links per page. --limit stops early, --all follows every page, and --cursor resumes from a previous page. The --json output includes nextCursor.
  • Linkwarden ORs the tag filter with the other conditions, so --tag and --pinned are applied again on the client. A tag filter on a large library can therefore fetch several pages.
  • Updating a link replaces its tag list on the server, so update reads the link first and sends the merged result.
  • Updating a collection recreates its member list, so collections update sends the existing members back unchanged.
  • logout removes the profile locally. To invalidate the token, revoke it with linkwarden tokens revoke <id>.

Development

git clone https://git.kaki87.net/VibedByKaKi/linkwarden-cli.git
cd linkwarden-cli
bun install
bun link                 # optional, installs the commands from the checkout
bun test                 # unit tests and tests against an in-memory mock server
bun run typecheck

The mock server in test/mock-server.ts follows the Linkwarden route handlers. To run the suite in test/e2e against a real server:

docker compose -f test/e2e/compose.yml -p lw-e2e up -d
LINKWARDEN_E2E_URL=http://localhost:13000 bun test test/e2e
docker compose -p lw-e2e down -v

The project uses Bun for everything: Bun.serve, Bun.file, Bun.stringWidth and bun:test replace the usual dependencies, and node:util parseArgs handles option parsing.

License

MIT