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 Workers + D1 + R2 setup can fit Cloudflare Free allowances for a small personal image library. R2 requires billing setup and charges beyond its allowance. Admin authentication is off by default: configure it before storing personal data. Deployment, CPU and upload capacity have not been tested here.
Hosting requirements
- Use the Workers deployment with D1 metadata and one R2 Standard bucket. Provision your own resources, run database/init.sql, supply D1_DATABASE_ID and R2_BUCKET_NAME, and enable an R2 upload channel in the application settings. Do not set KV_NAMESPACE_ID for this D1 setup; the runtime selects KV first if both are present.
- Keep Workers requests within the account Free allowance of 100,000/day and CPU within 10 ms per invocation. Large multipart parsing, metadata indexing, password hashing and image processing need measurement; this review does not establish an unlimited or high-volume upload capacity.
- Keep D1 within its account Free storage, 5 million rows read/day and 100,000 rows written/day. Listing, index maintenance, settings and sessions consume database operations in addition to each file upload.
- Use R2 Standard within 10 GB-month storage, 1 million Class A operations/month and 10 million Class B operations/month. Billing-enabled R2 can incur charges beyond those allowances; Infrequent Access storage has different charges.
- The IMAGES binding is declared, but resizing is opt-in. Leave resizing off or stay within Images Free allowances of 5,000 unique transformations/month. New transformations fail above the Free limit; an Images Paid plan can add charges. R2 files are not hosted Cloudflare Images storage.
- Configure admin credentials and user upload authentication before putting personal data in the app, then verify access. Default admin access is open. The deployment config generator logs business variables and does not redact every password field; do not place passwords in WORKER_VARS. Configure secrets through a private provisioning path such as Wrangler.
- Optional Telegram, Discord, Hugging Face, S3, WebDAV, content moderation and AI providers have separate terms and costs. This free-tier assessment excludes those services, external AI and custom-domain registration.
Sources checked 01/10/2026
Repository snapshot: 8fbe630. Hosting eligibility reflects the deployment documentation and listed assumptions.
- imgur ↗
It is suited to personal image hosting, website asset management, and lightweight file distribution.
- cloudinary ↗
It is suited to personal image hosting, website asset management, and lightweight file distribution.
- workers ↗
main = "index.js"
- d1 ↗
[[d1_databases]] binding = "img_d1"
- r2 ↗
[[r2_buckets]] binding = "img_r2"
- images ↗
[images] binding = "IMAGES"
- free-tier-eligible ↗
| **Free** | 100,000 per day | No charge for duration | 10 milliseconds of CPU time per invocation |
- free-tier-eligible ↗
| Rows read | 5 million / day | First 25 billion / month included + $0.001 / million rows |
- free-tier-eligible ↗
| Rows written | 100,000 / day | First 50 million / month included + $1.00 / million rows |
- free-tier-eligible ↗
| Storage | 10 GB-month / month | | Class A Operations | 1 million requests / month | | Class B Operations | 10 million requests / month |
- free-tier-eligible ↗
On the Free plan, you can request up to 5,000 unique transformations each month for free.
- free-tier-eligible ↗
// D1 数据库 if (env.D1_DATABASE_ID) { toml += ` [[d1_databases]] binding = "img_d1" database_name = "img_d1" database_id = "${env.D1_DATABASE_ID}" `; } // KV 命名空间 if (env.KV_NAMESPACE_ID) { toml += ` [[kv_namespaces]] binding = "img_url" id = "${env.KV_NAMESPACE_ID}" `; } // R2 存储桶 if (env.R2_BUCKET_NAME) { toml += ` [[r2_buckets]] binding = "img_r2" bucket_name = "${env.R2_BUCKET_NAME}" `; }
- free-tier-eligible ↗
if (!adminConfigured) { return AUTHORIZED('admin');
- MIT ↗
MIT License Copyright (c) 2024 MarSeventh 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.
- architecture ↗
let toml = `name = "${name}" main = "index.js" compatibility_date = "2024-08-21" compatibility_flags = ["global_fetch_strictly_public"] [assets] directory = "../../frontend-dist" binding = "ASSETS" not_found_handling = "single-page-application" [images] binding = "IMAGES" `;
- architecture ↗
run: node deploy/worker/generate-routes.js
- architecture ↗
run: node deploy/worker/generate-toml.js
- architecture ↗
await R2DataBase.put(fullId, formdata.get('file'));
- architecture ↗
return new D1Database(env.img_d1);
- architecture ↗
object = await R2DataBase.get(fileId);
- architecture ↗
if (accessConfig.imageTransformEnabled !== true) {
Upstream screenshot · MarSeventh/CloudFlare-ImgBed repository contributors ↗. 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.
Personal image uploads, hosted file links and a file-management interface. Excludes the Imgur community, discovery feed and social features.
See supporting source ↗Self-hosted image/file storage and delivery, with optional Cloudflare Images resizing. Excludes Cloudinary API compatibility, full asset-management workflows, advanced transformations and video processing.
See supporting source ↗How it works
The shape of CloudFlare ImgBed on Cloudflare, and how it stacks up against the rented tools it replaces.
Architecture
Diagram based on the linked repository documentation. See the sources and hosting assumptions above.
View upstream source ↗Configuration and workflow sources
Reviewed commit 8fbe63036b47. This configuration evidence comes from reading source files. Execution checks, when available, appear in the project's runtime review.
Partial source coverage: 52 files outside collection bounds; 0 collection or parsing issues. Dynamic imports and generated entrypoints may need manual review.
Deployment configuration · 1 files
Cloudflare Workers · compatibility 2024-08-21
cloudflare-imgbed · default
Entrypoint: index.js
Static assets: ../../frontend-dist · single-page-application
IMAGES→ ImagesASSETS→ 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.
- L298 · fetch handler exported · references ASSETS · calls matchRoute, env.ASSETS.fetch, request.method.toUpperCase, method.charAt, toLowerCase, method.slice, collectMiddlewares, Array.isArray, middlewares.push, mod.onRequest.slice, route.path.endsWith, route.path.slice, ctx.waitUntil.bind, maybeServeFromCache, executeChain
- L129 · matchRoute calls (conditional paths may differ): pathname.startsWith, pathname.slice, filter, rest.split
- L149 · collectMiddlewares calls (conditional paths may differ): Array.isArray, handlers.push
- L163 · createNextRequest calls (conditional paths may differ): toString
- L172 · executeChain calls (conditional paths may differ): createNextRequest, context.next
- L200 · parseCacheDirective calls (conditional paths may differ): cacheControl.split, rawDirective.trim, part.indexOf, toLowerCase, trim, part.slice, replace, Number.parseInt, Number.isFinite
- L220 · responseHasCacheDirective calls (conditional paths may differ): parseCacheDirective
- L224 · getResponseCacheTtl calls (conditional paths may differ): response.headers.get, parseCacheDirective
- L240 · isCacheStoreRequest calls (conditional paths may differ): request.headers.has
- L244 · isCacheableResponse calls (conditional paths may differ): isCacheStoreRequest, response.headers.has, response.headers.get, responseHasCacheDirective, getResponseCacheTtl
- L267 · maybeServeFromCache calls (conditional paths may differ): isCacheLookupRequest, producer, createCacheKeyRequest, cache.match, responseFromHeadCache, isCacheableResponse, ctx.waitUntil, catch, cache.put, response.clone, console.warn
Environment references: env.ASSETS
Environment references: env.WORKER_NAME · env.D1_DATABASE_ID · env.KV_NAMESPACE_ID · env.R2_BUCKET_NAME · env.WORKER_VARS
- L34 · hasOnRequestExport calls (conditional paths may differ): readFileSync, test
- L43 · toVarName calls (conditional paths may differ): replace, relative, join, map, rel.split, toUpperCase, part.charAt, part.slice
- L60 · toUrlPath calls (conditional paths may differ): replace, relative, rel.endsWith, rel.slice
- L80 · toImportPath calls (conditional paths may differ): replace, relative, rel.startsWith
- L89 · getMiddlewareChain calls (conditional paths may differ): dirPath.split, middlewares.find, chain.push
- L112 · scanDir calls (conditional paths may differ): sort, readdirSync, join, statSync, stat.isDirectory, SKIP_DIRS.has, basename, scanDir, entry.endsWith, replace, relative, dirPath.replace, middlewares.push, toImportPath, hasOnRequestExport, toVarName, toUrlPath, routes.push
- L13 · createDatabaseAdapter calls (conditional paths may differ): console.error
- L148 · getDatabase calls (conditional paths may differ): createDatabaseAdapter
Environment references: env.img_url · env.img_d1
- L18 · onRequest calls (conditional paths may differ): fetchSecurityConfig, fetchUploadConfig, userAuthCheck, UnauthorizedResponse, getUploadIp, isBlockedUploadIp, createResponse, url.searchParams.get, parseInt, handleCleanupRequest, initializeChunkedUpload, handleChunkMerge, handleChunkUpload, processFileUpload
- L78 · processFileUpload calls (conditional paths may differ): request.formData, url.searchParams.get, getUploadIp, getIPAddress, sanitizeUploadFolder, getTime, formdata.get, toFixed, createResponse, fileType.startsWith, arrayBuffer, file.slice, getImageDimensions, console.error, join, slice, fileName.split, pop, resolveFileExt, buildUniqueFileId
- L275 · buildUploadResponse calls (conditional paths may differ): createResponse, JSON.stringify
- L288 · uploadFileToCloudflareR2 calls (conditional paths may differ): getDatabase, createResponse, r2Settings.channels.find, R2DataBase.put, formdata.get, moderateContent, db.put, waitUntil, endUpload, buildUploadResponse
- L344 · uploadFileToS3 calls (conditional paths may differ): getDatabase, s3Channels.find, Math.floor, Math.random, createResponse, formdata.get, file.arrayBuffer, s3Client.send, db.put, purgeCDNCache, moderateContent, waitUntil, endUpload, buildUploadResponse
- L439 · uploadFileToTelegram calls (conditional paths may differ): getDatabase, tgChannels.find, Math.floor, Math.random, createResponse, formdata.get, uploadLargeFileToTelegram, fileName.replace, formdata.set, find, Object.keys, fileType.startsWith, url.searchParams.get, telegramAPI.sendFile, telegramAPI.getFileInfo, telegramAPI.getFilePath, toFixed, buildUploadResponse, moderateContent, db.put
- L558 · uploadFileToExternal calls (conditional paths may differ): getDatabase, formdata.get, createResponse, db.put, waitUntil, endUpload, buildUploadResponse
- L589 · uploadFileToDiscord calls (conditional paths may differ): getDatabase, createResponse, discordChannels.find, Math.floor, Math.random, formdata.get, discordAPI.sendFile, discordAPI.getFileInfo, toFixed, fileInfo.url.replace, moderateContent, db.put, waitUntil, endUpload, buildUploadResponse, console.error
- L674 · uploadFileToHuggingFace calls (conditional paths may differ): getDatabase, console.log, createResponse, hfChannels.find, Math.floor, Math.random, formdata.get, crypto.randomUUID, fullId.lastIndexOf, fullId.substring, huggingfaceAPI.uploadFile, moderateContent, db.put, purgeCDNCache, waitUntil, endUpload, buildUploadResponse, console.error
- L788 · uploadFileToWebDAV calls (conditional paths may differ): getDatabase, createResponse, webdavChannels.find, Math.floor, Math.random, formdata.get, webdavAPI.putFile, webdavAPI.buildPublicUrl, moderateContent, db.put, purgeCDNCache, waitUntil, endUpload, buildUploadResponse, console.error
- L862 · tryRetry calls (conditional paths may differ): url.searchParams.set, uploadFileToCloudflareR2, uploadFileToTelegram, uploadFileToS3, uploadFileToHuggingFace, uploadFileToWebDAV, uploadFileToDiscord, retryRes.text, res.text, createResponse, JSON.stringify
Environment references: env.img_r2
- L30 · onRequest calls (conditional paths may differ): decodeURIComponent, join, params.path.split, fetchSecurityConfig, parseImageTransform, validateImageTransformRequest, request.headers.get, buildFileAccessContext, isDomainAllowed, returnBlockImg, getDatabase, db.getWithMetadata, encodeURIComponent, returnWithCheck, validateImageTransformSource, transformImageRequestViaUrl, handleR2File, transformImageResponse, handleS3File, handleDiscordChunkedFile
- L231 · buildFileAccessContext calls (conditional paths may differ): url.searchParams.get, authenticate
- L256 · getChunkedFileCacheControl calls (conditional paths may differ): getFileCacheControl
- L264 · handleTelegramChunkedFile calls (conditional paths may differ): getDatabase, resolveTelegramCredentials, JSON.parse, chunks.sort, console.error, chunks.reduce, setCommonHeaders, getChunkedFileCacheControl, headers.set, totalSize.toString, Date.now, request.headers.get, headers.get, range.match, parseInt, handleHeadRequest, fetchTelegramChunkWithRetry, Math.max, Math.min, chunkData.slice
- L419 · fetchTelegramChunkWithRetry calls (conditional paths may differ): tgApi.getFileContent, response.arrayBuffer, console.warn, setTimeout
- L457 · handleDiscordChunkedFile calls (conditional paths may differ): getDatabase, resolveDiscordCredentials, JSON.parse, chunks.sort, console.error, chunks.reduce, setCommonHeaders, getChunkedFileCacheControl, headers.set, totalSize.toString, Date.now, request.headers.get, headers.get, range.match, parseInt, handleHeadRequest, fetchDiscordChunkWithRetry, Math.max, Math.min, chunkData.slice
- L613 · fetchDiscordChunkWithRetry calls (conditional paths may differ): discordAPI.getFileURL, fileUrl.replace, fetch, response.arrayBuffer, console.warn, setTimeout
- L662 · handleR2File calls (conditional paths may differ): request.headers.get, range.match, parseInt, R2DataBase.get, object.writeHttpMetadata, setCommonHeaders, getFileCacheControl, handleHeadRequest, headers.set, object.range.length.toString
- L736 · handleS3File calls (conditional paths may differ): getS3CdnFileUrl, setCommonHeaders, getFileCacheControl, handleHeadRequest, request.headers.get, fetch, console.warn, handleS3FileViaAPI, response.headers.get, headers.set, console.error
- L801 · getS3CdnFileUrl calls (conditional paths may differ): getDatabase, resolveS3Credentials, buildCdnFileUrl, console.warn
- L813 · handleS3FileViaAPI calls (conditional paths may differ): getDatabase, resolveS3Credentials, request.headers.get, s3Client.send, setCommonHeaders, getFileCacheControl, headers.set, response.ContentLength.toString, handleHeadRequest
- L879 · handleDiscordFile calls (conditional paths may differ): getDatabase, resolveDiscordCredentials, discordAPI.getFileURL, fileUrl.replace, setCommonHeaders, getFileCacheControl, handleHeadRequest, request.headers.get, fetch, response.headers.get, headers.set
- L948 · handleHuggingFaceFile calls (conditional paths may differ): getDatabase, resolveHuggingFaceCredentials, HuggingFaceAPI.getMetadataFileSize, setCommonHeaders, getFileCacheControl, headers.set, fileSize.toString, handleHeadRequest, request.headers.get, fetch, response.headers.get
- L1024 · handleWebDAVFile calls (conditional paths may differ): getDatabase, resolveWebDAVCredentials, getWebDAVPublicFileUrl, setCommonHeaders, getFileCacheControl, request.headers.get, fetch, webdavAPI.getFile, console.warn, response.headers.get, headers.set, handleHeadRequest
- L1106 · getWebDAVPublicFileUrl calls (conditional paths may differ): buildWebDAVUrl, console.warn
Environment references: env.img_r2
- L25 · generateShortId calls (conditional paths may differ): chars.charAt, Math.floor, Math.random
- L37 · getIPAddress calls (conditional paths may differ): fetchSecurityConfig, Array.isArray, filter, customApi.responseFields.map, replace, String, replaceIpPlaceholder, queryUrl.searchParams.append, fetch, queryUrl.toString, JSON.parse, trim, response.text, join, value.map, JSON.stringify, responseFields.map, reduce, map, split
- L113 · sanitizeFileName calls (conditional paths may differ): decodeURIComponent, pop, fileName.split, fileName.replace
- L126 · sanitizeUploadFolder calls (conditional paths may differ): folder.trim, test, decodeURIComponent, folder.replace, join, map, folder.split, filter, segments.map, seg.replace, sanitizedSegments.join
- L175 · isExtValid calls (conditional paths may differ): includes
- L189 · resolveFileExt calls (conditional paths may differ): pop, fileName.split, isExtValid, fileType.split
- L212 · getImageDimensions calls (conditional paths may differ): view.getUint32, view.getUint16, view.getInt32, Math.abs, console.error
- L297 · moderateContent calls (conditional paths may differ): fetchSecurityConfig, fetch, params.toString, fetchResponse.json, console.error, encodeURIComponent
- L371 · purgeCDNCache calls (conditional paths may differ): purgeCFCache, console.error, purgeRandomFileListCache, purgePublicFileListCache
- L389 · endUpload calls (conditional paths may differ): sanitizeUploadFolder, url.searchParams.get, purgeCDNCache, addFileToIndex
- L402 · getUploadIp calls (conditional paths may differ): request.headers.get, map, ip.split, i.trim
- L416 · isBlockedUploadIp calls (conditional paths may differ): getDatabase, db.get, list.split, list.includes, console.error
- L436 · buildUniqueFileId calls (conditional paths may differ): getDatabase, resolveFileExt, url.searchParams.get, sanitizeUploadFolder, sanitizeFileName, Date.now, Math.floor, Math.random, generateShortId, db.get, fileName.substring, fileName.lastIndexOf, baseName.substring, baseName.lastIndexOf
- L516 · selectConsistentChannel calls (conditional paths may differ): uploadId.charCodeAt, Math.abs
Environment references: env.dev_mode
- L26 · parseImageTransform calls (conditional paths may differ): parseDimension, parseFit, parseFallback, parseAllowedSizes, allowedSizes.has
- L92 · validateImageTransformRequest calls (conditional paths may differ): imageTransformError, request.headers.has
- L112 · validateImageTransformSource calls (conditional paths may differ): normalizeContentType, inferImageTypeFromFileName, canTransformImageType, imageTransformError
- L132 · transformImageRequestViaUrl calls (conditional paths may differ): hasConfiguredImageProcessor, test, request.headers.get, sourceUrl.searchParams.delete, join, map, Object.entries, encodeURIComponent, transformUrl.toString
- L170 · transformImageResponse calls (conditional paths may differ): normalizeContentType, response.headers.get, OUTPUT_FORMATS.get, canTransformImageType, imageTransformError, parseContentLength, response.clone, runImageTransform, catch, fallbackResponse.body.cancel, mergeTransformedHeaders, Number.isInteger, console.error
- L230 · runImageTransform calls (conditional paths may differ): output, transform, images.input, output.response, processor.transform
- L257 · hasConfiguredImageProcessor calls (conditional paths may differ): hasConfiguredStreamImageProcessor
- L266 · hasConfiguredStreamImageProcessor calls (conditional paths may differ): Boolean
- L271 · canTransformImageType calls (conditional paths may differ): OUTPUT_FORMATS.has, hasConfiguredStreamImageProcessor, hasConfiguredImageProcessor
- L287 · parseDimension calls (conditional paths may differ): searchParams.getAll, test, Number, Number.isSafeInteger
- L305 · parseFit calls (conditional paths may differ): searchParams.getAll
- L316 · parseFallback calls (conditional paths may differ): searchParams.getAll
- L327 · parseAllowedSizes calls (conditional paths may differ): value.trim, filter, map, value.split, toLowerCase, size.trim
- L338 · normalizeContentType calls (conditional paths may differ): toLowerCase, trim, split
- L342 · inferImageTypeFromFileName calls (conditional paths may differ): exec, fileName.trim, IMAGE_TYPES_BY_EXTENSION.get, toLowerCase
- L351 · parseContentLength calls (conditional paths may differ): test, Number, Number.isSafeInteger
- L357 · mergeTransformedHeaders calls (conditional paths may differ): headers.delete, removeVaryToken, transformedHeaders.get, headers.set
- L375 · removeVaryToken calls (conditional paths may differ): headers.get, filter, map, vary.split, token.trim, token.toLowerCase, tokenToRemove.toLowerCase, headers.set, remaining.join, headers.delete
Environment references: env.IMAGES · env.IMAGE_PROCESSOR
- L5 · withDefaultCacheControl calls (conditional paths may differ): response.headers.has, headers.set
- L20 · errorHandling calls (conditional paths may differ): withDefaultCacheControl, context.next
- L50 · extractRequiredPermission calls (conditional paths may differ): replace, pathname.toLowerCase, path.startsWith
- L71 · authentication calls (conditional paths may differ): extractRequiredPermission, authenticate, UnauthorizedException, context.next
- L16 · userAuthCheck calls (conditional paths may differ): authenticate
- L34 · checkAdmin calls (conditional paths may differ): AUTHORIZED, validateSession
- L54 · checkUser calls (conditional paths may differ): validateSession, AUTHORIZED, extractAuthCode, verifyPassword
- L93 · authenticate calls (conditional paths may differ): fetchSecurityConfig, adminUsername.trim, adminPassword.trim, userAuthCode.trim, getDatabase, validateApiToken, AUTHORIZED, checkAdmin, checkUser
- L139 · extractAuthCode calls (conditional paths may differ): url.searchParams.get, request.headers.get, get, console.error, cookies.match, decodeURIComponent
- L6 · onRequest calls (conditional paths may differ): getDatabase, getSecurityConfig, JSON.parse, JSON.stringify, request.json, normalizeSessionMaxAgeDays, normalizeImageTransformAllowedSizes, isHashed, hashPassword, db.put, destroySessionsByAuthType
- L140 · getSecurityConfig calls (conditional paths may differ): db.get, JSON.parse, normalizeIpQueryParams, normalizeIpQueryResponseFields, normalizeImageTransformAllowedSizes, normalizeSessionMaxAgeDays
- L204 · normalizeImageTransformAllowedSizes calls (conditional paths may differ): trim, String, filter, map, split, toLowerCase, size.trim, size.match, match.slice, Number, normalized.includes, normalized.push, normalized.join
- L233 · normalizeIpQueryParams calls (conditional paths may differ): Array.isArray, params.map
- L244 · normalizeIpQueryResponseFields calls (conditional paths may differ): Array.isArray, fields.map
Environment references: env.AUTH_CODE · env.BASIC_USER · env.BASIC_PASS · env.ModerateContentApiKey · env.ALLOWED_DOMAINS · env.WhiteList_Mode
- L12 · handleOptions calls (conditional paths may differ): context.next
- L24 · jsonResponse calls (conditional paths may differ): JSON.stringify
- L41 · errorResponse calls (conditional paths may differ): jsonResponse
- L54 · calculateChecksum calls (conditional paths may differ): JSON.stringify, encoder.encode, crypto.subtle.digest, Array.from, join, hashArray.map, padStart, b.toString
- L67 · calculateFallbackChecksum calls (conditional paths may differ): JSON.stringify, encoder.encode, Math.imul, padStart, h1.toString, h2.toString
- L86 · isValidSessionId calls (conditional paths may differ): validPattern.test
- L105 · isValidChunkId calls (conditional paths may differ): parseInt, isNaN, String
- L119 · sanitizeString calls (conditional paths may differ): str.replace
- L133 · sanitizeObject calls (conditional paths may differ): sanitizeString, Array.isArray, obj.map, sanitizeObject, Object.keys
- L163 · validateMetadata calls (conditional paths may differ): Array.isArray
- L212 · validateRecord calls (conditional paths may differ): record.id.includes, validateMetadata
- L239 · validateRequestBody calls (conditional paths may differ): isValidChunkId, isValidSessionId, Array.isArray, validateRecord, test
- L292 · onRequestPost calls (conditional paths may differ): request.json, errorResponse, validateRequestBody, sanitizeObject, calculateFallbackChecksum, calculateChecksum, calculatedChecksum.toLowerCase, checksum.toLowerCase, getDatabase, Date.now, db.put, JSON.stringify, jsonResponse, console.error
- L376 · onRequest calls (conditional paths may differ): onRequestPost, onRequestOptions, errorResponse
- L31 · getIndexChunkSize calls (conditional paths may differ): checkDatabaseConfig
- L42 · jsonResponse calls (conditional paths may differ): JSON.stringify
- L59 · errorResponse calls (conditional paths may differ): jsonResponse
- L73 · isValidSessionId calls (conditional paths may differ): validPattern.test
- L91 · validateRequestBody calls (conditional paths may differ): isValidSessionId, Number.isInteger
- L129 · readAllChunks calls (conditional paths may differ): db.get, missingChunks.push, JSON.parse, Array.isArray, chunks.push, parseInt, console.error, missingChunks.join
- L180 · assembleChunks calls (conditional paths may differ): chunks.sort, allFiles.push
- L200 · saveIndex calls (conditional paths may differ): getIndexChunkSize, files.slice, chunks.push, parseFloat, Date.now, Math.round, db.put, JSON.stringify, chunks.map, Promise.all, console.log, totalSizeMB.toFixed, console.error
- L269 · cleanupChunks calls (conditional paths may differ): deletePromises.push, catch, db.delete, console.warn, Promise.all, console.log
- L291 · cleanupOldIndexChunks calls (conditional paths may differ): db.get, JSON.parse, deletePromises.push, catch, db.delete, console.warn, Promise.all, console.log
- L327 · onRequestPost calls (conditional paths may differ): request.json, errorResponse, validateRequestBody, getDatabase, saveIndex, cleanupOldIndexChunks, jsonResponse, readAllChunks, assembleChunks, console.warn, getIndexChunkSize, Math.ceil, context.waitUntil, cleanupChunks, console.error
- L435 · onRequest calls (conditional paths may differ): onRequestPost, onRequestOptions, errorResponse
- L21 · jsonResponse calls (conditional paths may differ): JSON.stringify
- L34 · errorResponse calls (conditional paths may differ): jsonResponse
- L45 · onRequestPost calls (conditional paths may differ): request.json, errorResponse, includes, getDatabase, map, Object.entries, cleanPersistedMetadata, db.put, Promise.all, errors.push, JSON.stringify, key.startsWith, jsonResponse, errors.slice, console.error
- L157 · onRequest calls (conditional paths may differ): onRequestPost, onRequestOptions, errorResponse
- L8 · onRequest calls (conditional paths may differ): createAIContext, ai.getConfig, json, Date.now, readBoundedBody, formData, context.request.headers.get, form.get, JSON.parse, Array.isArray, validateFileId, ids.has, ids.add, getDatabase, includes, db.getWithMetadata, toLowerCase, String, ORIGINAL_TYPES.has, arrayBuffer
- L35 · worker calls (conditional paths may differ): Date.now, form.get, includes, db.getWithMetadata, toLowerCase, String, ORIGINAL_TYPES.has, arrayBuffer, image.slice, String.fromCharCode, signature.slice, fileIdentity, image.arrayBuffer, buffer.subarray, ai.invoke, btoa, errorResult
- L5 · onRequestPost calls (conditional paths may differ): readJSON, getAIConfig, prepareAIConfig, invoke, createAIContext, json, errorResponse
- L25 · jsonResponse calls (conditional paths may differ): JSON.stringify
- L41 · errorResponse calls (conditional paths may differ): jsonResponse
- L67 · onRequestGet calls (conditional paths may differ): url.searchParams.get, parseInt, getDatabase, db.list, Array.isArray, errorResponse, item.name.startsWith, stripSensitiveMetadata, isChunkedFileNeedingValue, db.get, console.error, records.push, jsonResponse
- L181 · onRequest calls (conditional paths may differ): onRequestGet, onRequestOptions, errorResponse
- L24 · jsonResponse calls (conditional paths may differ): JSON.stringify
- L40 · errorResponse calls (conditional paths may differ): jsonResponse
- L57 · isIndexRelatedKey calls (conditional paths may differ): key.startsWith
- L92 · stripManagePrefix calls (conditional paths may differ): key.startsWith, key.slice
- L106 · onRequestGet calls (conditional paths may differ): getDatabase, db.list, Array.isArray, errorResponse, isIndexRelatedKey, db.get, JSON.parse, stripManagePrefix, console.error, jsonResponse
- L203 · onRequest calls (conditional paths may differ): onRequestGet, onRequestOptions, errorResponse
- L3 · onRequest calls (conditional paths may differ): getDatabase, db.get, list.split, request.text, list.push, db.put, list.join
- L3 · onRequest calls (conditional paths may differ): getDatabase, db.get
- L5 · onRequest calls (conditional paths may differ): url.searchParams.get, parseInt, JSON.stringify, Math.max, getDatabase, createMetadataViewContext, readIndex, map, allRecords.files.filter, Promise.all, matchingFiles.map, buildFileMetadataForManagement, files.slice
- L3 · onRequest calls (conditional paths may differ): getDatabase, db.get, list.split, request.text, list.filter, db.put, list.join
- L15 · onRequest calls (conditional paths may differ): jsonResponse, context.request.json, normalizeBatchFileIds, mapConcurrent, join, map, fileId.split, deleteFile, String, results.filter, context.waitUntil, batchRemoveFilesFromIndex
- L48 · jsonResponse calls (conditional paths may differ): JSON.stringify
- L5 · onRequest calls (conditional paths may differ): getAIConfig, json, publicAIConfig, readJSON, prepareAIConfig, put, getDatabase, JSON.stringify, errorResponse
- L4 · onRequest calls (conditional paths may differ): getDatabase, getOthersConfig, JSON.stringify, request.json, createApiToken, deleteApiToken, db.put
- L71 · getOthersConfig calls (conditional paths may differ): db.get, JSON.parse
Environment references: env.disable_telemetry · env.AllowRandom · env.CF_ZONE_ID · env.CF_EMAIL · env.CF_API_KEY
- L3 · onRequest calls (conditional paths may differ): getDatabase, getPageConfig, JSON.stringify, request.json, processAnnouncementInfo, db.put
- L45 · getPageConfig calls (conditional paths may differ): db.get, JSON.parse, Number.isFinite, Number, config.push, Object.prototype.hasOwnProperty.call, config.findIndex
- L357 · processAnnouncementInfo calls (conditional paths may differ): Array.isArray, previousSettings.config.find, settings.config.find, Date.now
Environment references: env.USER_CONFIG
Build and deployment pipeline · 5 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, workflow_dispatch
deploy · no job dependencies declared
Condition: ${{ github.event.repository.fork }}
- Check deployment config
if [ -z "${{ secrets.CLOUDFLARE_API_TOKEN }}" ]; then echo "CLOUDFLARE_API_TOKEN not configured, skipping Worker deployment" echo "skip=true" >> $GITHUB_OUTPUT else echo "skip=false" >> $GITHUB_OUTPUT fi - Checkout
actions/checkout@v6Condition: steps.check.outputs.skip != 'true' - Setup Node.js
actions/setup-node@v5Condition: steps.check.outputs.skip != 'true' - Install dependencies
npm ci --workspace=@cloudflare-imgbed/common --workspace=@cloudflare-imgbed/worker --omit=devCondition: steps.check.outputs.skip != 'true' - Generate worker routes
node deploy/worker/generate-routes.jsCondition: steps.check.outputs.skip != 'true' - Generate wrangler config
node deploy/worker/generate-toml.jsCondition: steps.check.outputs.skip != 'true' - Deploy to Cloudflare Workers
cloudflare/wrangler-action@v3Wrangler command: deploy --config deploy/worker/wrangler.tomlCondition: steps.check.outputs.skip != 'true'
Triggers: push, workflow_dispatch
build-and-push · no job dependencies declared
Condition: github.repository == 'MarSeventh/CloudFlare-ImgBed'
- Checkout code
actions/checkout@v6 - Read version from package.json
echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT - Set up QEMU
docker/setup-qemu-action@v4 - Set up Docker Buildx
docker/setup-buildx-action@v4 - Log in to Docker Hub
docker/login-action@v4 - Extract metadata for Docker
docker/metadata-action@v6 - Build and push Docker image
docker/build-push-action@v7
Triggers: release
sync-release · no job dependencies declared
Condition: github.repository == 'MarSeventh/CloudFlare-ImgBed'
- Sync or Update Release
# 格式化参数 ARGS="" if [ "$IS_DRAFT" = "true" ]; then ARGS="$ARGS --draft"; else ARGS="$ARGS --draft=false"; fi if [ "$IS_PRERELEASE" = "true" ]; then ARGS="$ARGS --prerelease"; else ARGS="$ARGS --prerelease=false"; fi echo "正在检查目标仓库 $TARGET_REPO 中是否存在 Tag: $TAG_NAME..." # 检查目标仓库是否已有该 Release if gh release view "$TAG_NAME" --repo "$TARGET_REPO" > /dev/null 2>&1; then echo "检测到现有 Release,正在执行更新操作..." gh release edit "$TAG_NAME" \ --repo "$TARGET_REPO" \ --title "$RELEASE_TITLE" \ --notes "$RELEASE_B…
Triggers: schedule, workflow_dispatch
Render with official source and deploy to Pages · no job dependencies declared
Condition: ${{ github.repository == 'MarSeventh/CloudFlare-ImgBed' }}
- Check out this repository
actions/checkout@v7 - Check out official Star History source
actions/checkout@v7 - Set up pnpm
pnpm/action-setup@v6 - Set up Node.js
actions/setup-node@v7 - Install official renderer dependencies
set -euo pipefail (cd "${STAR_HISTORY_SOURCE_DIR}" && pnpm install --frozen-lockfile) (cd "${STAR_HISTORY_SOURCE_DIR}/backend" && pnpm install --frozen-lockfile) - Refresh data and render official light/dark charts locally
set -euo pipefail if [[ -z "${GITHUB_JOB_TOKEN}" ]]; then echo "::error::The workflow GITHUB_TOKEN is unavailable." exit 1 fi data_file="${RUNNER_TEMP}/star-history-data.json" server_log="${RUNNER_TEMP}/star-history-backend.log" cleanup() { if [[ -n "${server_pid:-}" ]]; then kill "${server_pid}" 2>/dev/null || true wait "${server_pid}" 2>/dev/null || true fi } trap cleanup EXIT refresh_args=( --repository "${STAR_HISTORY_REPOSITORIES}" --output "${data_file}" --token-env GITHUB_JOB_TOKEN --sta… - Configure GitHub Pages
actions/configure-pages@v6Condition: steps.render.outputs.changed == 'true' - Upload Pages artifact
actions/upload-pages-artifact@v5Condition: steps.render.outputs.changed == 'true' - Deploy Pages artifact
actions/deploy-pages@v5Condition: steps.render.outputs.changed == 'true' - Delete old GitHub Pages deployment records
actions/github-script@v9Condition: ${{ success() && steps.render.outcome == 'success' }}
Triggers: schedule, workflow_dispatch
Sync latest commits from upstream repo · no job dependencies declared
Condition: ${{ github.event.repository.fork }}
- Checkout target repo
actions/checkout@v6 - Check breaking update policy
bash .github/breaking-updates/scripts/breaking-update-guard.sh check - Sync upstream changes
aormsby/Fork-Sync-With-Upstream-action@v3.4Condition: steps.breaking_guard.outputs.blocked != 'true' - Complete breaking update notices
bash .github/breaking-updates/scripts/breaking-update-guard.sh completeCondition: steps.sync.outcome == 'success' - Trigger Worker deploy
gh workflow run deploy-worker.yml --repo "${{ github.repository }}" --ref mainCondition: steps.sync.outcome == 'success' - Sync failure guidance
{ echo "## ⚠️ 上游同步未完成 / Upstream synchronization did not complete" echo echo "上游同步步骤未能完成。若上游近期修改了 workflow 文件,GitHub 可能会暂停包含 workflow 变更的自动同步;也可能是当前 Fork 的 Actions 权限或同步权限发生了变化。" echo echo "The upstream synchronization step could not be completed. If the upstream workflow files changed recently, GitHub may pause automatic synchronization that includes workflow changes. The issue may also be related to Actions or synchronization permissions in this fork." echo echo "建议先在 GitHub 页面手动执行一次 **Sync f…Condition: failure() && steps.sync.outcome == 'failure'
deploy:worker: node deploy/worker/generate-routes.js && npx wrangler deploy --config deploy/worker/wrangler.toml
Repository README
View original on GitHub ↗Full upstream document by @MarSeventh · README.md · snapshot 8fbe630
🗂️ Beyond image hosting: an all-in-one, open-source file management hub.
[!IMPORTANT]
If you encounter issues, please check the announcement first. Important notifications and non-compatible updates will be explained in the announcement!
1. 💡 Introduction
CloudFlare ImgBed is a self-hosted image and file hosting solution for Docker and serverless environments, bringing Telegram, Discord, Cloudflare R2, S3-compatible storage, Hugging Face, WebDAV, and more into one management interface. It provides file management, authentication, directory organization, content moderation, a RESTful API, and WebDAV, with a growing range of personalized AI capabilities such as image tag recognition. It is suited to personal image hosting, website asset management, and lightweight file distribution. View all features →

🤝 Partners
| Cloudflare | EdgeOne | PackyCode | HuaNa Cloud | SuWei Cloud | Linux DO |
| Provides CDN acceleration and security protection | Provides CDN acceleration and security protection | Provide premium, stable, and budget-friendly LLM APIs. Generate text, images, and code all in one place. Sign up via our link for free credits and exclusive discounts! | Provides stable and high-quality cloud computing resources. Sign up via our link for exclusive discounts! | Provides stable and high-quality cloud computing resources. Sign up via our link for exclusive discounts! | Provides community support |
2. 🖥️ Demo
Demo Address: CloudFlare ImgBed · Access Password: cfbed

Other page screenshots
Login Page
|
Upload Progress
|
File Management
|
User Management
|
Status Page
|
Public Gallery
|
3. 📚 Documentation & Updates
📖 Documentation
The documentation covers deployment, storage configuration, feature usage, RESTful API integration, WebDAV, version upgrades, and troubleshooting. Whether you are deploying the project for the first time or maintaining an existing instance, you can find the relevant instructions here.
📝 Changelog
Follow the latest features, bug fixes, compatibility changes, and upgrade notes.
4. 🌱 Ecosystem
An open-source ecosystem grows through community support. Visit the CloudFlare ImgBed Ecosystem page to explore the following resources and more:
- Plugin Extensions: Browser extensions, integrations for Typecho, WordPress, and Obsidian, OpenList drivers, and more.
- Companion Applications: Desktop clients, bot tools, and more.
- AI Agent Applications: Official project skills and related tools.
- Tutorials and Guides: High-quality videos and articles from content creators.
Discover useful plugins, applications, and tutorials, or share your own work with the community. See the Ecosystem Call for Contributions for submission guidelines. We look forward to your participation!
5. 💝 Support & Sponsors
☕ Support the Project
Maintaining an open source project takes time and effort. If CloudFlare ImgBed has helped you, consider supporting its continued development.
💖 Sponsors
Thank you to every sponsor who supports this project! Your support helps sustain ongoing maintenance and drives the continued improvement of CloudFlare ImgBed.
6. 👥 Community
🧑💻 Contributors
Thank you to everyone who has contributed code, documentation, ideas, and feedback!
⭐ Star History
If you find the project useful, please consider giving it a Star ⭐. Thank you for your support!
7. ⚖️ License & Related Projects
📄 License
[!IMPORTANT] This project is licensed under the MIT License. You may use, modify, and distribute it, provided that the original copyright and license notices are retained in all copies or substantial portions of the software.
🔗 Related Open Source Projects
- Web frontend: MarSeventh/Sanyue-ImgHub
- Desktop client: MarSeventh/satellite
- Upstream project: cf-pages/Telegraph-Image
CloudFlare ImgBed evolved from Telegraph-Image. Thanks to its original authors and contributors.
Frequently asked about CloudFlare ImgBed
What is CloudFlare ImgBed?+
CloudFlare ImgBed is a self-hosted Cloudinary/Imgur alternative built on the Cloudflare developer platform. Host a small image library and manage file links on your own Cloudflare account.
What does CloudFlare ImgBed replace?+
CloudFlare ImgBed is listed as an alternative to Cloudinary, Imgur. Compare the features and tradeoffs before migrating.
What Cloudflare primitives does CloudFlare ImgBed use?+
CloudFlare ImgBed is built on D1, Images, R2, Workers.
How much does CloudFlare ImgBed cost to run?+
The reviewed Workers + D1 + R2 setup can fit Cloudflare Free allowances for a small personal image library. R2 requires billing setup and charges beyond its allowance. Admin authentication is off by default: configure it before storing personal data. Deployment, CPU and upload capacity have not been tested here. Use the Workers deployment with D1 metadata and one R2 Standard bucket. Provision your own resources, run database/init.sql, supply D1_DATABASE_ID and R2_BUCKET_NAME, and enable an R2 upload channel in the application settings. Do not set KV_NAMESPACE_ID for this D1 setup; the runtime selects KV first if both are present. Keep Workers requests within the account Free allowance of 100,000/day and CPU within 10 ms per invocation. Large multipart parsing, metadata indexing, password hashing and image processing need measurement; this review does not establish an unlimited or high-volume upload capacity. Keep D1 within its account Free storage, 5 million rows read/day and 100,000 rows written/day. Listing, index maintenance, settings and sessions consume database operations in addition to each file upload. Use R2 Standard within 10 GB-month storage, 1 million Class A operations/month and 10 million Class B operations/month. Billing-enabled R2 can incur charges beyond those allowances; Infrequent Access storage has different charges. The IMAGES binding is declared, but resizing is opt-in. Leave resizing off or stay within Images Free allowances of 5,000 unique transformations/month. New transformations fail above the Free limit; an Images Paid plan can add charges. R2 files are not hosted Cloudflare Images storage. Configure admin credentials and user upload authentication before putting personal data in the app, then verify access. Default admin access is open. The deployment config generator logs business variables and does not redact every password field; do not place passwords in WORKER_VARS. Configure secrets through a private provisioning path such as Wrangler. Optional Telegram, Discord, Hugging Face, S3, WebDAV, content moderation and AI providers have separate terms and costs. This free-tier assessment excludes those services, external AI and custom-domain registration. Check current Cloudflare pricing before deploying.
Is CloudFlare ImgBed open source?+
The upstream repository declares the MIT license. Read its terms at https://raw.githubusercontent.com/MarSeventh/CloudFlare-ImgBed/8fbe63036b474e4c0a4be1fdcf17671569c1cf97/LICENSE. Source code and contributor credit are available at https://github.com/MarSeventh/CloudFlare-ImgBed.
Community rating
No ratings yet. Tried this project? Share your experience.
One rating per verified account. You can change or remove yours. Accounts are email verified; use of the software is self-reported.









Discussion · 0
sign in to comment →