Project Documentation

StartPage Administrator Guide

Deployment, operations, and maintenance guidance for the StartPage project.

Table of Contents

  1. Overview
  2. Embed Mode & Themes
  3. Deployment Options
  4. Docker Deployment
  5. Local Development
  6. Database Management
  7. Database Migration Guide
  8. Backup and Restore
  9. Key Application Routes
  10. Configuration
  11. Troubleshooting

Overview

StartPage is a self-hosted, frecency-based link manager designed to serve as your browser start page. Links are dynamically ranked using a weighted algorithm that combines frequency and recency of access.

Key Features

Feature highlights

Guided tag entry

Any tag input rendered with data-tag-input automatically bootstraps the suggestion widget defined in static/js/theme.js. Pass the existing tag names through data-tag-suggestions (JSON string) and the UI handles filtering, deduping, and insertion. This now powers the Add/Edit forms and the bulk tag field on /dashboard.

  • Suggestions appear on focus and update as the user types.
  • Comma-separated inputs are respected, so pasting multiple tags still works.
  • Suggestions exclude already-added tags to prevent duplicates.

Tag rename & merge

The /tags view now exposes an inline Rename control for every tag. Submitting the form posts to /tag/:id/rename and ultimately db_utils.rename_tag.

  • Incoming names are lowercased, whitespace becomes hyphens, non-alphanumeric characters are stripped, and values are trimmed server-side.
  • If the destination name already exists, the links are merged into that tag and the original row is removed.
  • Otherwise the row is renamed in place and counts are recalculated.

Import & export

Admins can download backups and restore them from the dashboard banner. Exports stream via /exports/csv or /exports/json, while imports POST to /imports using the same schema.

  • CSV files use the header id,name,url,rank,accessed,tags with semicolon-delimited tags.
  • JSON exports mirror the same schema with a tags array.
  • Leave id blank to create a new link during import; provide an id to overwrite the name, URL, rank, accessed timestamp, and tags.
  • Headless servers can run uv run python -m services.io_utils csv -o backups/links.csv to export and curl -F format=csv -F upload=@links.csv http://host/imports to restore.

Auto-fill link names

The /fetch-title endpoint automatically extracts page titles from URLs to populate link names, improving user experience when adding bookmarks.

  • Uses httpx library with 10-second timeout for HTTP requests.
  • Regex-based HTML parsing to extract <title> tags (no BeautifulSoup dependency).
  • HTMX integration with 800ms debounce on URL field changes.
  • Graceful failure returns None if title cannot be fetched.
  • JavaScript handler populates link name only if field is empty, allowing user overrides.

Duplicate detection

The /duplicates/check endpoint provides real-time validation to prevent duplicate links by checking both URL and name uniqueness.

  • HTMX-powered with 500ms debounce on field changes.
  • Returns warning messages if duplicate URL or name is found.
  • Visual feedback with loading indicators during validation.
  • Checks run asynchronously without blocking form submission.
  • Helps maintain database integrity by preventing user errors.

Embed Mode & Themes

StartPage supports URL parameters for creating embeddable widgets and forcing specific themes. These features are perfect for browser extensions, iframe integrations, or custom start page setups.

Embed Mode

Add ?embed=true to any URL to enable embed mode. This hides the navigation bar and footer, creating a minimal widget view.

Example URLs:

http://localhost:8080/?embed=true
http://localhost:8080/?embed=true&tag=work
http://localhost:8080/dashboard?embed=true

Theme Parameter

Force a specific theme using the ?theme parameter. Available options:

Example URLs:

http://localhost:8080/?theme=dark
http://localhost:8080/?theme=light

Combining Parameters

You can use both embed mode and theme parameters together. All navigation within embed mode (tag filters, search, pagination) automatically preserves these parameters.

Example URLs:

http://localhost:8080/?embed=true&theme=dark
http://localhost:8080/?embed=true&theme=light&tag=personal

Use Cases

Browser Extensions

Create a new tab extension that shows your most-used links in a clean, minimal interface without navigation chrome.

Iframe Embeds

Embed StartPage into dashboards, home automation panels, or other web applications with theme matching.

Multiple Monitors

Display different tag-filtered views on different screens, each with its own theme preference.

Kiosk Mode

Run StartPage on a dedicated display with a locked theme and minimal UI for public or shared workspaces.

Implementation Details

The embed mode and theme parameters are:

Deployment Options

Docker (Recommended)

Best for production deployments. Isolated environment with automatic restarts.

  • Easy to deploy
  • Consistent environment
  • Automatic restart on failure
  • Data persistence via volumes

Local/Screen Session

Good for development or personal deployments on a dedicated server.

  • Direct access to code
  • Hot reload for development
  • No containerization overhead
  • Requires manual restart

Docker Deployment

Quick Start

just dockerbuild

This command builds the container and runs it with persistent storage at ~/.config/startpage

Manual Docker Commands

Build the Docker image:

docker build . -t startpage

Run the container:

docker run -d -p 8080:8080 \
  --restart=always \
  --name startpage \
  -v ~/.config/startpage:/usr/startpage/data \
  startpage

Docker Configuration

Environment variables you can set:

Accessing the Application

Once running, access StartPage at: http://localhost:8080

Docker Management

# Stop the container
docker stop startpage

# Start the container
docker start startpage

# View logs
docker logs startpage

# Restart the container
docker restart startpage
# or via justfile shortcut
just restart

# Remove the container
docker rm -f startpage

Updating the Container (just docker-replace)

To pull the latest code and replace the running container in one step:

just docker-replace

This recipe runs git pull, rebuilds the image, stops and removes the old container, and starts the new one with the same volume mount. Equivalent to running the stop/rm/build/run sequence manually.

Local Development

Prerequisites

Install just using your platform package manager: just.systems/man/en/packages.html

Installation

# Install dependencies
uv sync
# or
just install

Running Locally

Development mode (with hot reload):

just develop
# or with a custom port (default is 8000, not the Docker port 8080)
just develop 8080
# or directly
uv run uvicorn main:app --reload

Production mode (in screen session):

just run

This runs the app in a detached screen session named startpage. Use screen -r startpage to attach.

Standard run:

uv run uvicorn main:app

Testing

# Run all tests
uv run pytest

# Run specific test module
uv run pytest services/test_db_utils.py

# Type checking
uv run mypy services

Database Management

Database Location

For the full schema including an ER diagram and column descriptions, see dbschema.md in the project root.

Direct SQL maintenance

For bulk edits or scripted maintenance you can work directly against the SQLite database.

  1. Connect with sqlite3.
    sqlite3 data/links.db
    .headers on
    .mode column
    SELECT id, name, rank FROM links ORDER BY rank DESC LIMIT 5;
    UPDATE links SET rank = 1.0 WHERE name = 'Docs';
    .quit
  2. Understand the schema. Core tables are links (primary data), tags, and tagmap. Foreign keys keep orphaned rows from lingering, so deleting a tag automatically clears tagmap entries.
  3. Automate with Python. Import services.db_utils in your own scripts to re-use helpers such as save_link, get_links, rename_tag, or delete_link.

Remember: treat data/links.db as disposable state—commit schema changes and SQL scripts, not the database file.

Schema Versions

Version Schema File Features
1.x db_v1.sql Basic links, rank, metadata tables
2.x db_v2.sql + Tags and tagmap tables

Checking Database Version

sqlite3 data/links.db "SELECT value FROM metadata WHERE name = 'db_version';"

Database Migration Guide

Automatic Migration

StartPage automatically migrates your database when you upgrade to a new major version. The migration happens on the first application start after upgrading.

How it works:

  1. Application starts and reads the current database version from metadata table
  2. Compares with the app version from pyproject.toml
  3. If major versions differ, runs the appropriate migration script from sql_scripts/
  4. Updates the db_version in the metadata table

Migration from v1 to v2

Version 2 adds the tagging feature. The migration adds two new tables:

Steps to migrate:

# 1. Backup your database
cp data/links.db data/links.db.backup

# 2. Pull the latest code
git pull origin main

# 3. Install dependencies
uv sync

# 4. Start the application
# Migration will run automatically
uv run uvicorn main:app --reload

# 5. Verify migration succeeded
sqlite3 data/links.db "SELECT value FROM metadata WHERE name = 'db_version';"
# Should output: 2.0.0

Manual Migration (if needed)

If automatic migration fails, you can run the migration script manually:

# Backup first!
cp data/links.db data/links.db.backup

# Run migration script
sqlite3 data/links.db < sql_scripts/v1_to_v2.sql

# Verify
sqlite3 data/links.db "SELECT name FROM sqlite_master WHERE type='table';"
# Should show: links, metadata, tags, tagmap

Rolling Back a Migration

If you need to roll back:

# Stop the application first
docker stop startpage
# or kill the uvicorn process

# Restore from backup
cp data/links.db.backup data/links.db

# Checkout previous version
git checkout v1.2.3

# Restart application
docker start startpage

Migration Troubleshooting

Problem: Migration script fails with "table already exists"

Solution: Check if you're already on the target version. Run: SELECT value FROM metadata WHERE name = 'db_version';

Problem: Application won't start after migration

Solution: Check logs for errors. Verify database integrity: sqlite3 data/links.db "PRAGMA integrity_check;"

Backup and Restore

Creating a Backup

Local deployment:

cp data/links.db data/links.db.backup-$(date +%Y%m%d)

Docker deployment:

cp ~/.config/startpage/links.db ~/.config/startpage/links.db.backup-$(date +%Y%m%d)

Automated Backups

Create a cron job for daily backups:

# Edit crontab
crontab -e

# Add this line (runs daily at 2 AM)
0 2 * * * cp ~/.config/startpage/links.db ~/.config/startpage/backups/links-$(date +\%Y\%m\%d).db

Restoring from Backup

# Stop the application
docker stop startpage

# Restore the backup
cp data/links.db.backup-20250101 data/links.db

# Start the application
docker start startpage

Exporting Data

Export to SQL format:

sqlite3 data/links.db .dump > startpage-export.sql

Export links to CSV:

sqlite3 -header -csv data/links.db "SELECT * FROM links;" > links.csv

Key Application Routes

StartPage exposes several important routes for end users and administrators. Understanding these helps with deployment, monitoring, and integration.

End User Routes

Route Purpose
/ Homepage with frecency-ranked links and infinite scroll
/dashboard Full link management with bulk operations (delete, tag)
/tags Tag directory with rename, merge, and delete controls
/stats Analytics dashboard showing rank distribution, top links, and pruning risks
/settings Configuration page for batch size, max rank, temporary links, and navigation behavior
/add Add new link with auto-fill, duplicate detection, and temporary link options
/edit/{link_id} Edit existing link properties and tags
/search HTMX-powered real-time search endpoint (query param: q)
/help End user documentation and feature guide

Admin & API Routes

Route Method Purpose
/exports/csv GET Export all links as CSV with headers: id,name,url,rank,accessed,tags
/exports/json GET Export all links as JSON array
/imports POST Bulk import links from CSV or JSON (form fields: format, upload)
/fetch-title GET Auto-fetch page title from URL for link name population
/duplicates/check GET Real-time duplicate detection (params: field=url|name, value)
/redirect/{link_id} GET Redirect to link URL and increment rank in background task
/dashboard/bulk-delete POST Delete multiple links by ID
/dashboard/bulk-tag POST Append tags to multiple links simultaneously

Documentation Routes

Route Audience Content
/help End Users Feature guide, daily usage tips, frecency explanation, and workflow documentation
/docs Developers / API Consumers FastAPI Swagger UI generated from the OpenAPI schema and API metadata
/redoc Developers / API Consumers Alternative ReDoc view of the same OpenAPI schema
docs/index.html Project Maintainers Static administrator guide intended for repository/GitHub Pages documentation

Note: app route /docs is the FastAPI docs UI. This static page lives in docs/index.html for the project documentation site.

Configuration

Application Settings

Settings are editable via the /settings screen (also linked in the app's navbar) and persisted to config.toml at the project root. Set STARTPAGE_CONFIG_PATH to point at a different writable TOML file if you want to store settings elsewhere.

Setting Default Description
frecency.batch_size 20 Number of links to load per page (pagination batch size)
frecency.max_rank 1000 Total rank pool allowed before cleanup runs
temp_links.enabled true Whether temporary links can be created
temp_links.default_ttl_hours 24 Duration applied when the "Default" preset is selected
temp_links.max_custom_hours 720 Upper bound for the "Custom" preset (in hours, up to 30 days)
temp_links.purge_interval_seconds 600 Background cleanup cadence in seconds (also runs on every read). The /settings UI displays and accepts this value in minutes.
reset_filter_on_click false When enabled, clicking a link clears the active tag filter and returns to the unfiltered home view. Stored in SQLite metadata (not config.toml); toggle via /settings.

Modifying Settings

Prefer the UI whenever possible. For automation/headless workflows, edit config.toml with your favorite editor or provision a file before booting the app:

# config.toml
[frecency]
batch_size = 50
max_rank = 2000

[temp_links]
enabled = true
default_ttl_hours = 12
max_custom_hours = 72
purge_interval_seconds = 900

Prometheus Metrics Endpoint

StartPage exposes a Prometheus-compatible endpoint at /metrics. Access is restricted by an IP/CIDR whitelist stored in the SQLite metadata table under metrics_whitelist.

Trusted proxy CIDRs used for forwarded /metrics headers are stored in metadata under trusted_proxy_cidrs.

Use the CLI helper to inspect and update the allow-list and trusted proxy entries:

# Local development usage
uv run python -m services.config_cli metrics-whitelist show

# Append a new CIDR/IP without replacing existing entries
uv run python -m services.config_cli metrics-whitelist add 10.0.0.0/8

# Remove an existing CIDR/IP entry
uv run python -m services.config_cli metrics-whitelist remove 10.0.0.0/8

# Replace allow-list entries
uv run python -m services.config_cli metrics-whitelist set 127.0.0.1/32 10.0.0.0/8

# Reset to local-only defaults (127.0.0.1 and ::1)
uv run python -m services.config_cli metrics-whitelist reset

# Clear all entries (blocks all /metrics clients)
uv run python -m services.config_cli metrics-whitelist clear

# Show trusted proxy CIDRs used for forwarded /metrics headers
uv run python -m services.config_cli trusted-proxies show

# Add your reverse proxy/container bridge source IP or CIDR
uv run python -m services.config_cli trusted-proxies add 172.17.0.1/32

# Docker shortcut (built into the image)
docker exec -it startpage startpage-config metrics-whitelist add 10.0.0.0/8
docker exec -it startpage startpage-config trusted-proxies add 172.17.0.1/32

When running behind nginx, pass the real client address explicitly so whitelist checks work correctly:

location / {
    proxy_pass http://127.0.0.1:8001;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $remote_addr;
}

Forwarded headers are only trusted when the direct client IP matches the trusted proxy CIDR list (default: 127.0.0.1/32,::1/128).

If nginx runs from another host/container network, add that proxy source CIDR with the CLI (for example trusted-proxies add 172.18.0.0/16).

Trusted proxy CIDR updates apply immediately because /metrics reads these values from metadata for each request.

The OpenAPI docs at /docs (and /redoc) include the /metrics operation details and 403 behavior when the source IP is not whitelisted.

PromQL Quick Tests

Replace job="startpage" if your Prometheus scrape job uses a different name.

# Scrape status
up{job="startpage"}

# Core gauges
startpage_links_total{job="startpage"}
startpage_temp_links_total{job="startpage"}
startpage_tags_total{job="startpage"}
startpage_rank_sum{job="startpage"}

# Temporary-link percentage
100 * startpage_temp_links_total{job="startpage"} / clamp_min(startpage_links_total{job="startpage"}, 1)

# /metrics traffic in last 24 hours
increase(startpage_metrics_requests_total{job="startpage"}[24h])
increase(startpage_metrics_denied_total{job="startpage"}[24h])

# Denied percentage in last 24 hours
100 * increase(startpage_metrics_denied_total{job="startpage"}[24h]) / clamp_min(increase(startpage_metrics_requests_total{job="startpage"}[24h]), 1)

# App uptime (hours)
startpage_metrics_uptime_seconds{job="startpage"} / 3600

# Whitelist entry count
startpage_metrics_whitelist_entries{job="startpage"}

# Counter resets in 7 days (process restart hint)
resets(startpage_metrics_requests_total{job="startpage"}[7d])

Frecency Algorithm Parameters

The ranking formula is defined in services/db_utils.py:

score = 10000 * rank * (3.75 / ((0.0001 * (current_time - last_accessed) + 1) + 0.25))

Temporary Link Controls

Mark a link as temporary from the add/edit form to set an automatic expiration (24 hours by default). Temporary links are highlighted in the UI and removed automatically when they expire.

Use /settings to toggle the feature, change the default duration, cap the custom duration, and adjust the cleanup interval. Updates are persisted to config.toml and apply immediately.

Monitoring with Statistics

The /stats route provides a comprehensive analytics dashboard for monitoring link health and usage patterns. This is valuable for administrators who want to understand how the system is being used and identify potential issues before they occur.

Available Metrics:

  • Rank Overview: Total links, sum/average/max rank, and percentage of max rank capacity used
  • Top Links: Top 10 most accessed links by rank with last access times
  • Recent Activity: Recently accessed links to track current usage patterns
  • At-Risk Links: 10 least accessed links with color-coded health status:
    • Red "Prune soon" - rank < 1.1 (will be deleted in next cleanup)
    • Yellow "Watchlist" - rank < 2.5 (declining but not critical)
    • Green "Healthy" - rank ≥ 2.5 (actively used)
  • Access Patterns: Oldest and newest access timestamps across the entire collection
  • Configuration Display: Current batch size, max rank threshold, and total pages

Use Cases:

Troubleshooting

Application won't start

Check:

  • Port 8080 is not already in use: lsof -i :8080
  • Database file permissions are correct
  • All dependencies are installed: uv sync

Database is corrupted

Check integrity:

sqlite3 data/links.db "PRAGMA integrity_check;"

If corrupted: Restore from backup

Links not appearing

Check:

  • Database has links: sqlite3 data/links.db "SELECT COUNT(*) FROM links;"
  • Ranks are above pruning threshold (> 1.0)
  • Browser console for JavaScript errors

Tags not working

Check:

  • Database version is 2.x or higher
  • Tag tables exist: sqlite3 data/links.db ".tables"
  • Run migration if on old version

Performance issues

Solutions:

  • Reduce batch size via /settings or config.toml
  • Clean up old links with low ranks
  • Vacuum database: sqlite3 data/links.db "VACUUM;"
  • Increase Docker workers (if using Docker)

Getting Help

For additional support:

StartPage v2.7.0 - Project Administrator Guide

Last updated: February 2026