placeholders.dev
Generate configurable placeholder-image URLs with a small Cloudflare Worker.
placeholders.dev is a self-hosted Placehold.co alternative built on Cloudflare (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
See the upstream repository for the original creator and contributors.
Maintain this project? Maintainer verification →Cloudflare hosting
Free tier eligible within limits
The reviewed Cloudflare deployment is eligible for Free-plan allowances for the stated small workload and feature scope. Usage limits, CPU, required account setup and separate services apply.
Hosting requirements
- Use the default workers.dev deployment, which has no storage/backend service beyond static assets.
- Keep dynamic requests under the account-wide 100,000/day allowance and per-generation CPU under 10 ms; cap large dimensions and lengthy text.
- Upstream production analytics/logpush and custom domain settings are separate environment-specific choices.
- Workers Free dynamic requests are shared across this account (100,000/day), with 10 ms CPU per invocation; workload fit is conditional and has not been measured.
Sources checked 01/10/2026
Repository snapshot: 3197644. Hosting eligibility reflects the deployment documentation and listed assumptions.
- placehold-co ↗
re.com/?url=https://github.com/Cherry/placeholders.dev) ## Info placeholders.dev generates custom placeholder images on the fly, such as the examples above. All of these images are generated on Cloudflare's Edge, at 330+ locations, ensuring the best possible performance for all of your users. All images are cached for lengthy periods of time. ## Technology This project makes use of Cloudflare Workers, and Cloudflare Workers Static Assets. It also implements a HTMLRewriter to update the total Cloudflare PoPs in multiple locations. ### Example URLs: - Basic ur
- workers ↗
true send_metrics = false compatibility_date = "2023-12-09" compatibility_flags = ["nodejs_compat"] main = "./src/index.ts" [assets] directory = "./public" binding = "ASSETS" html_handling = "drop-trailing-slash" not_found_handling = "404-page" run_worker_first = true [env.staging] name = "placeholders-staging" workers_dev = true [env.production] name = "placeholders" workers_dev = false routes = [ {pattern = "placeholders.dev/*", zone_id = "51bfc6eeecc90565bd116ee3ebb5e3a8"}, {pattern = "images.placeholders.dev/*", zone_id = "51bfc6eeecc90565bd116ee3ebb5e3a8
- free-tier-eligible ↗
name = "placeholders-dev" workers_dev = true send_metrics = false compatibility_date = "2023-12-09" compatibility_flags = ["nodejs_compat"] main = "./src/index.ts" [assets] directory = "./public" binding = "ASSETS" html_handling = "drop-trailing-slash" not_found_handling = "404-page" run_worker_first = true [env.staging] name = "placeholders-staging" workers_dev = true [env.production] name = "placeholders" workers_dev = false routes = [ {pattern = "placeholders
- free-tier-eligible ↗
ount Manager. | | Requests<sup>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 second
- MIT ↗
MIT License Copyright (c) 2020 James Ross 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
- architecture ↗
true send_metrics = false compatibility_date = "2023-12-09" compatibility_flags = ["nodejs_compat"] main = "./src/index.ts" [assets] directory = "./public" binding = "ASSETS" html_handling = "drop-trailing-slash" not_found_handling = "404-page" run_worker_first = true [env.staging] name = "placeholders-staging" workers_dev = true [env.production] name = "placeholders" workers_dev = false routes = [ {pattern = "placeholders.dev/*", zone_id = "51bfc6eeecc90565bd116ee3ebb5e3a8"}, {pattern = "images.placeholders.dev/*", zone_id = "51bfc6eeecc90565bd116ee3ebb5e3a8
What it can replace
Compare the workflow you need. These mappings describe overlap; full feature parity requires a separate comparison.
Editorial workflow alternative: URL-driven placeholder images for website layouts; no image-upload, stock-photo or design-editor parity.
See supporting source ↗How it works
The shape of placeholders.dev 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 ↗Configuration and workflow sources
Reviewed commit 31976442d308. Files were read as data; upstream applications and CI jobs were not executed.
Deployment configuration · 1 files
Cloudflare Workers · compatibility 2023-12-09
placeholders-dev · default
Entrypoint: ./src/index.ts
Static assets: ./public · 404-page · Worker first: true
ASSETS→ Static assets
placeholders-staging · env.staging
Inherited from default: main, compatibility_date, compatibility_flags
Entrypoint: ./src/index.ts
No resource bindings declared in this scope.
placeholders · env.production
Inherited from default: main, compatibility_date, compatibility_flags
Entrypoint: ./src/index.ts
Configured route patterns: placeholders.dev/* · images.placeholders.dev/*
PLACEHOLDERS_ANALYTICS→ Analytics Engine
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.
- L144 · fetch handler exported · calls handleEvent, console.error
- L13 · handleEvent calls (conditional paths may differ): url.searchParams.sort, url.pathname.startsWith, cache.match, newHdrs.set, writeDataPoint, url.pathname.replace, baseURL.replace, size.split, sanitizeNumber, processOption, url.searchParams.get, simpleSvgPlaceholder, response.headers.set, ctx.waitUntil, cache.put, response.clone, isStaticFile, env.ASSETS.fetch, probableError.toString, asset.headers.set
Environment references: env.PLACEHOLDERS_ANALYTICS · env.ASSETS
- L5 · writeDataPoint calls (conditional paths may differ): request.headers.get, console.error
- L7 · sanitizeNumber calls (conditional paths may differ): String, test, str.trim, Number.isNaN, Number
- L16 · sanitizeString calls (conditional paths may differ): sanitizeHtml, value.replaceAll
- L22 · sanitizeColor calls (conditional paths may differ): sanitizeString, validateColor
- L31 · sanitizeStringForCss calls (conditional paths may differ): sanitizeString, value.replaceAll
- L37 · sanitizeBoolean calls (conditional paths may differ): String, str.toLowerCase
- L69 · processOption calls (conditional paths may differ): sanitizer
- L25 · simpleSvgPlaceholder calls (conditional paths may differ): Math.floor, Math.min, escapeXml, replaceAll, str.replaceAll, encodeURIComponent
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.
Triggers: push, pull_request, schedule
CodeQL-Build · no job dependencies declared
- Checkout repository
actions/checkout@v2 - Shell command
git checkout HEAD^2Condition: ${{ github.event_name == 'pull_request' }} - Initialize CodeQL
github/codeql-action/init@v2 - Autobuild
github/codeql-action/autobuild@v2 - Perform CodeQL Analysis
github/codeql-action/analyze@v2
Triggers: push, pull_request, repository_dispatch
test · no job dependencies declared
Condition: github.event_name == 'pull_request' || github.event_name == 'repository_dispatch' || (!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]'))
- actions/checkout@v4
actions/checkout@v4 - actions/setup-node@v4
actions/setup-node@v4 - Shell command
npm ci - Shell command
npm run lint - Shell command
npm run check-types - Shell command
npm run test
deploy · after test
Condition: github.ref == 'refs/heads/main' && (github.event_name == 'repository_dispatch' || (github.event_name == 'schedule' || github.event_name == 'push') && (!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')))
- actions/checkout@v4
actions/checkout@v4 - actions/setup-node@v4
actions/setup-node@v4 - Shell command
npm ci - Publish
cloudflare/wrangler-action@v3.14.1Wrangler command: deployCondition: github.event_name == 'repository_dispatch' || github.event_name == 'push' && github.repository_owner != 'Cherry' - Publish (production)
cloudflare/wrangler-action@v3.14.1Wrangler command: deploy --env=productionCondition: (github.event_name == 'repository_dispatch' || github.event_name == 'push') && github.repository_owner == 'Cherry'
publish: wrangler deploy --verbosepublish:prod: wrangler deploy --env production --verbosepublish:staging: wrangler deploy --env staging --verbose
Repository README
View original on GitHub ↗Full upstream document by @Cherry · README.md · snapshot 3197644
placeholders.dev
Generate super-fast placeholder images in 330+ edge locations, powered by Cloudflare Workers
Info
placeholders.dev generates custom placeholder images on the fly, such as the examples above. All of these images are generated on Cloudflare's Edge, at 330+ locations, ensuring the best possible performance for all of your users. All images are cached for lengthy periods of time.
Technology
This project makes use of Cloudflare Workers, and Cloudflare Workers Static Assets. It also implements a HTMLRewriter to update the total Cloudflare PoPs in multiple locations.
Example URLs:
- Basic url path:
https://images.placeholders.dev/350 - Full url path:
https://images.placeholders.dev/350x150 - Full query params:
https://images.placeholders.dev/?width=1055&height=100&text=Made%20with%20placeholders.dev&bgColor=%23f7f6f6&textColor=%236d6e71
Available API Options
width- Width of generated image. Defaults to
300.
- Width of generated image. Defaults to
height- Height of generated image. Defaults to 150.
text- Text to display on generated image. Defaults to the image dimensions.
fontFamily- Font to use for the text. Defaults to
sans-serif.
- Font to use for the text. Defaults to
fontWeight- Font weight to use for the text. Defaults to
bold.
- Font weight to use for the text. Defaults to
fontSize- Font size to use for the text. Defaults to 20% of the shortest image dimension, rounded down.
dy- Adjustment applied to the dy attribute of the text element to appear vertically centered. Defaults to 35% of the font size.
bgColor- Background color of the image. Defaults to
#ddd
- Background color of the image. Defaults to
textColor- Color of the text. For transparency, use an rgba or hsla value. Defaults to
rgba(0,0,0,0.5)
- Color of the text. For transparency, use an rgba or hsla value. Defaults to
textWrap- Wrap text to fit within the image (to best ability). Will not alter font size, so especially long strings may still appear outside of the image. Defaults to
false.
- Wrap text to fit within the image (to best ability). Will not alter font size, so especially long strings may still appear outside of the image. Defaults to
WebMCP
While placeholders.dev is open, it registers tools with the visitor's AI agent via the draft WebMCP navigator.modelContext API, so an agent can build placeholder URLs from a plain-language request without any setup. Tools live in public/webmcp.js and are pure URL work, so no requests leave the browser.
generate_placeholder_image_url- Builds a validated image URL from any of the API options above, and returns HTML and Markdown snippets alongside it. Values the image API would silently drop, such as an unparseable color or a
fontWeightkeyword, are rejected with an explanation instead. Also renders the result on the page so the visitor can see what the agent produced.
- Builds a validated image URL from any of the API options above, and returns HTML and Markdown snippets alongside it. Values the image API would silently drop, such as an unparseable color or a
explain_placeholder_image_url- Breaks an existing placeholders.dev URL into the options it uses, resolves the defaults the image API would apply, and flags query parameters it ignores.
Dev
npm run start:dev(this will usewrangler devto locally start the Cloudflare Worker for testing)
or
npm run publish(push toplaceholders-devworkers.dev)
or
npm run publish:staging(push toplaceholders-stagingworkers.dev)
Production
npm run publish:prod(publishes to placeholders.dev via Workers)
Frequently asked about placeholders.dev
What is placeholders.dev?+
placeholders.dev is a self-hosted Placehold.co alternative built on the Cloudflare developer platform. Generate configurable placeholder-image URLs with a small Cloudflare Worker.
What does placeholders.dev replace?+
placeholders.dev is listed as an alternative to Placehold.co. Compare the features and tradeoffs before migrating.
What Cloudflare primitives does placeholders.dev use?+
placeholders.dev is built on Workers.
How much does placeholders.dev cost to run?+
The reviewed Cloudflare deployment is eligible for Free-plan allowances for the stated small workload and feature scope. Usage limits, CPU, required account setup and separate services apply. Use the default workers.dev deployment, which has no storage/backend service beyond static assets. Keep dynamic requests under the account-wide 100,000/day allowance and per-generation CPU under 10 ms; cap large dimensions and lengthy text. Upstream production analytics/logpush and custom domain settings are separate environment-specific choices. Workers Free dynamic requests are shared across this account (100,000/day), with 10 ms CPU per invocation; workload fit is conditional and has not been measured. Check current Cloudflare pricing before deploying.
Is placeholders.dev open source?+
The upstream repository declares the MIT license. Read its terms at https://raw.githubusercontent.com/Cherry/placeholders.dev/31976442d30800f768cb991c285c9af0d0dcac38/LICENSE. Source code and contributor credit are available at https://github.com/Cherry/placeholders.dev.
Discussion · 0
sign in to comment →