RepoPilotOpen in app →

go-shiori/shiori

Simple bookmark manager built with Go

Healthy

Healthy across all four use cases

weakest axis
Use as dependencyHealthy

Permissive license, no critical CVEs, actively maintained — safe to depend on.

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • Last commit 3mo ago
  • 18 active contributors
  • Distributed ownership (top contributor 47% of recent commits)
Show all 7 evidence items →
  • MIT licensed
  • CI configured
  • Tests present
  • Slowing — last commit 3mo ago

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Healthy" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/go-shiori/shiori)](https://repopilot.app/r/go-shiori/shiori)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/go-shiori/shiori on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: go-shiori/shiori

Generated by RepoPilot · 2026-05-09 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/go-shiori/shiori shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

GO — Healthy across all four use cases

  • Last commit 3mo ago
  • 18 active contributors
  • Distributed ownership (top contributor 47% of recent commits)
  • MIT licensed
  • CI configured
  • Tests present
  • ⚠ Slowing — last commit 3mo ago

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live go-shiori/shiori repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/go-shiori/shiori.

What it runs against: a local clone of go-shiori/shiori — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in go-shiori/shiori | Confirms the artifact applies here, not a fork | | 2 | License is still MIT | Catches relicense before you depend on it | | 3 | Default branch master exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 125 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>go-shiori/shiori</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of go-shiori/shiori. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/go-shiori/shiori.git
#   cd shiori
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of go-shiori/shiori and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "go-shiori/shiori(\\.git)?\\b" \\
  && ok "origin remote is go-shiori/shiori" \\
  || miss "origin remote is not go-shiori/shiori (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
  && ok "license is MIT" \\
  || miss "license drift — was MIT at generation time"

# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
  && ok "default branch master exists" \\
  || miss "default branch master no longer exists"

# 4. Critical files exist
test -f "internal/cmd/root.go" \\
  && ok "internal/cmd/root.go" \\
  || miss "missing critical file: internal/cmd/root.go"
test -f "internal/cmd/serve.go" \\
  && ok "internal/cmd/serve.go" \\
  || miss "missing critical file: internal/cmd/serve.go"
test -f "go.mod" \\
  && ok "go.mod" \\
  || miss "missing critical file: go.mod"
test -f "internal/assets.go" \\
  && ok "internal/assets.go" \\
  || miss "missing critical file: internal/assets.go"
test -f "internal/cmd/add.go" \\
  && ok "internal/cmd/add.go" \\
  || miss "missing critical file: internal/cmd/add.go"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 125 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~95d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/go-shiori/shiori"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

Shiori is a lightweight, single-binary bookmark manager written in Go that archives and indexes web pages for offline access. It offers both CLI and web UI interfaces, imports from Pocket/Netscape bookmarks, and supports SQLite, PostgreSQL, MariaDB, and MySQL backends—solving the problem of managing bookmarks across devices without relying on proprietary services. Monolithic Go binary with a clear separation: core bookmark/archive logic in Go, web frontend in Vue/TypeScript/JavaScript under internal/webui (inferred from .vue, .ts files), CLI via Cobra (spf13/cobra in go.mod), and pluggable storage layer supporting multiple databases via JMOIRON/sqlx.

👥Who it's for

Personal knowledge management users and power users who want self-hosted bookmark storage; developers who need a portable, scriptable bookmark tool; privacy-conscious individuals avoiding cloud-locked services like Pocket.

🌱Maturity & risk

Production-ready and actively maintained. The project has comprehensive CI/CD via GitHub Actions (see .github/workflows/), a Go 1.25.1 requirement, Docker support (multiple Dockerfile variants), and documented APIs (docs/API.md, docs/APIv1.md). Regular dependency updates via Dependabot and structured versioning via .goreleaser.yaml indicate solid engineering practices.

Low-to-moderate risk. The project has a healthy dependency tree (go.mod shows 40+ direct deps), but the reliance on Playwright (go-community/playwright-go) for headless browser automation adds complexity. Single-maintainer concerns are mitigated by active CI and a Matrix community (#shiori:matrix.org). No critical breaking changes visible in recent release structure.

Active areas of work

Active development with recent workflow additions (.github/workflows includes _e2e.yml, _mkdocs-publish.yml, _swagger-check.yml), suggesting ongoing quality gates and documentation automation. Version bumping workflow (version_bump.yml) and dependabot.yml indicate disciplined release management.

🚀Get running

Clone the repo with git clone https://github.com/go-shiori/shiori && cd shiori. Install dependencies with go mod download (Go 1.25.1+ required). Build with make or go build -o shiori ./cmd/shiori. For web dev, check for Node/Bun setup (bun.lockb present) for frontend assets.

Daily commands: Run make dev or make (see Makefile for targets). For production: shiori serve (CLI). For containerized: docker-compose up using docker-compose.yaml. Frontend likely rebuilds via npm or bun install then bun run build (bun.lockb suggests Bun package manager).

🗺️Map of the codebase

  • internal/cmd/root.go — CLI entry point and command root definition—every developer must understand the command structure and how Shiori initializes
  • internal/cmd/serve.go — Web server setup and HTTP routing—essential for understanding how the API and frontend are served together
  • go.mod — Dependency manifest showing all external libraries (SQLite, JWT, HTTP router, database drivers)—critical for understanding architectural choices
  • internal/assets.go — Embedded static assets (frontend, stylesheets)—key to understanding how Shiori bundles frontend with the binary
  • internal/cmd/add.go — Bookmark creation logic and content fetching—core business logic for the primary use case
  • internal/cmd/import.go — Netscape bookmark import and data parsing—shows how external data is normalized into Shiori's schema
  • .github/workflows/push.yml — CI/CD pipeline for testing, building, and releasing—necessary to understand how to test changes locally and what automated checks run

🛠️How to make changes

Add a new CLI command

  1. Create a new file in internal/cmd/ (e.g., internal/cmd/mycommand.go) with a function NewCmdMyCommand() *cobra.Command returning a cobra command (internal/cmd/mycommand.go)
  2. Import and register your command in the root command's AddCommand() call (internal/cmd/root.go)
  3. Implement your command logic; use shiori's database abstraction for persistence (internal/cmd/mycommand.go)
  4. Add unit tests alongside your command file (e.g., internal/cmd/mycommand_test.go) (internal/cmd/mycommand_test.go)

Add a new REST API endpoint

  1. Define the route handler function in the http handler module (typically in internal/ or a dedicated http package) (internal/cmd/server.go)
  2. Register the route using the HTTP router (julienschmidt/httprouter) in the server setup function (internal/cmd/server.go)
  3. Implement middleware for authentication/authorization using JWT tokens (internal/cmd/server.go)
  4. Add E2E test cases in e2e/server/basic_test.go or a new test file to verify endpoint behavior (e2e/server/basic_test.go)

Add support for a new database backend

  1. Import the new database driver in go.mod (e.g., github.com/lib/pq for PostgreSQL is already present) (go.mod)
  2. Extend the database abstraction layer (likely in internal/db or similar) to support connection string parsing for your database (internal/cmd/root.go)
  3. Update the schema migration or initialization logic to handle the new database dialect (internal/cmd/serve.go)
  4. Add integration tests that spin up your database in Docker (via docker-compose.yaml) and run the full test suite (docker-compose.yaml)

Add a new import/export format

  1. Create a new handler file in internal/cmd/ (e.g., internal/cmd/importjson.go) to parse the new format (internal/cmd/importjson.go)
  2. Register the new command as a subcommand of import or create a standalone command in root.go (internal/cmd/root.go)
  3. Implement data normalization to map external format fields to Shiori's internal bookmark schema (internal/cmd/importjson.go)
  4. Add unit and E2E tests to verify parsing and data integrity (e2e/server/basic_test.go)

🔧Why these technologies

  • Go + Cobra — Single binary distribution; fast startup; native cross-platform compilation; Cobra provides structured CLI with subcommands
  • SQLite (primary) + MySQL/PostgreSQL support — SQLite for zero-config single-user deployments; SQL drivers for scalable multi-user server setups with shared databases
  • Embedded static assets (go:embed) — Bundles frontend into single binary; eliminates separate asset server; simplifies deployment and distribution
  • JWT + HTTP cookies — Stateless authentication suitable for distributed deployments; standard for REST APIs; no server-side session storage needed
  • httprouter + standard Go http — Lightweight, fast routing; minimal dependencies; composable middleware pattern; idiomatic Go
  • Playwright + Go testing — Browser-based E2E tests verify actual user flows; catches UI/JS regressions; cross-browser compatibility

⚖️Trade-offs already made

  • Single binary with embedded frontend vs. separate server + static asset CDN

    • Why: Simplicity, easy distribution, zero external dependencies at runtime
    • Consequence: Frontend cannot be updated without recompiling and redeploying; larger binary size (~50MB+); not suitable for large-scale static file serving
  • SQLite as default database vs. mandatory PostgreSQL

    • Why: Low barrier to entry; no database setup required for personal use
    • Consequence: Single-process SQLite cannot handle high concurrent writes; multi-user server deployments must switch to MySQL/PostgreSQL
  • Stateless JWT authentication vs. server-side sessions

    • Why: Horizontally scalable; no session store needed; works across load balancers
    • Consequence: Token revocation requires additional mechanism (e.g., blacklist); token payload is visible to client (though cryptographically signed)
  • Embed bookmarks and metadata in single binary vs. separate data files

    • Why: Distribution simplicity; prevents accidental separation of code and data
    • Consequence: Database migrations must be built-in; rolling back versions requires schema compatibility logic

🚫Non-goals (don't propose these)

  • Real-time multi-user collaboration (no WebSockets or CRDT sync)
  • Full-text search across bookmark content (relies on database native FTS, not external search engine)
  • Mobile native apps (web-only; responsive design compensates)
  • OAuth provider integration (only OAuth consumer for Pocket import)
  • Advanced analytics or usage tracking
  • Plugin/extension system

🪤Traps & gotchas

Storage choice at install time: database is configured via environment variables (see docs/Configuration.md)—changing DB post-setup requires migration. Web extension requires separate repo: shiori-web-ext is a different GitHub repo (go-shiori/shiori-web-ext); the main binary doesn't include it. Playwright dependency: headless browser automation may fail in containers without browser binaries—see Dockerfile.e2e for correct image setup. JWT secret generation: ensure a strong secret is set before deploying (no default in code visible). Netscape bookmark import: format is strict—malformed HTML bookmarks fail silently.

🏗️Architecture

💡Concepts to learn

  • Full-Text Search (SQLite FTS5) — Shiori uses git.sr.ht/~emersion/go-sqlite3-fts5 for fast text search across bookmarks and archive content; understanding FTS query syntax and index design is essential for implementing search features.
  • Web Content Readability Extraction — The go-shiori/go-readability dependency extracts article-like content from web pages (filtering out nav, ads, etc.); critical for the 'reader mode' feature showcased in the README.
  • WARC (Web Archive Format) — Shiori archives pages as WARC files for offline replay; understanding WARC structure helps in debugging archive failures and implementing custom archive formats.
  • Database Abstraction Layer (sqlx) — The project uses jmoiron/sqlx to support multiple databases (SQLite, PostgreSQL, MySQL, MariaDB) with the same code; understanding prepared statements and dialect-specific SQL is crucial for schema changes.
  • JWT (JSON Web Token) Authentication — Shiori uses golang-jwt/jwt/v5 for stateless API auth; required knowledge for securing new endpoints and understanding session-less REST API design.
  • Headless Browser Automation (Playwright) — The project uses playwright-community/playwright-go for JavaScript-heavy sites and dynamic content capture; understanding browser automation trade-offs (speed vs. accuracy) affects archive quality.
  • Docker Multi-Stage Builds & Compose — Multiple Dockerfiles (Dockerfile, Dockerfile.alpine, Dockerfile.e2e) and docker-compose.yaml show production-grade containerization; essential for understanding deployment and local dev environment setup.
  • go-shiori/shiori-web-ext — Official Firefox and Chrome web extension for Shiori; allows users to save bookmarks directly from the browser toolbar.
  • go-shiori/go-readability — Extracted readability library (Go port of Mozilla's Readability); core dependency for parsing readable content from web pages.
  • go-shiori/go-epub — EPUB generation library used by Shiori to create offline archives of bookmarks in e-book format.
  • linkding/linkding — Alternative self-hosted bookmark manager in Python + Django; solves the same problem with a different stack and UI paradigm.
  • ArchiveBox/ArchiveBox — Larger-scale web archival tool (Python); overlaps in archiving and offline access but designed for site-wide preservation rather than personal bookmarks.

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add integration tests for database abstraction layer across MySQL, PostgreSQL, and SQLite

The repo supports multiple database backends (MySQL, PostgreSQL, SQLite via lib/pq, go-sql-driver/mysql, mattn/go-sqlite3, modernc.org/sqlite) but there's no evidence of comprehensive integration tests validating schema consistency and query behavior across all three. The testcontainers-go dependency is available but underutilized. This would catch regressions when database queries change and ensure feature parity across backends.

  • [ ] Create internal/database/integration_test.go with TestSuite using testcontainers-go
  • [ ] Add tests for CRUD operations on bookmarks, users, and tags tables across all three DB drivers
  • [ ] Verify FTS5 queries (go-sqlite3-fts5 dependency) work consistently in test matrix
  • [ ] Add to GitHub Actions workflow (_test.yml) to run against containerized MySQL, PostgreSQL instances

Add end-to-end tests for WARC export/import functionality

The repo has go-shiori/warc as a dependency and likely supports WARC format for archiving bookmarks (common in bookmark managers), but there's no evidence of e2e tests validating the full export→import cycle. The Dockerfile.e2e exists but its test coverage is unclear. This would ensure data integrity for users migrating between systems.

  • [ ] Create internal/warc/integration_test.go testing Export() and Import() functions end-to-end
  • [ ] Verify round-trip consistency: create bookmarks → export to WARC → import → validate all fields preserved
  • [ ] Add playwright-go tests in docs/e2e/ for WARC export through the web UI
  • [ ] Add test case to .github/workflows/_e2e.yml

Add API documentation and tests for missing endpoints referenced in docs/API.md vs docs/APIv1.md

The repo has two separate API documentation files (API.md and APIv1.md), suggesting API versioning, but no swagger spec validation in CI beyond _swagger-check.yml. Gaps likely exist between documented endpoints, swagger definitions, and actual implementations. This would prevent API drift and improve developer experience.

  • [ ] Audit docs/API.md and docs/APIv1.md against actual route handlers in cmd/shiori/ and internal/http/
  • [ ] Run swag init to generate OpenAPI spec from Go annotations and compare against documentation
  • [ ] Add unit tests in internal/http/*_test.go for any undocumented or recently added endpoints (e.g., new bookmark filtering)
  • [ ] Update .github/workflows/_swagger-check.yml to validate spec completeness and fail if endpoints lack swagger comments

🌿Good first issues

  • Add unit tests for the WARC archiving module (go-shiori/warc integration). The project has CI for testing (.github/workflows/_test.yml) but the archive layer likely lacks coverage.
  • Document the database schema and migration process in docs/Storage.md. Current docs lack concrete table definitions and column types, making it hard for contributors to understand the model layer.
  • Create an example integration test for the Pocket importer in .github/workflows/_e2e.yml. The feature exists (mentioned in README) but no visible e2e test validates the import flow end-to-end.

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 585ea34 — chore(deps): bump the all group with 9 updates (#1151) (dependabot[bot])
  • 06de3f4 — chore(deps): updated go, alpine, frontend/webapp and golangci lint (#1063) (fmartingr)
  • da56f82 — ci: fix secrets access (fmartingr)
  • d5a4f23 — ci: add DockerHub publishing for versioned releases (#1148) (fmartingr)
  • ca949c5 — fix: resolve linting errors and e2e test failures (fmartingr)
  • 9f6a4c3 — feat: support proxy forward headers authentication (#1105) (pudymody)
  • 24e06a5 — docs: fix issue with reverse proxy webroot configuration (#1121) (mannykary)
  • b58e76e — docs: add cloudbreak.app under managed hosting (#1123) (benjaminoakes)
  • 98564fa — ci: never close stale issues/rps (fmartingr)
  • 3091d84 — fix: parse pocket new CSV format (#1112) (bakito)

🔒Security observations

  • High · Outdated Go Version and Dependencies — go.mod, .github/workflows/_test.yml. The project uses Go 1.25.1 which may contain unpatched security vulnerabilities. Additionally, several dependencies have known CVEs and are outdated. Notable examples include: golang.org/x/crypto v0.42.0 (missing critical patches), golang.org/x/net v0.44.0, and mattn/go-sqlite3 v1.14.32. The project should be updated to use the latest stable Go version and all dependencies should be audited and updated. Fix: 1. Upgrade Go to the latest stable version (1.23+). 2. Run 'go get -u ./...' to update all dependencies. 3. Run 'go list -u -m all' to identify outdated packages. 4. Implement automated dependency scanning in CI/CD pipeline using 'nancy' or 'govulncheck'.
  • High · Missing Input Validation for Database Queries — Internal database layer (not visible in provided structure). The codebase uses 'github.com/huandu/go-sqlbuilder' and 'github.com/jmoiron/sqlx' for database operations. Without visible parameterized query enforcement in the file structure, there is risk of SQL injection vulnerabilities if user input is concatenated directly into queries. Fix: 1. Audit all database query construction to ensure parameterized queries are used. 2. Use prepared statements exclusively. 3. Implement input validation and sanitization for all user-supplied data. 4. Use ORM features that prevent SQL injection. 5. Add automated SAST scanning to detect SQL injection patterns.
  • High · Potential XSS Vulnerabilities in Web Interface — Web interface layer, content rendering handlers. The application handles user bookmarks and content rendering without visible security header configurations. The presence of 'github.com/go-shiori/go-readability' and 'github.com/go-shiori/go-epub' suggests HTML content is being parsed and rendered, creating XSS vulnerability risks if content sanitization is not properly implemented. Fix: 1. Implement Content Security Policy (CSP) headers. 2. Use HTML sanitization libraries (e.g., 'bluemonday'). 3. Escape all user-controlled output. 4. Implement X-Content-Type-Options: nosniff header. 5. Add automated XSS scanning in CI/CD pipeline.
  • High · Weak Cryptographic Implementation — go.mod, Authentication layer. The use of golang.org/x/crypto v0.42.0 is significantly outdated. This version lacks important security patches for encryption and hashing functions. The JWT library (golang-jwt/jwt/v5) may be vulnerable to algorithm confusion attacks if not properly configured. Fix: 1. Upgrade golang.org/x/crypto to v0.46.0 or later. 2. Review JWT configuration to ensure 'alg' is explicitly validated. 3. Implement strong key rotation policies. 4. Use only recommended algorithms (HS256, RS256, ES256).
  • Medium · Insufficient TLS Configuration in Scratch Docker Image — Dockerfile, docker-compose.yaml. The production Dockerfile uses 'FROM scratch' with manually copied certificates. This minimal approach can make debugging and patching difficult. The application exposes port 8080 without HTTPS configuration visible, requiring reverse proxy setup for secure communication. Fix: 1. Use Alpine as base image instead of scratch for maintainability. 2. Implement TLS/HTTPS directly in the application. 3. Use environment variables for certificate paths. 4. Add security headers (HSTS, X-Frame-Options, etc.). 5. Document required reverse proxy configuration (nginx/Traefik).
  • Medium · Database Credentials in Docker Compose — docker-compose.yaml. The docker-compose.yaml contains hardcoded database credentials (POSTGRES_PASSWORD: shiori) and environment variable examples with credentials visible in comments. This is a security risk if the file is committed to version control. Fix: 1. Use .env files (add to .gitignore). 2. Use docker-compose.override.yml for local credentials. 3. Remove credential examples from version control. 4. Implement environment-based secret management. 5. Add pre-commit hooks to prevent credential commits.
  • Medium · undefined — undefined. undefined Fix: undefined

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Healthy signals · go-shiori/shiori — RepoPilot