Keep your chord charts in one place and play them from any screen. Tuner, hands-free
scrolling, and a clean performance view, with optional AI to tidy up a chart or rewrite the words.
Powered by any-llm -- routed through a single LLM gateway.
porchsongs preserves meter, rhyme scheme, chord alignment, and emotional meaning -- it only swaps out the imagery that doesn't fit.
porchsongs.ai is the hosted version of porchsongs. Sign in with Google, pick a plan, and start rewriting. No setup, no API keys to manage.
If you prefer to self-host or want to point porchsongs at your own LLM gateway, follow the Quick Start below.
- Paste your lyrics -- with or without chords, any format works
- Chat to workshop the lyrics -- tell the AI what to change ("swap the truck for my bike," "make verse 2 about coding") and iterate in a live conversation
- Play and enjoy -- chords are automatically realigned above your new lyrics, and any chart can be transposed to your key as you play
pip install uv
uv sync
cd frontend && npm install && npm run build && cd ..
cd backend
LLM_API_BASE=https://your-gateway/v1 LLM_API_KEY=your-key \
uv run uvicorn app.main:app --reloadOpen http://localhost:8000. porchsongs routes all AI traffic
through a single LLM gateway; set LLM_API_BASE and LLM_API_KEY (see the
environment variables below), then pick a model in Settings.
The AI is optional, and self-hosting without it is a supported setup:
cd backend && uv run uvicorn app.main:app --reloadImporting charts, storing tab PDFs, the library, the performance view, transpose, the tuner, hands-free scrolling, the chord dictionary, and PDF export all work with no gateway configured. Only three things need one: tidying up a chart's formatting, rewriting lyrics, and suggesting tags for a chart. Those actions disable themselves and say why. Tagging itself is entirely manual and always works; the AI suggestion is one opt-in tap on one chart.
This used to be impossible. Saving a chart went through the LLM parse endpoint and the save button was gated on having a model selected, so an instance with no gateway could not store a song at all.
Not everything in a songbook is a chord chart. Tab you have collected as PDFs can be stored alongside your charts and played from the same performance view: page forward and back with the on-screen controls, the arrow keys, or a bluetooth page turner, and zoom in to read the fret numbers from a stand.
Stored tabs are kept as files, not parsed. Nothing rewrites them, and no gateway is involved. Any one of them can be kept on the device for offline play, which is a per-tab choice rather than an automatic sync: a chart is a few kilobytes of text, but a tab collection is hundreds of megabytes and syncing all of it is not a favour. A kept tab opens with no network request at all.
By default, porchsongs runs in zero-config dev mode -- no login required, a local user is auto-created. See Authentication below for production setups.
For frontend development with hot reload:
# Terminal 1: backend
cd backend && uv run uvicorn app.main:app --reload
# Terminal 2: frontend (proxies /api to backend)
cd frontend && npm run devcp .env.example .env
# Edit .env -- set JWT_SECRET to a long random string
docker compose up --buildThis starts PostgreSQL + runs database migrations + serves the app on port 8000.
porchsongs supports three auth modes, controlled by environment variables:
No env vars needed. The app is open to anyone who can reach it. A local user is auto-created.
Set APP_SECRET to gate the app behind a password:
APP_SECRET=your-secret-password
JWT_SECRET=a-long-random-string-at-least-32-charsYou can also use a bcrypt hash for APP_SECRET:
# Generate a hash
python3 -c "import bcrypt; print(bcrypt.hashpw(b'mypassword', bcrypt.gensalt()).decode())"
# Use it in .env
APP_SECRET='$2b$12$...'For Google OAuth and other premium features, see the porchsongs-premium repo.
PREMIUM_PLUGIN=porchsongs_premium.pluginporchsongs uses PostgreSQL everywhere (production, development, and testing).
# Production (set in .env or environment)
DATABASE_URL=postgresql://porchsongs:porchsongs@localhost:5432/porchsongs
# Local dev / testing
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/porchsongs_test
# Quick PostgreSQL setup via Docker
docker run --name porchsongs-pg -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=porchsongs_test -p 5432:5432 -d postgres:16
# Apply migrations
uv run alembic upgrade headDocker Compose includes a PostgreSQL service and runs migrations automatically on startup.
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
postgresql://...localhost.../porchsongs |
Database connection string |
JWT_SECRET |
change-me-in-production |
Secret for signing JWT tokens (use 32+ chars) |
AUTH_BACKEND |
app_secret |
Auth mode: app_secret (premium plugins can add others) |
APP_SECRET |
(none) | Password gate (app_secret mode). Supports plaintext or bcrypt hash |
CORS_ORIGINS |
* |
Allowed CORS origins (comma-separated) |
JWT_EXPIRY_MINUTES |
15 |
Access token lifetime |
REFRESH_TOKEN_DAYS |
30 |
Refresh token lifetime |
PREMIUM_PLUGIN |
(none) | Module path for premium auth backend |
LLM_API_BASE |
(none) | LLM gateway base URL (OpenAI-compatible, e.g. https://your-gateway/v1). Required for AI features |
LLM_API_KEY |
(none) | LLM gateway API key (read server-side, never sent to the browser) |
LLM_PROVIDER |
otari |
any-llm provider name for the gateway |
See .env.example for the full list.
# Backend tests (118 tests)
uv run pytest
uv run pytest -v # verbose
uv run pytest tests/test_auth.py # auth tests only
# Frontend tests (39 tests)
cd frontend && npx vitest run
# Lint & type check
uv run ruff check backend/
uv run ruff format --check backend/
cd frontend && npx eslint src/
cd frontend && npm run typecheckporchsongs routes all AI traffic through a single LLM gateway using any-llm. Point LLM_API_BASE / LLM_API_KEY at any OpenAI-compatible gateway (an Otari deployment by default) and pick a model in Settings; the model catalog is discovered from the gateway. Keys live only on the server and are never sent from the browser.
| Website | any-llm.ai |
| GitHub | mozilla-ai/any-llm |
Routing everything through one gateway gives porchsongs:
- One place to manage credentials, models, and spend -- no per-provider keys in the app
- Model discovery -- the Settings model picker is populated from the gateway's catalog
- Consistent interface -- streaming, async, and reasoning work the same regardless of the model behind the gateway
Bring your own API key, configure it in Settings, and start rewriting.
