Share code and text instantly. No account required. Privacy-first.
- 150+ Languages - Syntax highlighting powered by Shiki (same engine as VS Code)
- Markdown Rendering - Full GitHub Flavored Markdown with code blocks
- Client-Side Encryption - AES-256-GCM encryption. Your password never leaves the browser
- Burn After Reading - Self-destructing pastes after first view
- Expiration Options - Never, 10 min, 1 hour, 1 day, 1 week, 1 month
- Zero Authentication - No account, no email, no friction
- Dark Mode - Pure black OLED-optimized dark theme
- Bot Protection - Honeypot fields, time-based detection, JS challenges
- Rate Limiting - In-memory sliding window rate limiter
- Admin Analytics - Optional
/admindashboard (paste/view totals, language mix, size distribution, activity over time). Disabled unlessADMIN_TOKENis set - Open Source - MIT licensed. Self-host it, fork it, contribute to it
| Layer | Technology |
|---|---|
| Framework | Next.js 16.3 (App Router, Server Actions, Cache Components) |
| Language | TypeScript 5 (strict mode) |
| Styling | Tailwind CSS 4 |
| Database | SQLite via Drizzle ORM + better-sqlite3 |
| Object Storage | Cloudflare R2 (S3-compatible) |
| Syntax Highlighting | Shiki (150+ languages) |
| Markdown | React Markdown + remark-gfm |
| Encryption | Web Crypto API (AES-256-GCM + PBKDF2) |
| Rate Limiting | In-memory sliding window (single-instance by design) |
| Charts | Recharts + Evil Charts (admin analytics) |
| Validation | Zod |
| Deployment | Railway — one service + a volume |
bin-21/
app/ # Next.js App Router pages
[id]/ # View paste page + raw endpoint
admin/ # Analytics dashboard (env-gated)
layout.tsx # Root layout with theme provider
page.tsx # Home / create paste
server/
actions/ # Server Actions (entry points, Zod validation)
services/ # Core business logic (DB, R2, stats)
lib/
db/ # Drizzle schema, SQLite client, migrations
shiki.ts # Syntax highlighter (bounded language cache)
languages.ts # 150+ language definitions
rate-limit.ts # In-memory rate limiter
janitor.ts # Reclaims expired pastes
admin-auth.ts # Admin token verification
bot-detection.ts # Bot detection utilities
components/
ui/ # Vendored chart primitives
admin/ # Analytics dashboard components
types/ # Shared TypeScript types
instrumentation.ts # Server startup hook (starts the janitor)
proxy.ts # Next.js 16 proxy (rate limiting, admin gate)
Data flow: Client form -> Server Action (validates with Zod) -> Service (uploads content to R2, saves metadata to SQLite) -> Returns paste ID -> Redirect to view page.
Paste content is stored in Cloudflare R2, never in the database. Encrypted pastes use client-side AES-256-GCM - the password never reaches the server.
Single-instance by design. The SQLite file lives on a mounted volume, and Railway does not allow replicas on a service with a volume. That is what makes in-memory rate limiting and the in-process janitor correct. The trade-offs: no horizontal scaling, and brief downtime on redeploy.
- Node.js 20+
- Cloudflare R2 bucket
No database server to run — SQLite is a file, created automatically on first boot.
git clone https://github.com/t21dev/bin-21.git
cd bin-21
npm install
cp .env.example .envEdit .env with your credentials:
# Optional locally — defaults to ./.data/bin21.db
DATABASE_PATH=./.data/bin21.db
R2_ACCOUNT_ID=your-account-id
R2_ACCESS_KEY_ID=your-access-key
R2_SECRET_ACCESS_KEY=your-secret-key
R2_BUCKET_NAME=bin21-pastes
NEXT_PUBLIC_APP_URL=http://localhost:3000Start the dev server — migrations run automatically on boot:
npm run devOpen http://localhost:3000.
Leave
JANITOR_ENABLEDunset locally. It deletes expired pastes and their R2 objects, so a dev machine pointed at a production bucket would reclaim live storage.
npm run dev # Start dev server
npm run build # Production build
npm start # Start production server
npm run lint # ESLint
npm run db:push # Push schema to database
npm run db:generate # Generate Drizzle migrations
npm run db:studio # Open Drizzle StudioSelf-hosting? See the full Self-Hosting Guide for Docker Compose, MinIO, reverse proxy, and more.
- Create a new project on Railway
- Connect your GitHub repo
- Attach a volume to the service, mounted at
/data - Set environment variables from
.env.example— in particularDATABASE_PATH=/data/bin21.dbandJANITOR_ENABLED=true - Enable automated volume backups. The volume is now the only copy of your paste metadata; there is no managed database to fall back on
- Deploy
No database or cache service is needed — one service plus a volume.
FROM node:20-alpine AS base
WORKDIR /app
FROM base AS deps
COPY package*.json ./
RUN npm ci
FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM base AS runner
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]- Client-side encryption: Password-protected pastes are encrypted in your browser using AES-256-GCM with PBKDF2 key derivation (100,000 iterations). The plaintext password never reaches the server.
- No tracking: No analytics, no cookies (beyond theme preference), no user accounts.
- IP hashing: IPs are salted and hashed for rate limiting. Raw IPs are never stored.
- Burn after reading: Paste is permanently deleted from both database and R2 after first view.
- Expiration: Expired pastes are automatically cleaned up.
| Action | Limit | Window |
|---|---|---|
| Create paste | 10 requests | 1 minute |
| View paste | 60 requests | 1 minute |
| Failed password | 5 attempts | 5 minutes |
Contributions are welcome. Please open an issue first to discuss what you'd like to change.
- Fork the repo
- Create your branch (
git checkout -b feature/my-feature) - Commit your changes
- Push to the branch
- Open a Pull Request
MIT - do whatever you want with it.
Built by T21 Dev