docs · users
Getting started
In under a minute: install the CLI, sign in with your handle, and upload your first item.
1. Install Bun
The @subgraph/cli is Bun-native. On macOS or Linux:
curl -fsSL https://bun.sh/install | bash On Windows, use the PowerShell installer from bun.sh. Minimum: Bun 1.3.
2. Clone and install
The CLI isn't on npm yet. From source:
git clone <repo>/subgraph-site
cd subgraph-site
bun install 3. Sign in
From the repo root:
cd packages/cli
bun run src/index.ts login me.bsky.social
Your browser will open to your PDS's OAuth flow. Authenticate. You'll see a
"Signed in. Close this tab." page; the CLI will show logged in as did:plc:….
Session tokens (DPoP-bound, refreshable) are stored at ~/.config/subgraph/session.json.
The sign-in flow uses an atproto OAuth 2.0 loopback redirect on
127.0.0.1:21849. If that port is in use, kill the other process
and try again — this is a known limitation we'll fix with an ephemeral port.
4. Upload
Photos, video, audio, recipes, files — the CLI auto-detects the kind:
bun run src/index.ts upload ~/Photos/*.jpg --series "Joshua Tree, Spring 2026"
bun run src/index.ts upload ~/Videos/demo.mp4 --title "Demo"
bun run src/index.ts upload ~/audio/episode-001.mp3 --title "Episode 1"
bun run src/index.ts recipe publish ./tacos.md
5. Long-form articles
Articles write to site.standard.document — they need a
publication container first:
subgraph pub init --name "My Blog" --url "https://example.com"
subgraph doc publish ./post.md 6. Other commands
subgraph whoami # show current identity
subgraph list # list your items
subgraph list series # list your series
subgraph series create "My trip" --publication at://...
subgraph delete at://did:plc:.../site.subgraph.item/<rkey>
subgraph logout # clear the session Troubleshooting
- Port 21849 in use — something (often a previous run) holds the
loopback port. Find and kill:
lsof -i :21849thenkill <pid>. - Sign-in timed out — the browser closed or the user cancelled before
completing auth. Run
subgraph loginagain. - "state mismatch on OAuth callback" — the callback URL carries a
statevalue that doesn't match what we sent. This is CSRF protection working as intended; a stale browser tab or a concurrent sign-in attempt is the usual cause. Close any open OAuth tabs, retry.