Cloudsteading
Pastebin Worker’s documented public instance showing the paste editor and settings; no paste was submitted.

Pastebin Worker

Share text pastes and downloadable files through Workers, KV and R2.

Pastebin Worker is a self-hosted Pastebin/WeTransfer alternative built on Cloudflare (KV, R2, Workers). Free tier eligible within limits. Inspect the source and license in the linked repository.

Source & license

Upstream license: MIT

License TL;DR

You can use it, change it, self-host it and sell it. Keep the original copyright and license notice with copies of the code. You don’t have to publish your changes. The authors don’t promise it will work.

Explain MIT in plain English →

Summary of the main license. Separate packages and assets can have different terms.

Inspect repository ↗Read this project’s actual license ↗

Repository owner

@SharzyL

See the upstream repository for the original creator and contributors.

Maintain this project? Maintainer verification →

Cloudflare hosting

Free tier eligible within limits

The documented Pastebin Worker deployment can use Cloudflare Free allowances for a small workload under the request, CPU and service-specific quotas below. This is conditional eligibility, not a measured zero-cost deployment; optional features, domains and external providers can add costs.

Hosting requirements
  • Workers Free allows 100,000 requests per day shared across the account and 10 ms CPU per invocation; measure CPU-heavy authentication, parsing and rendering before assuming it fits.
  • Keep KV below 100,000 reads/day, 1,000 writes, deletes and list operations/day each, and 1 GB; cache refreshes and backups consume writes.
  • Use R2 Standard storage, at most 10 GB-month, 1 million Class A operations and 10 million Class B operations/month; provision an eligible billing-enabled R2 account.
  • Use a small personal or team workload; domain registration and optional third-party providers are separate costs. Provision your own IDs, secrets and migrations.
Check current pricing ↗
Sources checked 01/10/2026

Repository snapshot: 0835cac. Hosting eligibility reflects the deployment documentation and listed assumptions.

  • pastebin ↗

    This is a pastebin running on Cloudflare workers. Try it on [shz.al](https://shz.al).

  • wetransfer ↗

    This is a pastebin running on Cloudflare workers. Try it on [shz.al](https://shz.al).

  • workers ↗

    name = "pb" compatibility_date = "2025-04-24" workers_dev = false main = "worker/index.ts" [[rules]] type = "Text" globs = [ "**/*.html", "**/*.md", "**/*.css" ] fallthrough = true [assets] directory = "dist/frontend" run_worker_first = true binding = "ASSETS" [triggers] # clean r2 garbage every day crons = ["0 0 * * *"] [observability] # enable to collect logs enabled = true #-----------------------------------

  • kv ↗

    fer to https://developers.cloudflare.com/workers/wrangler/configuration/#routes pattern = "shz.al" custom_domain = true [[kv_namespaces]] binding = "PB" # do not touch this id = "435f8959b9de485ea48751ba557d90f5" # id of your KV namespace [[r2_buckets]] binding = "R2" # do not touch this bucket_name = "pb-shz-al" # bucket name of your R2 bucket [vars] # must be consistent with your routes DEPLOY_URL = "https://shz.al" # url to repo, displayed in the index page REPO = "https://github.com/Sh

  • r2 ↗

    [[kv_namespaces]] binding = "PB" # do not touch this id = "435f8959b9de485ea48751ba557d90f5" # id of your KV namespace [[r2_buckets]] binding = "R2" # do not touch this bucket_name = "pb-shz-al" # bucket name of your R2 bucket [vars] # must be consistent with your routes DEPLOY_URL = "https://shz.al" # url to repo, displayed in the index page REPO = "https://github.com/SharzyL/pastebin-worker" # the page title displayed in index page INDEX_PAGE_TITLE = "Pastebin Worker" # the name displ

  • free-tier-eligible ↗

    name = "pb" compatibility_date = "2025-04-24" workers_dev = false main = "worker/index.ts" [[rules]] type = "Text" globs = [ "**/*.html", "**/*.md", "**/*.css" ] fallthrough = true [assets] directory = "dist/frontend" run_worker_first = true binding = "ASSETS" [triggers] # clean r2 garbage every day crons = ["0 0 * * *"] [observability] # enable to collect logs enabled = true #-----------------------------------

  • free-tier-eligible ↗

    fer to https://developers.cloudflare.com/workers/wrangler/configuration/#routes pattern = "shz.al" custom_domain = true [[kv_namespaces]] binding = "PB" # do not touch this id = "435f8959b9de485ea48751ba557d90f5" # id of your KV namespace [[r2_buckets]] binding = "R2" # do not touch this bucket_name = "pb-shz-al" # bucket name of your R2 bucket [vars] # must be consistent with your routes DEPLOY_URL = "https://shz.al" # url to repo, displayed in the index page REPO = "https://github.com/Sh

  • free-tier-eligible ↗

    [[kv_namespaces]] binding = "PB" # do not touch this id = "435f8959b9de485ea48751ba557d90f5" # id of your KV namespace [[r2_buckets]] binding = "R2" # do not touch this bucket_name = "pb-shz-al" # bucket name of your R2 bucket [vars] # must be consistent with your routes DEPLOY_URL = "https://shz.al" # url to repo, displayed in the index page REPO = "https://github.com/SharzyL/pastebin-worker" # the page title displayed in index page INDEX_PAGE_TITLE = "Pastebin Worker" # the name displ

  • free-tier-eligible ↗

    up>1, 2, 3, 4</sup> | Duration | CPU time | | --- | --- | --- | --- | | **Free** | 100,000 per day | No charge for duration | 10 milliseconds of CPU time per invocation | | **Standard** | 10 million included per month <br> +$0.30 per additional million | No charge or limit for duration | 30 million CPU milliseconds included per month<br> +$0.02 per additional million CPU milliseconds<br><br> Max of [5 minutes of CPU time](https://developers.cloudflare.com/workers/platform/limits/#account-plan-limits) per invocation (default: 30 seconds)<br> Max of 15 minutes of CPU time per [Cron Trigger](https://developers.cloudflare.com/workers/configuration/cron-triggers/) or [Queue Consumer](https://developers.cloudflare.co

  • free-tier-eligible ↗

    cing/). | | Free plan<sup>1</sup> | Paid plan | | --- | --- | --- | | Keys read | 100,000 / day | 10 million/month, + $0.50/million | | Keys written | 1,000 / day | 1 million/month, + $5.00/million | | Keys deleted | 1,000 / day | 1 million/month, + $5.00/million | | List requests | 1,000 / day | 1 million/month, + $5.00/million | | Stored data | 1 GB | 1 GB, + $0.50/ GB-month | <sup>1</sup> The Workers Free plan includes limited Workers KV usage. All limits reset daily at 00:00 UTC. If you exceed any one of these limits, further operations of that type will fail with an error. Note Workers KV pricing for read, write and delete operations is on a per-key basis. Bulk read operations are billed by the amount

  • free-tier-eligible ↗

    infrequent access storage) for 1.1 GB, you will be billed for 2 GB. ### Free tier You can use the following amount of storage and operations each month for free. | | Free | | --- | --- | | Storage | 10 GB-month / month | | Class A Operations | 1 million requests / month | | Class B Operations | 10 million requests / month | | Egress (data transfer to Internet) | Free <sup>[1](#user-content-fn-1)</sup> | Caution The free tier only applies to Standard storage, and does not apply to Infrequent Access storage. ### Storage usage Storage is billed using gigabyte-month (GB-month) as the billing metric. A GB-month is calculated by averaging the *peak* storage per day over a billing period (30 days). For examp

  • MIT ↗

    Copyright (c) 2021 Sharzy L <me@sharzy.in> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRIN

  • architecture ↗

    name = "pb" compatibility_date = "2025-04-24" workers_dev = false main = "worker/index.ts" [[rules]] type = "Text" globs = [ "**/*.html", "**/*.md", "**/*.css" ] fallthrough = true [assets] directory = "dist/frontend" run_worker_first = true binding = "ASSETS" [triggers] # clean r2 garbage every day crons = ["0 0 * * *"] [observability] # enable to collect logs enabled = true #-----------------------------------

  • architecture ↗

    fer to https://developers.cloudflare.com/workers/wrangler/configuration/#routes pattern = "shz.al" custom_domain = true [[kv_namespaces]] binding = "PB" # do not touch this id = "435f8959b9de485ea48751ba557d90f5" # id of your KV namespace [[r2_buckets]] binding = "R2" # do not touch this bucket_name = "pb-shz-al" # bucket name of your R2 bucket [vars] # must be consistent with your routes DEPLOY_URL = "https://shz.al" # url to repo, displayed in the index page REPO = "https://github.com/Sh

  • architecture ↗

    [[kv_namespaces]] binding = "PB" # do not touch this id = "435f8959b9de485ea48751ba557d90f5" # id of your KV namespace [[r2_buckets]] binding = "R2" # do not touch this bucket_name = "pb-shz-al" # bucket name of your R2 bucket [vars] # must be consistent with your routes DEPLOY_URL = "https://shz.al" # url to repo, displayed in the index page REPO = "https://github.com/SharzyL/pastebin-worker" # the page title displayed in index page INDEX_PAGE_TITLE = "Pastebin Worker" # the name displ

Documented public demo screenshot · SharzyL/pastebin-worker repository contributors; screenshot captured by Cloudsteading ↗. Depicts the upstream project. We have not deployed and tested a fresh installation here.

What it can replace

Compare the workflow you need. These mappings describe overlap; full feature parity requires a separate comparison.

Pastebin logoPastebin ↗

Publishing text pastes and sharing uploaded files by download URL; filesystem sync, email transfer delivery and managed large-transfer guarantees are excluded.

See supporting source ↗
WeTransfer logoWeTransfer ↗

Publishing text pastes and sharing uploaded files by download URL; filesystem sync, email transfer delivery and managed large-transfer guarantees are excluded.

See supporting source ↗
external SaaS target
varies
→ KV + R2 + Workers
external SaaS target
varies
→ KV + R2 + Workers

How it works

The shape of Pastebin Worker on Cloudflare, and how it stacks up against the rented tools it replaces.

Architecture

Diagram of deployment declarations at the reviewed commit. Each app has its own entrypoint; declared resources do not prove runtime calls. Follow file and line sources below.

View upstream source ↗
Public interface
Configured entry points1
pb
wrangler.toml
↓
App
pb
entry
Cloudflare Workers
Entrypoint: worker/index.tsConfigured cron (UTC): 0 0 * * *
↓

Configuration and workflow sources

Reviewed commit 0835cac4ab8f. Files were read as data; upstream applications and CI jobs were not executed.

Deployment configuration · 1 files
wrangler.toml ↗

Cloudflare Workers · compatibility 2025-04-24

pb · default

Entrypoint: worker/index.ts

Static assets: dist/frontend · Worker first: true

Cron triggers (UTC): 0 0 * * *

Configured route patterns: shz.al

  • PB → KV
  • R2 → R2
  • ASSETS → Static assets

Named environments are separate deployments. Bindings are shown only where declared. Configured routes are URL patterns, not verified application endpoints.

Runtime source · handlers, binding usage and workflow steps

Observed TypeScript/JavaScript declarations from Worker entrypoints and resolved relative imports. Calls and workflow steps may run conditionally; their listed order is not a proven end-to-end request flow. Router declarations may be mounted under a prefix or may not be registered. This shows code wiring, not a successful deployment or runtime test. Dynamic wiring, aliases and generated code may not resolve.

worker/index.ts ↗
  • L11 · fetch handler exported · calls handleRequest
  • L16 · scheduled handler exported · calls ctx.waitUntil, cleanExpiredInR2
  • L21 · handleRequest calls (conditional paths may differ): handleOptions, handleNormalRequest, response.headers.set, corsWrapResponse, console.error
  • L50 · handleNormalRequest calls (conditional paths may differ): handlePostOrPut, handleGet, handleDelete
worker/common.ts ↗
  • L3 · decode calls (conditional paths may differ): decode
  • L7 · btoa_utf8 calls (conditional paths may differ): btoa, String.fromCharCode, encode
  • L11 · atob_utf8 calls (conditional paths may differ): atob, decode, Uint8Array.from, value_latin1.charCodeAt
  • L18 · escapeHtml calls (conditional paths may differ): str.replace
  • L36 · dateToUnix calls (conditional paths may differ): Math.floor, date.getTime
  • L40 · genRandStr calls (conditional paths may differ): CHAR_GEN.charAt, Math.floor, Math.random
  • L60 · timingSafeEqual calls (conditional paths may differ): encoder.encode, crypto.subtle.timingSafeEqual
shared/parsers.ts ↗
  • L9 · parseSize calls (conditional paths may differ): sizeStr.trim, SIZE_REGEX.test, parseFloat
  • L24 · parseExpiration calls (conditional paths may differ): expirationStr.trim, EXPIRE_REGEX.test, parseFloat
  • L39 · parseExpirationReadable calls (conditional paths may differ): expirationStr.trim, EXPIRE_REGEX.test, parseFloat
  • L62 · parsePath calls (conditional paths may differ): pathname.slice, pathname.lastIndexOf, decodeURIComponent, filename.indexOf, filename.slice, pathname.indexOf
  • L114 · parseFilenameFromContentDisposition calls (conditional paths may differ): filenameStarRegex.exec, decodeURIComponent, filenameRegex.exec
worker/handlers/handleCors.ts ↗
  • L7 · handleOptions calls (conditional paths may differ): headers.get
  • L25 · corsWrapResponse calls (conditional paths may differ): response.headers.set
worker/handlers/handleWrite.ts ↗
  • L30 · multipartToMap calls (conditional paths may differ): parseMultipartRequest, parseSize, partsMap.set, decode, console.warn
  • L67 · handlePostOrPut calls (conditional paths may differ): verifyAuth, handleMPUCreate, handleMPUCreateUpdate, handleMPUResume, handleMPUAbort, url.pathname.startsWith, request.headers.get, contentType.includes, multipartToMap, parts.has, parts.get, JSON.parse, contentAsString, parseExpiration, verifyPassword, verifyName, JSON.stringify, parsePath, url.searchParams.get, handleMPUComplete
  • L146 · makeResponse calls (conditional paths may differ): JSON.stringify

Environment references: env.R2_MAX_ALLOWED · env.DEFAULT_EXPIRATION · env.MAX_EXPIRATION · env.DEPLOY_URL

worker/handlers/handleRead.ts ↗
  • L16 · decodeMaybeStream calls (conditional paths may differ): decode, getReader, content.pipeThrough, reader.read
  • L43 · lastModifiedHeader calls (conditional paths may differ): toUTCString
  • L48 · isCurlAgent calls (conditional paths may differ): request.headers.get, startsWith, ua.toLowerCase
  • L53 · handleStaticPages calls (conditional paths may differ): isCurlAgent, verifyAuth, getCurlIndexMarkdown, staticPageCacheHeader, path.endsWith, path.lastIndexOf, path.indexOf, renderIndexPage, console.error, getAssetPaths, escapeHtml, renderCssLinks, JSON.stringify, path.startsWith, env.ASSETS.fetch, mime.getType, resp.blob, url.pathname.startsWith, url.pathname.endsWith, url.pathname.slice
  • L175 · getPasteWithoutContent calls (conditional paths may differ): getPasteMetadata
  • L180 · handleGet calls (conditional paths may differ): handleStaticPages, parsePath, url.searchParams.has, getPaste, getPasteWithoutContent, disallowedMimes.includes, url.searchParams.get, mime.getType, sanitize, request.headers.get, Date.parse, lastModifiedHeader, decodeMaybeStream, isLegalUrl, Response.redirect, makeMarkdown, pasteCacheHeader, metaResponseFromMetadata, JSON.stringify, renderDisplayPage

Environment references: env.CACHE_STATIC_PAGE_AGE · env.CACHE_PASTE_AGE · env.INDEX_PAGE_TITLE · env.ASSETS · env.DISALLOWED_MIME_FOR_PASTE

worker/handlers/handleDelete.ts ↗
  • L5 · handleDelete calls (conditional paths may differ): parsePath, getPasteMetadata, timingSafeEqual, deletePaste
worker/storage/storage.ts ↗
  • L42 · metaResponseFromMetadata calls (conditional paths may differ): toISOString
  • L79 · updateAccessCounter calls (conditional paths may differ): Math.random, env.PB.put, message.includes
  • L97 · getPaste calls (conditional paths may differ): env.PB.getWithMetadata, workerAssert, migratePasteMetadata, getTime, ctx.waitUntil, deletePaste, updateAccessCounter, env.R2.get
  • L136 · getPasteMetadata calls (conditional paths may differ): env.PB.getWithMetadata, getTime, migratePasteMetadata
  • L164 · updatePaste calls (conditional paths may differ): dateToUnix, Math.max, parseSize, env.R2.put, String, env.PB.put
  • L210 · createPaste calls (conditional paths may differ): dateToUnix, Math.max, parseSize, env.R2.put, String, env.PB.put
  • L251 · pasteNameAvailable calls (conditional paths may differ): env.PB.getWithMetadata, getTime
  • L262 · deletePaste calls (conditional paths may differ): env.R2.delete, env.PB.delete
  • L269 · cleanExpiredInR2 calls (conditional paths may differ): env.R2.list, Number, toDelete.push, needKvLookup.push, Promise.all, needKvLookup.map, getPasteMetadata, toDelete.slice, env.R2.delete, console.log

Environment references: env.PB · env.R2 · env.R2_THRESHOLD

worker/pages/auth.ts ↗
  • L5 · encodeBasicAuth calls (conditional paths may differ): btoa_utf8
  • L11 · decodeBasicAuth calls (conditional paths may differ): encodedString.split, atob_utf8, credentials.split
  • L28 · verifyAuth calls (conditional paths may differ): Object.entries, request.headers.has, decodeBasicAuth, request.headers.get, passwdMap.has, compareSync, passwdMap.get

Environment references: env.BASIC_AUTH

shared/verify.ts ↗
  • L6 · isLegalUrl calls (conditional paths may differ): URL.canParse
  • L10 · verifyPassword calls (conditional paths may differ): password.includes
  • L23 · verifyName calls (conditional paths may differ): NAME_REGEX.test
  • L32 · verifyExpiration calls (conditional paths may differ): parseExpiration, parseExpirationReadable
worker/handlers/handleMPU.ts ↗
  • L7 · mpuExpireMetadata calls (conditional paths may differ): url.searchParams.get, parseExpiration, Math.min, dateToUnix, String
  • L18 · handleMPUCreate calls (conditional paths may differ): url.searchParams.get, NAME_REGEX.test, pasteNameAvailable, genRandStr, env.R2.createMultipartUpload, mpuExpireMetadata, JSON.stringify
  • L49 · handleMPUCreateUpdate calls (conditional paths may differ): url.searchParams.get, getPasteMetadata, timingSafeEqual, env.R2.createMultipartUpload, mpuExpireMetadata, JSON.stringify
  • L78 · handleMPUResume calls (conditional paths may differ): url.searchParams.get, parseInt, env.R2.resumeMultipartUpload, multipartUpload.uploadPart, console.warn, String, JSON.stringify
  • L111 · handleMPUAbort calls (conditional paths may differ): url.searchParams.get, abort, env.R2.resumeMultipartUpload, console.warn, String
  • L130 · handleMPUComplete calls (conditional paths may differ): url.searchParams.get, env.R2.resumeMultipartUpload, multipartUpload.complete, console.warn, String, parseSize, env.R2.delete

Environment references: env.MAX_EXPIRATION · env.R2 · env.R2_MAX_ALLOWED

worker/pages/docs.ts ↗
  • L9 · renderTemplate calls (conditional paths may differ): replaceAll, template.replaceAll
  • L20 · getDocMarkdown calls (conditional paths may differ): renderTemplate
  • L34 · getCurlIndexMarkdown calls (conditional paths may differ): renderTemplate
  • L38 · renderDocAsHtml calls (conditional paths may differ): makeMarkdown, md.replace

Environment references: env.DEPLOY_URL · env.REPO · env.TOS_MAINTAINER · env.TOS_MAIL · env.DEFAULT_EXPIRATION · env.MAX_EXPIRATION · env.R2_MAX_ALLOWED

worker/pages/markdown.ts ↗
  • L95 · forceNoopener calls (conditional paths may differ): html.replace, test
  • L102 · sanitizeHtml calls (conditional paths may differ): forceNoopener, filterXSS
  • L126 · tokenText calls (conditional paths may differ): join, list.items.map, Array.isArray, token.tokens.map
  • L138 · firstContentToken calls (conditional paths may differ): tokens.find
  • L142 · extractMetadata calls (conditional paths may differ): firstContentToken, escapeHtml, tokenText, tokens.slice, tokens.indexOf, slice
  • L155 · renderToc calls (conditional paths may differ): toc.filter, openDepths.pop, openDepths.push, escapeHtml
  • L305 · makeMarkdown calls (conditional paths may differ): marked.use, parser.parseInline, join, tokens.map, slugger.slug, metadata.toc.push, escapeHtml, text.replace, body.match, repeat, exec, marked.lexer, extractMetadata, sanitizeHtml, marked.parser, renderToc, getAssetPaths, renderCssLinks
worker/ssrUtils.ts ↗
  • L14 · getAssetPaths calls (conditional paths may differ): entryKey.replace
  • L18 · renderCssLinks calls (conditional paths may differ): join, cssPaths.map
Build and deployment pipeline · 2 GitHub Actions workflows

Repository CI declarations, separate from runtime request processing. Job dependencies and conditions are shown as written; long commands are shortened with an ellipsis; a workflow file does not prove a recent successful run.

Test and Deploy · .github/workflows/deploy.yml ↗

Triggers: push

deploy · no job dependencies declared

  1. actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
  2. Install Nodeactions/setup-node@820762786026740c76f36085b0efc47a31fe5020
  3. Install pnpmpnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271
  4. Setuppnpm install --frozen-lockfile
  5. Build Frontendpnpm build:frontend
  6. Testpnpm fmt pnpm lint pnpm typecheck pnpm test --testTimeout 15000
  7. Deploypnpm wrangler deploy
PR Tests · .github/workflows/pr.yml ↗

Triggers: pull_request

coverage-goshujin · no job dependencies declared

  1. actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
  2. Install Nodeactions/setup-node@820762786026740c76f36085b0efc47a31fe5020
  3. Install pnpmpnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271
  4. Run Test with Coveragepnpm install --frozen-lockfile pnpm build:frontend pnpm coverage --testTimeout 15000
  5. Upload Coverageactions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a

test · no job dependencies declared

  1. actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
  2. Install Nodeactions/setup-node@820762786026740c76f36085b0efc47a31fe5020
  3. Install pnpmpnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271
  4. Install Depspnpm install --frozen-lockfile
  5. Build Frontendpnpm build:frontend
  6. Lintpnpm prettier -c . pnpm lint
  7. Type Checkpnpm typecheck
  8. Run Test with Coveragepnpm coverage --testTimeout 15000
  9. Upload Coverageactions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a

report-coverage · after test

  1. actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
  2. Download HEAD coverage artifactsactions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
  3. Download goshujin coverage artifactsactions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
  4. Report Coveragedavelosert/vitest-coverage-report-action@8b157684c6a6b259b97d45e72b44242865c0f6a5
package.json ↗
  • deploy: wrangler deploy
  • build:frontend: vite build frontend --outDir ../dist/frontend --emptyOutDir
  • build:frontend:dev: vite build frontend --mode development --outDir ../dist/frontend --emptyOutDir
  • build: wrangler deploy --dry-run --outdir=dist

Full upstream document by @SharzyL · README.md · snapshot 0835cac

Pastebin Worker

This is a pastebin running on Cloudflare workers. Try it on shz.al.

Philosophy: effortless deployment, friendly CLI usage, rich functionality.

Features:

  1. Share your paste with as short as 4 characters, or even customized URL.
  2. Syntax highlighting powered by highlight.js.
  3. Client-side encryption.
  4. Share markdown file with rendered HTML.
  5. URL shortener.
  6. Smart and tweakable handling for Content-Type and Content-Disposition.

Usage

  1. You can post, update, delete your paste directly on the website (such as shz.al).

  2. It also provides a convenient HTTP API to use. See API reference for details. You can easily call API via command line (using curl or similar tools). Note that a single request body is capped at 100 MB by Cloudflare (the platform returns HTTP 413 for larger bodies before the worker runs) — for larger files, use the website or the pb CLI below, which transparently chunk the upload.

  3. pb is a Python script (requires Python 3.9+ with the requests package) to make it easier to use on command line; it automatically switches to multipart upload above 5 MiB and shows a progress bar.

  4. doc/skill.md is a concise, AI-agent-oriented packaging of the API. Make it available to your coding agent so it can upload, fetch, and manage pastes via this service.

Deploy

You are free to deploy the pastebin on your own domain if you host your domain on Cloudflare.

  1. Install node and pnpm.

  2. Clone the repository and enter the directory.

  3. Create a KV namespace and R2 bucket, fill the KV namespace ID and R2 bucket name in wrangler.toml.

$ pnpm wrangler kv namespace create PB
$ pnpm wrangler r2 bucket create <name>
  1. Modify entries in wrangler.toml. Its comments will tell you how.

  2. Login to Cloudflare and deploy with the following steps:

$ pnpm install
$ pnpm wrangler login
$ pnpm build:frontend
$ pnpm deploy
  1. Enjoy!

Cost

The service runs on Cloudflare Workers, Workers KV, and R2. Each has a free tier; beyond it you pay only for what you use. Figures below are accurate as of writing — prices change, so confirm against the official pricing pages before relying on them:

  • Workers — request routing and execution. Egress is free.
    • Free plan: 100 k requests/day, 10 ms CPU per invocation.
    • Paid plan ($5/mo base): 10 M requests/month + 30 M ms CPU/month included, then $0.30 per additional M requests and $0.02 per additional M CPU-ms. Also unlocks the higher KV limits below (KV has no separate paid plan).
  • Workers KV — small pastes and per-paste metadata.
    • Free plan (daily, resets 00:00 UTC): 100 k reads, 1 k writes, 1 k deletes, 1 k list ops, 1 GB storage.
    • Paid plan (monthly + overage): 10 M reads ($0.50/M extra), 1 M writes ($5/M), 1 M deletes ($5/M), 1 M list ops ($5/M), 1 GB storage ($0.50/GB-month extra).
  • R2 — paste content above R2_THRESHOLD. Egress is free. Class A op = upload (PutObject); Class B op = fetch (GetObject). Cloudflare rounds storage up to the next GB-month.
    • Free: 10 GB-month storage, 1 M Class A ops/month, 10 M Class B ops/month.
    • Standard paid: $0.015/GB-month storage, $4.50/M Class A ops, $0.36/M Class B ops.
  • Workers Logs — optional, off unless enabled in wrangler.toml.
    • Free: 200 k events/day, 3-day retention.
    • Paid: 20 M events/month included + $0.60 per additional million, 7-day retention.

Costs scale primarily with: large file traffic (R2 ops + storage), high-volume reads (Workers requests + KV reads), and verbose logging (Workers Logs events).

Bottom line — what each tier comfortably handles:

  • Free tier — a personal pastebin. Binding limits are KV writes (1 k uploads/day) and KV/Workers reads (~100 k fetches/day), with 1 GB small-paste storage and 10 GB large-paste storage on R2. Plenty for individual or small-team use.
  • $5/month Paid — a small public or community service. Roughly ~33 k uploads/day and ~333 k fetches/day stay within the included monthly KV allotment; Workers requests included to ~10 M/month (~333 k/day). R2 storage and ops come out of R2's own free tier first, then a few cents per GB-month and per million ops — adding only a few dollars even at moderate traffic.

[!NOTE] Small pastes go to KV (not R2) to keep garbage collection cheap. KV honors per-key expiration natively, so expired pastes vanish on their own. R2 has no built-in expiration, so cleaning up expired objects would require periodically listing and scanning every object in the bucket — costly in Class A/B ops as the bucket grows.

Auth

If you want a private deployment (only you can upload paste, but everyone can read the paste), add the following entry to your wrangler.toml.

[vars.BASIC_AUTH]
user1 = "$2b$08$i/yH1TSIGWUNQVsxPrcVUeR0hsGioFNf3.OeHdYzxwjzLH/hzoY.i"
user2 = "$2b$08$KeVnmXoMuRjNHKQjDHppEeXAf5lTLv9HMJCTlKW5uvRcEG5LOdBpO"

Passwords here are hashed by bcrypt2 algorithm. You can generate the hashed password by running ./scripts/bcrypt.js.

Now every access to POST request, and every access to static pages, requires an HTTP basic auth with the user-password pair listed above. For example:

$ curl example-pb.com
HTTP basic auth is required

$ curl -Fc=@/path/to/file example-pb.com
HTTP basic auth is required

$ curl -u admin1:wrong-passwd -Fc=@/path/to/file example-pb.com
Error 401: incorrect passwd for basic auth

$ curl -u admin1:this-is-passwd-1 -Fc=@/path/to/file example-pb.com
{
  "url": "https://example-pb.com/YCDX",
  "admin": "https://example-pb.com/YCDX:Sij23HwbMjeZwKznY3K5trG8",
  "isPrivate": false
}

Administration

Delete a paste:

$ pnpm delete-paste <name-of-paste>

List pastes:

$ pnpm -s wrangler kv key list --binding PB > kv_list.json

Development

Note that the frontend and worker code are built separatedly. To start a Vite development server of the frontend,

$ pnpm dev:frontend

To develop the backend worker, we must build a develop version of frontend,

$ pnpm build:frontend:dev

Then starts a local worker,

$ pnpm dev

The difference between build:frontend:dev and build:frontend is that the former will points the API endpoint to your deployment URL, while the later points to http://localhost:8787, the address of a local worker.

Run tests:

$ pnpm test

Run tests with coverage report:

$ pnpm coverage

Remember to run eslint checks and prettier before commiting your code.

$ pnpm fmt
$ pnpm lint
$ pnpm typecheck

Frequently asked about Pastebin Worker

What is Pastebin Worker?+

Pastebin Worker is a self-hosted Pastebin/WeTransfer alternative built on the Cloudflare developer platform. Share text pastes and downloadable files through Workers, KV and R2.

What does Pastebin Worker replace?+

Pastebin Worker is listed as an alternative to Pastebin, WeTransfer. Compare the features and tradeoffs before migrating.

What Cloudflare primitives does Pastebin Worker use?+

Pastebin Worker is built on KV, R2, Workers.

How much does Pastebin Worker cost to run?+

The documented Pastebin Worker deployment can use Cloudflare Free allowances for a small workload under the request, CPU and service-specific quotas below. This is conditional eligibility, not a measured zero-cost deployment; optional features, domains and external providers can add costs. Workers Free allows 100,000 requests per day shared across the account and 10 ms CPU per invocation; measure CPU-heavy authentication, parsing and rendering before assuming it fits. Keep KV below 100,000 reads/day, 1,000 writes, deletes and list operations/day each, and 1 GB; cache refreshes and backups consume writes. Use R2 Standard storage, at most 10 GB-month, 1 million Class A operations and 10 million Class B operations/month; provision an eligible billing-enabled R2 account. Use a small personal or team workload; domain registration and optional third-party providers are separate costs. Provision your own IDs, secrets and migrations. Check current Cloudflare pricing before deploying.

Is Pastebin Worker open source?+

The upstream repository declares the MIT license. Read its terms at https://raw.githubusercontent.com/SharzyL/pastebin-worker/0835cac4ab8f974035d31845f5c2b93b0c85b5c6/LICENSE. Source code and contributor credit are available at https://github.com/SharzyL/pastebin-worker.

Discussion · 0

sign in to comment →
No comments yet — be the first.