Library / SDK
WishMelz/imgurl avatar
WishMelz/imgurl

imgurl turns a GitHub release into an image host behind jsDelivr

基于github的图片管理系统/免费 https://wishmelz.github.io/imgurl

536 stars125 forksTypeScriptMIT

At a glance

What is it?
imgurl is a Vue and TypeScript single-page app that stores uploaded images as GitHub release assets and serves them through jsDelivr, so the only infrastructure is a GitHub personal access token and a repository of your own. The README describes it as a free, simple and efficient image management system built on the GitHub API.
Who is it for?
Adopt imgurl if you need somewhere to put images for a small project and would rather not run an object store, because the storage is a GitHub release and the delivery is jsDelivr's CDN, so there is no server to pay for. Do not adopt it for anything that needs to scale past a hobbyist's traffic or that must survive a mistake, since a token with repo and user scope controls the repository and deleting a release asset removes the image from the CDN.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 37 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The storage model: a GitHub release, served by jsDelivr

imgurl is described as a free, simple and efficient image management system based on the GitHub API, which uploads and manages images and accelerates image access through the jsDelivr CDN.

That design is the whole thing. There is no server component and no database. The GitHub repository is the database, the GitHub release is the container for the images, and jsDelivr is the CDN that serves them. The README describes the result as being completely free because it is based on a GitHub repository with no extra cost, and the CDN acceleration comes from using jsDelivr to provide global delivery.

The setup instructions make the storage arrangement explicit. You create a new repository on GitHub, which can be private or public, and then you go into the repository page and create a Release, entering a version number such as v1.0.0 and clicking Publish release. The release is what holds the uploaded images.

Using a release rather than a branch or the repository root is a deliberate choice with a couple of consequences. A release is a tagged, immutable-ish snapshot, so an upload with a given filename can be replaced by a new version rather than silently overwriting the old file, which avoids the browser cache problem where an image URL keeps serving the old bytes. It also means the images sit behind the release assets API rather than the contents API, and the release tag becomes part of the CDN URL.

The feature list calls the security reliable and attributes it to using GitHub Personal Access Token authentication, and the responsive design entry covers desktop and mobile access. The image management entry is about listing what has been uploaded and managing it, and the interface supports drag-and-drop upload with one-click link copying.

The primary language is TypeScript, the licence is MIT, the default branch is main, and the last push was on 2026-09-16. There are no GitHub releases for the project itself, and the repository description carries the live demo link at imgurl-drab.vercel.app.

The token, and the permission scope that comes with it

The configuration guide starts with the GitHub token, and it is worth being precise about what it asks for.

You go to the GitHub Personal Access Tokens settings page, click Generate new token, and on the New personal access token page you set a token name such as imgurl-token, choose an expiry, and tick two permissions: repo, for repository access, and user, for user information access. Then you click Generate token.

The README includes a warning that the generated token is shown only once and must be saved. That is standard GitHub behaviour, and it matters here because the token is the only credential the system has. There is no server to hold it, so it lives in the browser.

Step three of the configuration is where the token is entered. You open the ImgURL application, click the settings button, and fill in the GitHub token and the repository path, whose format is username/repository name such as WishMelz/my-images.

Two implications follow from that design, and neither is spelled out in the README.

The first is that the token is a client-side credential. Anyone with access to the browser profile that stored it holds write access to that repository. A personal access token with repo scope is a broad permission: it is not scoped to one repository, and it can push code as well as upload release assets. For a personal image repository that is an acceptable trade, but it means the token should be issued for that one repository if GitHub's fine-grained token options are used, and it should have an expiry.

The second is that the storage is only as durable as the repository. The README says the repository can be private or public, and a private repository works with jsDelivr only for the sizes and access patterns that CDN supports for private sources. A public repository means anyone can enumerate the release assets and the images inside them, which is fine for images meant to be public and wrong for anything else.

The versioning note from step two interacts with this too. Because assets live in a release, an upload that replaces a filename needs a new release, and the old version stays addressable. That is a feature for cache busting and a liability for anyone expecting the newest file at a stable URL.

The upload flow and the link formats the tool hands you

The usage section is short, and it is the part of the README that tells you what you actually do once it is configured.

For uploading, you drag images onto the upload area or click the upload button to choose them, batch upload is supported, and the tool automatically generates a reachable CDN link for each upload. The drag-and-drop is the only real interaction on that screen, which makes sense given that the alternative is picking files one at a time through a file dialog.

For management, you view a list of the images already uploaded, copy an image link with one click, and choose between link formats, and the README names three of them: Markdown, HTML and a direct link. That trio is worth noting because it is the difference between a hosted image and a pasted snippet. The Markdown form is what you put in a README or a comment, the HTML form is what you put in an email or a page, and the direct link is what you hand to something that will fetch the bytes itself.

The screenshots in the README, hosted on jsDelivr under the WishMelz/file repository, show the upload screen and the image list with their links.

What the section does not describe is the part that decides whether the tool is useful long term: what happens when you upload a file with a name that already exists in the same release. The storage arrangement makes it a release asset, and a release is a published snapshot, so either the upload is rejected, or it overwrites the asset, or the tool creates a new release. The README does not say which, and the difference matters because jsDelivr caches by URL and a changed file at the same URL is the classic way to serve stale images for hours.

There is also no deletion story described. The image list is a list, and the README says it supports managing images, but it does not describe a delete action, and with release assets a delete is a release edit rather than a simple call. Combined with the fact that the repository is the only copy, that is the main operational question to answer before adopting the tool for anything you care about.

Running it locally, and the mismatch in the stated requirements

The quick start lists Node.js 14.0 or higher and npm or yarn as the environment requirements, then the install commands:

bash
npm install
bash
yarn install

The local development server and the production build follow the same shape:

bash
npm run dev
bash
npm run build

The README's environment requirements are where the interesting disagreement is. The package.json declares the package manager as pnpm at version 10.33.0, and the engines field asks for Node ^22.18.0 or >=24.0.0. The README says Node 14.0 or higher, and it documents npm and yarn rather than pnpm. The repository has a pnpm-lock.yaml and no package-lock.json or yarn.lock, so the lockfile in the tree agrees with the manifest and not with the README.

That means the documented requirement is roughly a decade out of date relative to the code, and following it literally gets you a Node version the project does not claim to support. The engines field is the one to trust, and pnpm is the one to use, since that is what the lockfile matches.

The build and lint scripts show what a modern Vue and TypeScript project looks like here. The build script runs a type-check alongside the production build with npm-run-all2, so the type check is part of building rather than a separate step. Type checking is vue-tsc, which is the Vue-aware TypeScript checker. The lint script runs two linters in sequence: oxlint with --fix and eslint with --fix and a cache, and eslint is configured with eslint-plugin-oxlint alongside the standard Vue and TypeScript configs. Formatting is Prettier, and format runs against src/ with the experimental CLI flag.

The dependency list is a good map of what the interface is made of. Vue 3.5 with vue-router 5 for routing, Pinia 4 with pinia-plugin-persistedstate for state, so the settings you enter survive a reload, and Tailwind 4 with the Vite plugin for styling. The component library is reka-ui, which is the successor to Radix Vue, with lucide for icons and class-variance-authority and tailwind-merge for variant handling. vue-sonner handles toasts, and axios does the HTTP calls to the GitHub API. @vueuse/core provides the composables.

The top-level entries match a Vite Vue scaffold with a few additions: components.json for the shadcn-style component configuration, env.d.ts for environment typing, and separate tsconfig files for the app and the node tooling.

Where imgurl is the wrong choice

Three limits, and the first is about what GitHub is being asked to be.

The first is that GitHub was not designed to be an object store, and the tool leans on the release assets API as if it were one. That works, and many small projects do exactly this, but it inherits every property of the service rather than the properties you want from a store. There is no content type negotiation, no cache control beyond what jsDelivr imposes, no multipart upload, no resizing, no expiry rules, and the storage limit is a repository limit rather than a bucket you can plan around. A file served from a GitHub release has an address that encodes the repository, the release tag and the asset name, which means a URL cannot be changed without breaking it.

The second is the credential exposure, which the README frames as a feature. Using a GitHub Personal Access Token with repo and user scope, entered into a browser and stored there, is a broad credential held in a place that is not designed for long-lived secrets. It works for a single developer. It does not work for a team where several people need to upload to one repository with a token, and it does not work for any flow where the token would end up in a deployed configuration rather than in a developer's browser.

The third is the mismatch between the documentation and the project. The README's Node version requirement is years out of date compared to the manifest's engines field, the package manager is documented as npm or yarn while the lockfile is pnpm, and the project itself is at version 0.0.0 in the manifest with private set, so it is not published as a package and is expected to be forked or deployed from source. The live demo at imgurl-drab.vercel.app and the main site at wishmelz.github.io/imgurl are the intended distribution path, which is to say, you use the hosted instance or you build it yourself.

The alternative approach is a purpose-built image host, either a managed service or your own bucket behind a CDN. The difference in approach is that a real object store gives you image-specific behaviour, resizing, transformations, signed URLs and a content delivery network you control, and it has none of the GitHub permission model attached. For a blog with a hundred images, imgurl is faster to set up and costs nothing. For a product where images are part of the product, the GitHub release model is the wrong substrate and a bucket is the right one.

Editorial conclusion

Adopt imgurl if you need somewhere to put images for a small project and would rather not run an object store, because the storage is a GitHub release and the delivery is jsDelivr's CDN, so there is no server to pay for. Do not adopt it for anything that needs to scale past a hobbyist's traffic or that must survive a mistake, since a token with repo and user scope controls the repository and deleting a release asset removes the image from the CDN. Verify first that your GitHub plan allows the release assets and CDN access this needs, that the repository you point it at is one you are willing to have a personal access token with write access to, that the token is stored only in the browser the README describes rather than in a build artefact you publish, and that the Node version you use matches the package.json engines field, which asks for Node 22.18 or newer even though the README's environment section says Node 14.0 or higher.

Frequently asked questions

How does imgurl store and serve images?

Images are uploaded through the GitHub API as assets of a GitHub release in a repository you create, and they are served through the jsDelivr CDN. The repository acts as the storage, the release holds the files, and the CDN provides the delivery, so there is no server component.

What GitHub permissions does imgurl need?

The configuration guide asks for a personal access token with the repo permission for repository access and the user permission for user information access. You set a token name, choose an expiry, and generate it, and the README warns that the token is shown only once.

How do I run imgurl locally?

Install dependencies with npm install, then start the development server with npm run dev, and build the production version with npm run build. The manifest declares pnpm as the package manager at version 10.33.0 and the repository ships a pnpm-lock.yaml.

What does the imgurl interface offer?

You drag images onto the upload area or pick them with a button, and batch upload is supported. Once uploaded, you can view a list of the images, copy the link in one click, and choose between link formats including Markdown, HTML and a direct link. The interface is responsive for desktop and mobile.

What stack is imgurl built on?

It is a Vue 3 and TypeScript single-page app built with Vite, Tailwind 4 and reka-ui components, with Pinia and its persistedstate plugin for state, vue-router for routing, axios for the GitHub API calls, vue-sonner for toasts and @lucide/vue for icons.

Can the repository used for imgurl storage be private?

The configuration steps say to create a new repository that can be private or public, and then create a release in it to hold the uploads. The README notes that the token is shown only once at generation and must be saved.

Official sources

  1. Issues
  2. License: MIT
  3. Project website
  4. README
  5. WishMelz/imgurl on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/wishmelz-imgurl.svg)](https://hysenlabs.com/projects/wishmelz-imgurl)

Was this useful?