# Project pack: Linkwarden @ 952ac45

Facts about **Linkwarden** for an AI following the [BespokeAI handover guide](https://handoff.bespokeai.software/guide/v2.md). This pack is pinned to one version and never changes after publication.

> Linkwarden is a bookmark keeper that also saves a full copy of each page, as a screenshot, a PDF and a readable version, so recipes, articles and guides stay with you even if the website changes or vanishes. Sort links into collections and tags, highlight and annotate as you read, and share collections with family or friends. It runs on your own server, with phone apps and a browser extension.

## Version and source
- Repository: https://github.com/linkwarden/linkwarden
- Pinned commit: `952ac4540657cae3a67c3ca59433899d2fda8374`. Use exactly this version.
- Fresh checkout: use the setup below in an empty folder. If you already have this exact source checked out, keep your branch and use the development command instead; do not clone again or reset local changes.
- Or download a zip: https://github.com/linkwarden/linkwarden/archive/952ac4540657cae3a67c3ca59433899d2fda8374.zip
- Official docs: https://docs.linkwarden.app
- Live demo: https://demo.linkwarden.app. Official public demo, shared by everyone. Don't save anything private.
- Licence: **AGPL-3.0-only**. Free to use and change. If you run a changed version that other people use over the internet (not just you or your household), you must offer them your source code under the same licence. Private use has no extra obligations.

## Running the pinned source
- Fresh pinned-source setup (skip clone and checkout in an existing worktree):

  ```sh
  git clone https://github.com/linkwarden/linkwarden bespoke-linkwarden
  cd bespoke-linkwarden
  git checkout --detach 952ac4540657cae3a67c3ca59433899d2fda8374
  # Prepare development requirements and local configuration first.
  # Setup notes: web app on :3000 plus the worker
  yarn install && cp .env.sample .env && yarn prisma:generate && yarn prisma:deploy && yarn concurrently:dev
  ```

The commands below are a starting point from the reviewed source. Prepare the listed tools, local configuration and companion services before starting the app. These instructions have not yet been validated in participant sessions.

### Separate trial of the current release
This tries an upstream release, which may differ from the pinned source above. It does not run your source changes. Use only sample data and a separate data directory or volume. Check the upstream documentation before running it.

  ```sh
  mkdir linkwarden && cd linkwarden
  curl -O https://raw.githubusercontent.com/linkwarden/linkwarden/952ac4540657cae3a67c3ca59433899d2fda8374/docker-compose.yml
  curl -L https://raw.githubusercontent.com/linkwarden/linkwarden/952ac4540657cae3a67c3ca59433899d2fda8374/.env.sample -o .env
  # Edit .env: set NEXTAUTH_SECRET, POSTGRES_PASSWORD and MEILI_MASTER_KEY to three different long random phrases.
  docker compose up -d
  # Then open http://localhost:3000 and create your account.
  ```

- Needs: Docker with Docker Compose; about 4 GB RAM and 2 CPU cores, For development, Node 20+ and Yarn (Corepack), PostgreSQL, and Playwright's Chromium for page capture
- Develop: `yarn install && cp .env.sample .env && yarn prisma:generate && yarn prisma:deploy && yarn concurrently:dev   # web app on :3000 plus the worker`
- Test: `yarn test   # vitest; e2e tests: yarn workspace @linkwarden/web e2e`
- Check/lint: `yarn workspace @linkwarden/web lint`
- Build: `yarn prisma:generate && yarn web:build`
- Stack: typescript, javascript, css (Next.js, React, Prisma, Tailwind CSS, Playwright, Expo (mobile)). Difficulty for a non-developer with an AI: **moderate**. Theme, preservation formats, AI tagging, sign-up and storage are settings or environment variables. The code is a TypeScript monorepo: a Next.js web app (apps/web), a background worker that captures pages (apps/worker), a Prisma database package and a mobile app. Pages and API routes are easy to find; anything touching preservation runs a headless browser and needs the full Docker stack to test.

## Customisation points (settings → extend → code)
- **preferences** [settings]: Light, dark or system theme, which formats to save (screenshot, PDF, readable, full page, archive.org), what opens when you click a link, and duplicate checks. → `Settings → Preference (apps/web/pages/settings/preference.tsx)`
- **ai-tagging** [settings]: Auto-tag links using existing tags, a fixed list of tags or freely, with a local or cloud model. → `Settings → Preference → AI settings, plus AI environment variables (apps/worker/lib/autoTagLink.ts, prompts in apps/worker/lib/prompts.ts)`
- **collections-and-sharing** [settings]: Nested collections with colours and icons, members with their own permissions, and public read-only pages. → `Collections (apps/web/pages/collections/, apps/web/pages/public/)`
- **language** [settings]: Interface language. → `Settings → Account; 15 languages in apps/web/public/locales/`
- **server-options** [settings]: Sign-up on/off, storage, single sign-on, email, worker timeouts and limits. → `.env (all options in .env.sample; docs.linkwarden.app/self-hosting/environment-variables)`
- **rss** [extend]: Save new posts from blogs and news sites automatically into a collection. → `Settings → RSS subscriptions (apps/web/pages/settings/rss-subscriptions.tsx, apps/worker/workers/rssPolling.ts)`
- **import-export** [extend]: Bring years of bookmarks in, and take them out again. → `Import from a browser bookmarks file, Pocket, Omnivore, Wallabag or Linkwarden (apps/web/components/ImportDropdown.tsx, apps/web/pages/api/v1/migration/)`
- **api-and-extension** [extend]: Save links from scripts, the iOS Shortcut, the browser extension or Floccus bookmark sync. → `REST API at /api/v1 with tokens from Settings → Access tokens (apps/web/pages/api/v1/, apps/web/pages/settings/access-tokens.tsx); browser extension in apps/extension/`
- **web-pages** [code]: Change screens, cards and layouts. → `apps/web/pages/ (dashboard.tsx, links/, collections/), apps/web/components/ (e.g. LinkViews/), styles in apps/web/styles/globals.css`
- **preservation** [code]: Change how pages are captured and stored. → `apps/worker/lib/preservationScheme/ and apps/worker/workers/linkProcessing.ts`
- **data-model** [code]: New fields, e.g. a "cooked it" date on recipes. Needs a Prisma migration. → `packages/prisma/schema.prisma + packages/prisma/migrations/`

## Must keep working
- Saved links, page copies and highlights. Back up the database and the data folder together before any change.
- Collection permissions and public links. Members must only see collections shared with them.
- The REST API under apps/web/pages/api/v1/. The phone apps, browser extension and Floccus sync depend on it.
- Database changes need a Prisma migration in packages/prisma/migrations/.
- Page-capture safety (apps/worker/lib/ssrfProxy.ts, protectPageRequests.ts). Don't weaken it.

## Staying updatable
Preferences, environment variables, RSS, import and the API cover most personal needs without a fork. Upstream asks contributors to pick a popular suggestion and contact the maintainer (@daniel31x13) before starting, and takes translations only through Crowdin. There is no AGENTS.md or CLAUDE.md.

## Data, costs and safety
- Personal data: **yes**. Stored in: PostgreSQL (./pgdata) for links, collections, tags and highlights; page copies, screenshots, PDFs and uploads in ./data (or S3); the search index in ./meili_data. Back up ./pgdata (or a pg_dump) and ./data together before upgrades or code changes. Settings → Account lets you export your data. Your saved links reveal a lot about your interests, health and money.
- Service: AI provider for tagging (local Ollama, OpenAI, Anthropic, Azure, OpenRouter, Perplexity or any OpenAI-compatible API) (optional, free with Ollama on your own server, otherwise pay-per-use): Suggests tags for each saved link from the page's text.
- Service: Internet Archive (Wayback Machine) (optional, free): Optionally sends each saved link to archive.org for a public snapshot.
- Service: Email (SMTP) (optional, free with most providers): Sign-in links, verification and password resets.
- Service: S3-compatible storage (optional, pay-per-use): Keep page copies and uploads outside the server (SPACES_* variables).
- Service: Single sign-on providers (Authentik, Authelia, Google, Microsoft and many more) (optional, free or varies): Log in with an existing account.
- Setting `NEXTAUTH_SECRET` (**secret**: the person enters it themselves, never in chat): Long random phrase that protects logins and links to saved copies. Never paste it into an AI chat.
- Setting `NEXTAUTH_URL`: Your address followed by /api/v1/auth, e.g. http://localhost:3000/api/v1/auth.
- Setting `POSTGRES_PASSWORD` (**secret**: the person enters it themselves, never in chat): Database password. Never paste it into an AI chat.
- Setting `MEILI_MASTER_KEY` (**secret**: the person enters it themselves, never in chat): Key for the built-in Meilisearch full-text search. Never paste it into an AI chat.
- Setting `NEXT_PUBLIC_DISABLE_REGISTRATION`: Set to true after creating your accounts so strangers can't sign up.
- Setting `STORAGE_FOLDER`: Folder for page copies and uploads (default data, the ./data volume in Docker).
- Setting `NEXT_PUBLIC_OLLAMA_ENDPOINT_URL`: Address of your Ollama server for private AI tagging, with OLLAMA_MODEL (e.g. phi3:mini-4k).
- Setting `OPENAI_API_KEY` (**secret**: the person enters it themselves, never in chat): Key for OpenAI or an OpenAI-compatible provider (with OPENAI_MODEL and CUSTOM_OPENAI_BASE_URL). ANTHROPIC_API_KEY, OPENROUTER_API_KEY and others work the same way. Never paste keys into an AI chat.
- Setting `NEXT_PUBLIC_USER_CONTENT_DOMAIN`: A second web address for serving saved page copies, so a harmful saved page can't reach your account.
- Setting `ALLOW_PRIVATE_NETWORK_ACCESS`: Allow saving pages on your home network (e.g. a router page). Off by default for safety.
- Change NEXTAUTH_SECRET, POSTGRES_PASSWORD and MEILI_MASTER_KEY from the sample values, and set NEXT_PUBLIC_DISABLE_REGISTRATION=true once your accounts exist.
- Saved web pages are untrusted content. On an internet-facing server, use HTTPS and a separate user-content domain (NEXT_PUBLIC_USER_CONTENT_DOMAIN).
- AI tagging sends the text of each saved page to the provider you choose. Use Ollama to keep it on your own server.
- Keep ALLOW_PRIVATE_NETWORK_ACCESS off unless you need it, so links can't be used to probe your home network.

---

# Customising Linkwarden

Linkwarden is a TypeScript monorepo: a Next.js web app (`apps/web`), a background worker that visits each saved page and stores copies (`apps/worker`), a Prisma database package (`packages/prisma`), a phone app (`apps/mobile`) and a browser extension (`apps/extension`). **Most personal changes are preferences, environment variables, RSS, import or the API.** Upstream has no AGENTS.md, so follow the existing code style (Prettier is configured).

## Level 1: Settings (no code)
- **Preferences:** Settings → Preference. Light, dark or system theme; which formats to save for new links (screenshot, PDF, readable text, full single-file page, archive.org snapshot); what opens when you click a link; duplicate-link warnings.
- **AI tagging:** set a provider in `.env` (`NEXT_PUBLIC_OLLAMA_ENDPOINT_URL` + `OLLAMA_MODEL` for local, or `OPENAI_*`, `ANTHROPIC_*`, `OPENROUTER_*`, `AZURE_*`, `PERPLEXITY_*`), then choose in Settings → Preference whether to tag freely, from your existing tags, or from a fixed list. Guide: https://docs.linkwarden.app/self-hosting/ai-worker
- **Collections:** nested collections with colours and icons; invite members with view, add, edit or delete rights; make a collection public as a read-only page.
- **Language:** Settings → Account (15 languages).
- **Server:** everything else is in `.env` (`.env.sample` lists all options): `NEXT_PUBLIC_DISABLE_REGISTRATION`, storage (`STORAGE_FOLDER` or `SPACES_*` for S3), email, dozens of single sign-on providers, worker timeouts and limits.

## Level 2: Extend (no fork)
- **RSS:** Settings → RSS subscriptions saves new posts from a feed into a collection automatically.
- **Import and export:** Settings → Account. Import a browser bookmarks HTML file, Pocket, Omnivore, Wallabag or another Linkwarden; export your data.
- **REST API:** `/api/v1/...` with a token from Settings → Access tokens (`Authorization: Bearer <token>`). Links, collections, tags, highlights, search, RSS and more. The OpenAPI file lives in the `linkwarden/docs` repository (`openapi/linkwarden.yaml`).
- **Clients:** official browser extension (`apps/extension`), iPhone and Android apps, an iOS Shortcut, SingleFile upload and Floccus bookmark sync.

## Level 3: Code

| Change | Where |
|---|---|
| Pages | `apps/web/pages/` (`dashboard.tsx`, `links/`, `collections/`, `public/`, `settings/`) |
| Link cards and lists | `apps/web/components/LinkViews/` |
| Styles | `apps/web/styles/globals.css`, `apps/web/tailwind.config.js` |
| API routes | `apps/web/pages/api/v1/` with helpers in `apps/web/lib/` |
| Page capture | `apps/worker/lib/preservationScheme/`, `apps/worker/workers/linkProcessing.ts` |
| AI tagging | `apps/worker/lib/autoTagLink.ts`, prompts in `apps/worker/lib/prompts.ts` |
| Data | `packages/prisma/schema.prisma` + a migration (`yarn prisma:dev`) |
| Text | `apps/web/public/locales/en/common.json` (other languages come from Crowdin) |

## Running locally
- **Just use it (Docker):**
  1. `mkdir linkwarden && cd linkwarden`
  2. Download `docker-compose.yml` and `.env.sample` (saved as `.env`) from the pinned commit (see the quickstart in `bespoke.yaml`).
  3. In `.env`, set `NEXTAUTH_SECRET`, `POSTGRES_PASSWORD` and `MEILI_MASTER_KEY` to three different long random phrases.
  4. `docker compose up -d`, then open http://localhost:3000.
- **Develop:** `yarn docker:build` runs the dev compose stack from your code. Without Docker: Node 20+, Yarn, PostgreSQL; `yarn install`, `cp .env.sample .env` (set `DATABASE_URL`), `yarn prisma:generate`, `yarn prisma:deploy`, then `yarn concurrently:dev`.
- **Check:** `yarn test` (Vitest), `yarn workspace @linkwarden/web lint`, and `yarn workspace @linkwarden/web e2e` for Playwright tests.

## Pitfalls
- Back up the database and the `data` folder together; one without the other leaves broken links to copies.
- The worker runs a real browser against untrusted pages. Keep the SSRF protections in `apps/worker/lib/ssrfProxy.ts` and `protectPageRequests.ts` intact.
- Phone apps and the extension call the API. Add new endpoints instead of changing existing responses.
- Don't edit non-English locale files directly; upstream syncs them from Crowdin.

---
Machine-readable: [bespoke.yaml](https://handoff.bespokeai.software/p/linkwarden/952ac45/r2/bespoke.yaml) · Hosting options: [hosting.md](https://handoff.bespokeai.software/p/linkwarden/952ac45/r2/hosting.md) · Agent skill: [SKILL.md](https://handoff.bespokeai.software/p/linkwarden/952ac45/r2/SKILL.md)
