Save ideas worth coming back to.
Stashed is a self-hosted, shared idea archive designed for small groups. Members can save their own ideas, discover what others are thinking, and return to useful thoughts later.
Anyone can read public ideas. Authors can also keep ideas unlisted (available only by URL) or private (available only to themselves), and are the only members who can edit or delete their ideas.
Stashed is a full-stack web app: the React frontend and Hono API run together on a single Cloudflare Worker, backed by Cloudflare D1 and Drizzle ORM.
| Landing | Browse ideas |
|---|---|
![]() |
![]() |
| Read an idea | Explore a collection |
|---|---|
![]() |
![]() |
- GitHub authentication and registration
- Public, unlisted, and private idea visibility
- Collaborative Collections for organizing Ideas without changing their access rules
- A shared public idea feed, ordered by recently updated ideas
- Author-owned idea creation, editing, and deletion
- Idea statuses for tracking how thoughts develop
- Searchable tags created as ideas are saved
- Full-text search across idea titles and content
- Markdown editing and rendering
- Responsive light and dark appearances
Important
Registration is currently open through GitHub. Anyone who can reach a deployed instance and authenticate with GitHub can register; Stashed does not currently include invitations or an approved-members list.
- Frontend: React 19, Vite, TypeScript, Tailwind CSS v4, TanStack Router, TanStack Query, TanStack Form, Plate
- Backend: Hono on Cloudflare Workers (serves the SPA and the API from one Worker)
- Database: Cloudflare D1 + Drizzle ORM
- Tooling: pnpm, just, Oxlint, Knip, Prettier, Wrangler
Significant technical decisions are recorded as Architecture Decision Records.
- Node.js 22+
- pnpm
- just (optional — you can use the npm scripts directly)
- A Cloudflare account (only needed for deploying and
db:remote)
pnpm install
pnpm db:local
pnpm devOpen http://127.0.0.1:5173. Confirm the server is up with:
curl http://127.0.0.1:5173/api/health
# {"ok":true,"app":"Stashed"}Run just to list every recipe. The ones you'll use most:
| Command | What it does |
|---|---|
just dev |
Start the dev server (SPA + Worker) at 127.0.0.1:5173 |
just build |
Type-check + production build |
just check |
lint + format-check + typecheck + build (pre-commit gate) |
just knip |
Find unused code/dependencies in all and production code |
just deploy |
Build, then deploy to Cloudflare |
just db-generate |
Generate Drizzle migrations from the schema |
just db-local |
Apply migrations to the local D1 database |
just db-remote |
Apply migrations to the remote D1 database |
just screenshots |
Regenerate committed README/documentation screenshots |
just clean |
Remove build artifacts (dist, .wrangler, tsbuildinfo) |
Screenshot assets are committed under docs/assets/screenshots/. Install
Playwright's Chromium once, then regenerate all of them with:
pnpm exec playwright install chromium
pnpm screenshotsThe capture is read-only against the public production site and uses a fixed
desktop viewport, dark-primary/light-secondary themes, reduced motion, and UTC
locale. The two detail routes are explicit public records in
scripts/screenshots.mjs; the command fails rather than silently substituting
another record if either changes.
Because it captures public production content, the copy and public avatars can
legitimately change between regenerations.
To capture only specific assets, pass their stable names:
pnpm screenshots -- --only ideas,collection-detailSet STASHED_SCREENSHOT_BASE_URL to capture the same public routes from a
different deployment.
Knip runs in both comprehensive and production modes. Its project patterns in
knip.json mark the React and Worker trees as shipped code while keeping local
maintenance scripts in the comprehensive pass. Configuration hints fail the
command so new entry-point gaps are fixed rather than silently ignored.
Stashed deploys the React frontend, Hono API, and static assets together as a
single Cloudflare Worker. The Worker uses the stashed-db D1 database declared
in wrangler.jsonc.
Production releases are branch-gated:
- Pull requests and pushes to
mainorprodrun the CI workflow. - Only a push to
prodcan run the production deployment workflow. - The deployment applies pending D1 migrations before publishing the Worker.
- Production seed data is never applied automatically.
Some schema migrations also require a one-time data migration for existing production records. See the migration guide for release-specific instructions.
The current production origin is:
https://stashed.kiranrajeevkv.workers.dev
Create a GitHub environment named production, restrict it to the prod
branch, and add these environment secrets:
| Secret | Purpose |
|---|---|
CLOUDFLARE_ACCOUNT_ID |
Selects the Cloudflare account |
CLOUDFLARE_API_TOKEN |
Deploys Workers and applies D1 migrations |
Scope the API token to this Cloudflare account. It needs Workers Scripts: Write and D1: Edit. The GitHub App client ID, client secret, and Stashed session secret and TypeSafe API key are Worker secrets stored directly in Cloudflare; they do not need to be duplicated in GitHub.
The intended release flow is to work normally on main, then open a pull
request from main into the protected prod branch when the current state is
ready for production.
For the initial Worker deployment, set the Worker secrets without committing
the local .env file:
pnpm exec wrangler secret bulk .env
pnpm db:remote
pnpm run deployKeep the local callback (http://127.0.0.1:5173/api/auth/github/callback) and
add this exact production callback to the GitHub App:
https://stashed.kiranrajeevkv.workers.dev/api/auth/github/callback



