A clean, fast, theme-aware browser for any VRChat Package Manager repository.
VPMList fetches a VPM repository manifest, normalizes it, and renders the contained packages in a searchable, sortable, sharable single-page app — no backend, no build step per repository.
Point it at any public VPM listing URL (index.json) and it just works.
- Single-listing or multi-listing mode — by default the site is locked to the repository you bake in via
VITE_DEFAULT_REPO_URL. FlipVITE_ALLOW_URL_CHANGE=trueand visitors can paste any VPMindex.jsonURL. - Shareable URLs (when URL changes are enabled) — the active repo is reflected in the route hash (
#/?url=...), so any link you copy loads the same listing on the other side. - One-click "Add to VCC" — generates the
vcc://vpm/addRepo?url=...protocol link the VRChat Creator Companion expects. - Per-package details — pick any version, see dependencies / VPM dependencies / keywords / license / Unity version / changelog / docs links.
- Searchable, sortable, paginated package table powered by PrimeVue's
DataTable. - Light / dark / system theme with
prefers-color-schemefollow-mode and a manual toggle, persisted tolocalStorage. - English / 日本語 UI via
vue-i18n, auto-detected from the browser language and persisted. - Strict TypeScript, Vitest unit tests, ESLint + Prettier.
| Layer | Library |
|---|---|
| Build | Vite 7 + @vitejs/plugin-vue |
| Framework | Vue 3 (Composition API, <script setup lang="ts">) |
| Language | TypeScript (strict) |
| UI | PrimeVue 4 — Aura preset from @primeuix/themes, auto-imported via unplugin-vue-components |
| Styles | Tailwind CSS v4 + tailwindcss-primeui |
| State | Pinia 3 |
| Router | Vue Router 4 (hash history) |
| i18n | vue-i18n 10 |
| Tests | Vitest 4 + @vue/test-utils + happy-dom |
# 1. Install
npm install
# 2. (Optional) Set a default repository to load on first visit
cp .env.example .env
# edit .env → VITE_DEFAULT_REPO_URL=https://your-listing.example.com/index.json
# 3. Run the dev server
npm run dev # http://localhost:5173
# 4. Build for production
npm run build # → dist/
npm run preview # serve dist/ locally| Command | Purpose |
|---|---|
npm run dev |
Start the Vite dev server with HMR |
npm run build |
Type-check (vue-tsc) and build for production |
npm run preview |
Preview the production build |
npm run test |
Run Vitest once (CI mode) |
npm run test:watch |
Vitest in watch mode |
npm run lint |
ESLint over .ts / .vue |
npm run format |
Prettier write |
| Env var | Default | Description |
|---|---|---|
VITE_DEFAULT_REPO_URL |
(empty) | The repository URL displayed on first visit. When VITE_ALLOW_URL_CHANGE is false, this is the only URL the UI will ever load. |
VITE_ALLOW_URL_CHANGE |
false |
When set to true (or 1), visitors can edit the URL bar and load any other VPM repository manifest, and the #/?url=... shareable link is honored. When unset or false, the URL bar is read-only and locked to VITE_DEFAULT_REPO_URL. |
- Locked (default) — ship a single curated listing. The URL bar is
read-only, the "Load" button is hidden,
#/?url=...query parameters are ignored, and the only repository ever fetched isVITE_DEFAULT_REPO_URL. Use this when you publish only your own packages. - Open — set
VITE_ALLOW_URL_CHANGE=truein.env. The URL bar becomes editable, the active repo is mirrored toroute.query.url, and any visitor can paste a different VPM listing URL. Use this when running a generic VPM browser.
Note: The lock is a UX boundary, not a security one. The default URL is inlined into the bundle at build time, and a determined visitor can always craft their own fetch. Treat
VITE_ALLOW_URL_CHANGE=falseas "this site only shows our packages", not as a sandbox.
src/
├── main.ts # Vue / Pinia / Router / PrimeVue / i18n bootstrap
├── App.vue # Shell layout
├── style.css # Tailwind v4 + tailwindcss-primeui + primeicons
├── router/ # Hash-mode router
├── stores/
│ ├── repo.ts # Active repo: loading / error / data
│ └── settings.ts # Theme + locale (persisted to localStorage)
├── i18n/ # vue-i18n setup, en.ts / ja.ts message bundles
├── types/vpm.ts # VPM manifest TypeScript interfaces
├── services/vpmRepo.ts # fetch + validate + normalize a VPM repo
├── utils/
│ ├── semver.ts # Lightweight semver compare / pickLatest
│ ├── vcc.ts # Build vcc://vpm/addRepo URLs
│ └── clipboard.ts # navigator.clipboard wrapper with fallback
├── composables/
│ └── useTheme.ts # html.dark toggle + prefers-color-scheme
├── views/HomeView.vue # Page composition
└── components/
├── AppHeader.vue
├── ListingHeader.vue
├── AddToVccBar.vue
├── HelpDialog.vue
├── PackageList.vue
└── PackageInfoDialog.vue
tests/unit/ # Vitest specs (utils / services / stores / components)
npm run testThe test suite covers:
utils/semver— comparator, sort order, prerelease handlingutils/vcc—vcc://URL encodingservices/vpmRepo— fetch / validation / normalization (withvi.fnfetch)stores/repo— Pinia action transitions on success / failurecomponents/PackageList— search filter behavior
VPMList fetches the manifest directly from the browser, so the remote server must respond with permissive CORS headers. GitHub Pages-hosted VPM listings (the most common deployment) do this by default. If a CORS error occurs, the UI surfaces a hint and the failing URL — there is intentionally no proxy in the loop.
- VPM repository manifest spec — https://vcc.docs.vrchat.com/vpm/repos
- VPM package manifest spec — https://vcc.docs.vrchat.com/vpm/packages
A ready-to-go workflow lives at
.github/workflows/deploy-pages.yml.
On every push to main / master it lints, tests, builds with
BASE_PATH=/<repo-name>/, and publishes dist/ to GitHub Pages.
Before the first run:
- In your repo settings, enable Pages → Build and deployment → Source: GitHub Actions.
- (Optional) Define repository Variables under
Settings → Secrets and variables → Actions → Variables:
VITE_DEFAULT_REPO_URL— the listing the deployed site should display.VITE_ALLOW_URL_CHANGE— set totrueto let visitors browse other repositories.
- Push to
main. The workflow can also be triggered manually from the Actions tab.
The site will be available at https://<owner>.github.io/<repo>/.
Released under the GNU General Public License v3.0.
See LICENSE for the full text.