Self-hosted service
VERT-sh/VERT avatar
VERT-sh/VERT

VERT: a WebAssembly file converter whose local promise ends at video and at its own defaults

The next-generation file converter. Open source, fully local* and free forever.

16,253 stars867 forksSvelteAGPL-3.0

At a glance

What is it?
VERT-sh/VERT is an AGPL-3.0 Svelte app that converts images, audio and documents in the browser with WebAssembly. Video can fall back to a server daemon, and the self-hosted build points at the official vertd, Plausible and Stripe endpoints unless you change build arguments before the image is built.
Who is it for?
VERT suits anyone who wants image, audio and document conversion in the browser without uploading files, and self-hosters willing to rebuild the image with their own settings. It does not deliver fully local video conversion out of the box: that needs your own vertd or PUB_DISABLE_ALL_EXTERNAL_REQUESTS set to true.
Can I use it commercially?
Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Svelte, according to GitHub's language statistics.

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

From daily snapshots

Star history

  • VERT-sh/VERT16,253 ★ · +628
Star history
Repository2026-09-212026-11-14
VERT-sh/VERT15.6k16.3k

628 Stars in the last 54 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Conversion runs in the browser through ffmpeg and ImageMagick WebAssembly builds

VERT converts files on your device with WebAssembly instead of sending them to a cloud service. The dependency list shows where that work happens: @ffmpeg/core and @ffmpeg/core-mt pinned at 0.12.10, @ffmpeg/ffmpeg ^0.12.15, @ffmpeg/util ^0.12.2, and @imagemagick/magick-wasm ^0.0.44. The vite configuration pulls in vite-plugin-wasm and vite-plugin-top-level-await to load those modules.

The feature claims are short: images, audio, documents and video; more than 250 file formats; conversion settings; and no file or file size limits. The app is written in Svelte and TypeScript, built with SvelteKit and @sveltejs/adapter-static, so the output is a static site. That design means the browser tab does the heavy lifting, and conversion speed and memory depend on the device opening the page rather than on any server you run. A live instance is hosted at vert.sh.

Video conversion can leave your device by default

The asterisk in fully local* matters. Local video conversion in the browser is supported, but some conversions require server-side processing, and that is handled by a separate daemon called vertd. In both docker-compose.yml and .env.example the daemon address defaults to the official instance:

code
PUB_VERTD_URL=https://vertd.vert.sh

The consequence for a privacy-minded user: on the hosted site or an unmodified self-hosted build, a video that needs server processing is sent to vertd.vert.sh. Keeping video on your own infrastructure means self-hosting vertd from the VERT-sh/vertd repository and pointing PUB_VERTD_URL at it, with docs/VIDEO_CONVERSION.md covering that path. Images, audio and documents do not depend on the daemon.

PUB_DISABLE_ALL_EXTERNAL_REQUESTS is off unless you turn it on

VERT makes three kinds of outbound requests: to vertd, to Stripe for donations, and to Plausible for analytics. One switch disables all of them:

code
PUB_DISABLE_ALL_EXTERNAL_REQUESTS=false

The .env.example comment calls it useful for privacy-focused deployments or air-gapped environments, but the default in both .env.example and docker-compose.yml is false. Plausible analytics is off in compose because PUB_PLAUSIBLE_URL defaults to empty, and PUB_HOSTNAME (localhost:5173 by default) is only used for that analytics tracking. The donation settings, PUB_DONATION_URL=https://donations.vert.sh and a live Stripe publishable key, ship as defaults with a request to keep them unchanged because they fund development. An administrator who wants a deployment with no third-party calls has to flip the switch deliberately; leaving defaults in place keeps the official vertd and Stripe endpoints in play.

Settings are baked in at build time, not read at container start

The Dockerfile has two stages. The builder stage, based on oven/bun, declares every PUB_ variable as an ARG, copies it into ENV, runs bun install and then bun run build. The final stage is nginx:stable-alpine, which receives only the built files in /usr/share/nginx/html and a config from nginx/default.conf. None of the PUB_ variables are set in that final stage.

In docker-compose.yml those values are passed under build.args, and the service also names image: ghcr.io/vert-sh/vert:latest. The practical effect: changing PUB_VERTD_URL or PUB_DISABLE_ALL_EXTERNAL_REQUESTS in your environment and restarting the container does nothing to an already built image. You need to rebuild. If you pull the prebuilt ghcr.io image instead of building, you get whatever values it was built with. The container listens on port 80 and compose maps it to ${PORT:-3000}, with restart: unless-stopped and a curl-based health check every 30 seconds.

Failure blocks need HTTPS, and the two config files disagree on the default

PUB_DISABLE_FAILURE_BLOCKS controls whether VERT temporarily blocks video conversions of an uploaded file after repeated failures within an hour. The block relies on calculating file hashes of videos, which requires a secure context (HTTPS). The .env.example comment says setting it to false, which enables blocking, is only recommended for production deployments where HTTPS is available, and that file sets it to true.

The docker-compose.yml default is the opposite: PUB_DISABLE_FAILURE_BLOCKS defaults to false, and the Dockerfile ARG also defaults to false. Someone who deploys with compose behind plain HTTP gets the blocking feature switched on in a context where the hashing it needs is unavailable. If your instance is not served over HTTPS, set PUB_DISABLE_FAILURE_BLOCKS=true at build time.

No releases and a package version of 0.0.1

The repository has no GitHub releases, and package.json still reads "version": "0.0.1". The last push to main was on 2026-10-20. For self-hosters this means there is no tagged version to pin: you build from a commit on main or pull the latest tag of the container image, and upgrading means taking whatever main contains at that point. Record the commit you deployed so you can rebuild the same thing later.

The license is AGPL-3.0, which matters if you modify VERT and expose it to others over a network. Contributors are asked to read CONTRIBUTING.md before opening an issue or pull request. The repository also carries flake.nix and flake.lock, a bun.lock, and translation tooling through Paraglide: the build script runs paraglide-js compile against ./project.inlang into ./src/lib/paraglide before vite build, with message files in messages/.

Building from source runs through Bun and Paraglide

The scripts in package.json are the reference for local work. dev runs vite dev, build compiles translations and then runs vite build, preview runs vite preview, and check runs svelte-kit sync followed by svelte-check against tsconfig.json. format runs prettier and lint runs eslint. A migrate:messages script calls scripts/migrate-language-keys.mjs for translation key changes.

The toolchain is current: svelte ^5.57.2, @sveltejs/kit ^3.1.0, vite ^8.3.4, typescript ^6.0.3, and tailwindcss ^3.4.19. Setup steps live in docs/GETTING_STARTED.md and container instructions in docs/DOCKER.md, with an FAQ in docs/FAQ.md. Because the Docker build uses bun install and the repository ships bun.lock, Bun is the package manager the project builds with.

Editorial conclusion

VERT suits anyone who wants image, audio and document conversion in the browser without uploading files, and self-hosters willing to rebuild the image with their own settings. It does not deliver fully local video conversion out of the box: that needs your own vertd or PUB_DISABLE_ALL_EXTERNAL_REQUESTS set to true. Before deploying, decide on PUB_VERTD_URL, PUB_PLAUSIBLE_URL and PUB_DISABLE_FAILURE_BLOCKS, and build the image yourself rather than pulling ghcr.io/vert-sh/vert:latest if any of them differ from the defaults.

Frequently asked questions

Is the VERT converter app safe for private files?

VERT converts images, audio and documents on your device with WebAssembly. Some video conversions use the vertd daemon, which defaults to the official vertd.vert.sh instance unless you self-host vertd or set PUB_DISABLE_ALL_EXTERNAL_REQUESTS to true.

Is VERT a free video converter?

VERT is free and licensed AGPL-3.0. In-browser video conversion is supported, but some conversions need server-side processing through vertd, which you can self-host.

What kind of file converter program is VERT?

VERT is a Svelte and TypeScript web app that converts images, audio, documents and video across more than 250 formats with no file size limits, running in the browser through WebAssembly.

Official sources

  1. Issues
  2. License: AGPL-3.0
  3. Project website
  4. README
  5. VERT-sh/VERT 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/vert-sh-vert.svg)](https://hysenlabs.com/projects/vert-sh-vert)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/vert-sh-vert.svg)](https://hysenlabs.com/projects/vert-sh-vert)

Was this useful?