A self-hosted pastebin with syntax highlighting, paraphrase-protected pastes, gist collections, and full-text search.
- Create pastes with syntax highlighting for 17 languages
- Optional paraphrase protection — only people with the paraphrase can view the content
- Custom URLs — choose your own slug instead of a random ID
- Delete any paste using a secret server-side delete code
- View count — each paste tracks how many times it has been viewed
- Line highlighting — click any line to highlight it and update the URL hash (e.g.
/p/abc123#L42) - Gists — group multiple pastes into a single shareable collection (up to 20 pastes)
- Edit and delete gists (requires delete code)
- Admin page at
/admin— metrics dashboard (total views, most viewed paste, top languages, protected count, recent activity) plus paginated browse and delete for all pastes and gists (protected by delete code) - Full-text search across all pastes
- Raw paste view
- File upload support — drag and drop a file to populate the editor
- Dark and light theme
- PostHog analytics (server-side and client-side)
Client: React, TypeScript, Vite, Tailwind CSS, react-syntax-highlighter
Server: Node.js, Express, TypeScript, MongoDB
Infrastructure: Docker, Nginx, Caddy (TLS termination)
- Docker and Docker Compose
- A domain pointing to your server (for TLS via Caddy)
-
Clone the repository:
git clone https://github.com/shubhamxdd/pastebin cd pastebin -
Create a
.envfile in the project root:POSTHOG_API_KEY=your_posthog_api_key POSTHOG_HOST=https://us.i.posthog.com VITE_POSTHOG_API_KEY=your_posthog_api_key VITE_POSTHOG_HOST=https://us.i.posthog.com PASTE_DELETE_CODE=your_secret_delete_code
-
Update
Caddyfilewith your domain:your-domain.com { reverse_proxy client:80 } -
Build and start:
docker compose up --build -d
| Variable | Required | Description |
|---|---|---|
PASTE_DELETE_CODE |
Yes | Secret code required to delete or edit any paste or gist |
POSTHOG_API_KEY |
No | PostHog project API key for server-side analytics |
POSTHOG_HOST |
No | PostHog instance URL |
VITE_POSTHOG_API_KEY |
No | PostHog project API key for client-side analytics (baked in at build time) |
VITE_POSTHOG_HOST |
No | PostHog instance URL for client |
List pastes with optional full-text search and pagination.
Query params: q, page, limit (max 50, default 20)
Response 200:
{
"pastes": [
{
"id": "abc123",
"title": "My snippet",
"language": "typescript",
"created_at": "2024-07-01T10:00:00.000Z",
"protected": false
}
],
"total": 42,
"page": 1,
"limit": 20
}Create a paste.
Body:
{
"title": "My snippet",
"content": "console.log('hello')",
"language": "javascript",
"paraphrase": "optional-secret",
"customId": "optional-slug"
}Response 201:
{
"id": "abc123"
}Get a paste. If protected, content is omitted and protected is true. Each call increments the view count.
Response 200 (unprotected):
{
"id": "abc123",
"title": "My snippet",
"content": "console.log('hello')",
"language": "javascript",
"created_at": "2024-07-01T10:00:00.000Z",
"views": 42,
"protected": false
}Response 200 (protected):
{
"id": "abc123",
"title": "My snippet",
"language": "javascript",
"created_at": "2024-07-01T10:00:00.000Z",
"views": 42,
"protected": true
}Verify paraphrase and return the full content of a protected paste.
Body:
{
"paraphrase": "your-secret"
}Response 200:
{
"id": "abc123",
"title": "My snippet",
"content": "console.log('hello')",
"language": "javascript",
"created_at": "2024-07-01T10:00:00.000Z",
"views": 42,
"protected": true
}Response 401:
{
"error": "Incorrect paraphrase"
}Delete a paste. Requires the server-side delete code.
Body:
{
"deleteCode": "your-delete-code"
}Response 200:
{
"success": true
}Response 401:
{
"error": "Incorrect delete code"
}Returns the raw paste content as text/plain. For protected pastes, pass ?paraphrase=your-secret.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/collections |
List all gists with pagination |
POST |
/api/collections |
Create a gist |
GET |
/api/collections/:id |
Get a gist with all its pastes |
PATCH |
/api/collections/:id |
Update title and/or pastes (requires deleteCode) |
DELETE |
/api/collections/:id |
Delete a gist (requires deleteCode) |
List all gists with pagination.
Query params: page, limit (max 50, default 20)
Response 200:
{
"collections": [
{
"id": "xyz789",
"title": "My collection",
"paste_count": 3,
"created_at": "2024-07-01T10:00:00.000Z"
}
],
"total": 10,
"page": 1,
"limit": 20
}Create a gist from existing pastes. Maximum 20 pastes per gist.
Body:
{
"title": "My collection",
"paste_ids": ["abc123", "def456"]
}Response 201:
{
"id": "xyz789"
}Get a gist with all its pastes populated. Protected pastes within the collection omit content.
Response 200:
{
"id": "xyz789",
"title": "My collection",
"created_at": "2024-07-01T10:00:00.000Z",
"pastes": [
{
"id": "abc123",
"title": "My snippet",
"content": "console.log('hello')",
"language": "javascript",
"created_at": "2024-07-01T10:00:00.000Z",
"protected": false
}
]
}Update the title and/or paste list of a gist. Requires the delete code.
Body:
{
"deleteCode": "your-delete-code",
"title": "Updated title",
"paste_ids": ["abc123", "def456", "ghi789"]
}Response 200:
{
"success": true
}Response 401:
{
"error": "Incorrect delete code"
}Delete a gist. Requires the delete code. The individual pastes are not deleted.
Body:
{
"deleteCode": "your-delete-code"
}Response 200:
{
"success": true
}Returns aggregate metrics across all pastes and collections. Used by the admin dashboard.
Response 200:
{
"total_pastes": 120,
"total_collections": 15,
"total_views": 4821,
"protected_pastes": 8,
"pastes_last_7_days": 23,
"most_viewed": {
"id": "abc123",
"title": "My snippet",
"language": "typescript",
"views": 310
},
"top_languages": [
{ "language": "javascript", "count": 42 },
{ "language": "typescript", "count": 38 },
{ "language": "python", "count": 21 },
{ "language": "bash", "count": 10 },
{ "language": "json", "count": 9 }
]
}Response 200:
{
"status": "ok",
"uptime": 3600,
"mongodb": "connected",
"memory": {
"used_mb": 64,
"heap_used_mb": 32,
"heap_total_mb": 48
},
"node_version": "v20.14.0",
"stats": {
"pastes": 120,
"collections": 15
},
"timestamp": "2024-07-01T10:00:00.000Z"
}Server:
cd server
npm install
npm run devClient:
cd client
npm install
npm run devThe client dev server proxies /api requests to localhost:3000.