Step through what Renovate actually does with your config: parsing, migration of deprecated options, massaging, validation, preset resolution and merging. It runs Renovate's own code in your browser. Think "compiler explorer for Renovate configs".
Try it live, or run it yourself:
docker run -p 8080:80 ghcr.io/secustor/renovate-config-debugger # http://localhost:8080Paste this into the config editor and press Run pipeline or simply open it with the below content filled:
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"masterIssue": true,
"packageRules": [
{ "matchManagers": ["npm"], "matchUpdateTypes": ["minor"], "groupName": "npm minor" }
]
}Migration rewrites the deprecated masterIssue into dependencyDashboard,
extends explodes into the preset resolution tree, and the simulator tells you
which updates that packageRules entry actually matches.
- The pipeline stage by stage, as a structured trace with before/after snapshots, JSON-patch deltas and Renovate's own validation messages.
- A preset tree that survives
config:recommended. Its ~1,100 presets stay legible: a summary header shows the honest cost (a handful change top-level options, the rest contribute grouping packageRules), and there are roll-ups, search, a flat table and a "hide zero-contribution routers" toggle. - A packageRules simulator. Describe a hypothetical update and it shows which entries match, rule by rule and clause by clause, using Renovate's real matcher code, plus the per-dependency config those rules merge to.
- Per-key provenance, so you can see which layer set each key, including the self-hosted global and inherited layers below.
- Share links. Copy link reopens the current analysis (config, format, platform context, layers, view) from the URL fragment, so it never reaches a server log. It warns if the Renovate version has drifted, and it never carries tokens or manually injected presets.
- Load from repo. Give it
owner/repo, a full URL orgit@host:org/repo.gitwith an optional ref: it probes Renovate's documented config-file locations, says which file won, and sets the platform context for known hosts. It also offers (on by default) to bring the org's inherited config along, resolved the way a realinheritConfigrun resolves it, fromorg-inherited-config.jsonin{{parentOrg}}/renovate-config. Both are editable before you load.
Global + inherited config layers (self-hosted admins)
Paste a global config (the JSON form of config.js / env / CLI) and an
inherited config (inheritConfig, or let a repo load fetch it) alongside the repo config, and the
pipeline models the full stack as two extra timeline stages with matching
provenance badges: defaults → globalExtends presets → global config →
inherited config (validated with Renovate's inherit rules, presets resolved,
global-only options stripped) → repo presets → repo config. Repo configs setting
global-only options get Renovate's own boundary warning, and platform /
endpoint from the global config drive the platform-context control, so
overriding them is explicit and visibly warned.
Reading a private config or preset repo takes two steps, and the second one is easy to miss: sign in with GitHub, then install the App on the repositories it should read. Signing in by itself grants nothing, so a private repo keeps coming back as "not found" until the App is installed on it. Public repos need neither step.
That split is the point. It is what lets you decide, repository by repository, what the debugger can read, and the selection stays editable afterwards. The App asks for a single permission, Contents: read-only. Inside an organization, a member may need an owner to approve the install.
docs/GitHub-App-Access.md has the walkthrough, including owner approval, changing the selection later, revoking, and the personal-access-token fallback for GitHub Enterprise Server.
Warning
Docker setups are experimental at the moment.
The app is a static bundle, so hosting it is the one container above. There is
also docker-compose.yml, a worked example of both
services with every optional variable present but commented out:
docker compose up # published images
docker compose up --build # build from this checkout insteadThe two services are the app image above and the optional
ghcr.io/secustor/renovate-config-debugger-oauth-proxy (token exchange, Node,
port 8788). Both are configured at run time, not build time, so one image serves
an OAuth-off and an OAuth-on deployment. With both required variables set the
container writes /rcv-config.js at startup and the sign-in UI appears;
otherwise the shipped stub stays and the feature is off.
| Variable | Required for sign-in | Notes |
|---|---|---|
RCV_GITHUB_CLIENT_ID |
yes | Client id of your own GitHub App (public value). |
RCV_OAUTH_WORKER_URL |
yes | Base URL of the token-exchange proxy as the browser reaches it. |
RCV_GITHUB_APP_SLUG |
no | The App's slug; enables a direct "install on repositories" link. |
RCV_GA_MEASUREMENT_ID |
no | GA4 measurement id (G-…); enables Google Analytics. Off unset. |
Sign in with GitHub, self-hosted
Sign-in cannot ship turned on: the callback URL, consent screen and client
secret all belong to your deployment. You provision two things, both covered
step by step in
packages/oauth-worker/README.md:
- A GitHub App you own, with
Contents: read-onlyand your callback URL. - The token-exchange proxy, because a static page cannot hold the
client_secretGitHub still requires. Deploy the Cloudflare Worker, or run the-oauth-proxyimage. Both run identical code (the handler is a pure function; the image runs it under Node). It readsGITHUB_CLIENT_ID,GITHUB_CLIENT_SECRETandALLOWED_ORIGINS(comma-separated exact origins, meaning the origin you serve the app from; anything else is refused with403before GitHub is contacted).
Self-hosting does not move the privacy boundary. The proxy only ever sees the
code → token / refresh_token → token exchange, never a config, a preset or
an API request. It keeps no state and logs no bodies or tokens, and every
content fetch still goes browser → api.github.com.
Building the images locally
docker build --target app -t rcv-app . # the app image
docker build --target oauth-proxy -t rcv-oauth-proxy . # the proxy imageThe build context is the repo root. TLS termination and reverse-proxy setup are deliberately out of scope. Put the app image behind whatever you already run.
mise install # node + pnpm (or use your own, see package.json engines)
pnpm install
pnpm dev # dev server
pnpm test # every workspace test except e2e (which needs a build first)
pnpm typecheck
pnpm lint && pnpm format:checkdocs/Architecture.md covers how it all works: the shim plugin, the golden tests, the pinned Renovate.
Privacy, tokens & GitHub sign-in
- Configs never leave the browser except for the preset fetches they themselves declare; all GitHub/GitLab/Gitea/Forgejo/npm content fetches go browser → host API directly, with nothing proxying your config or presets.
- Tokens (OAuth or personal access token) live in
sessionStorage/memory and are cleared when the tab closes. They never go intolocalStorageor into a URL. - Sign in with GitHub adds exactly one piece of server infrastructure: the
stateless
packages/oauth-worker, which does nothing but the OAuthcode → token/refresh_token → tokenexchange, because a static site cannot hold theclient_secretGitHub still requires. It never sees a config, a preset, or an API request. - Private presets and private repo configs need auth. The GitHub App's only permission is Contents: read-only, so the consent screen truthfully reads "read the contents of the repositories you select"; signing in also raises the rate limit from 60 to 5,000 requests/hour. Sign-out clears the local token, and the chip links to GitHub's authorization page for true revocation. Granting the App access to a given repository is a separate step, covered in docs/GitHub-App-Access.md.
- Sign-in is off by default. It turns on only when the deploy provides
VITE_GITHUB_CLIENT_IDandVITE_OAUTH_WORKER_URL(plus optionalVITE_GITHUB_APP_SLUG) or theirRCV_*equivalents. Otherwise a personal access token under Platform context & per-host tokens is the only GitHub auth, and it is also the fallback for GitHub Enterprise Server, orgs that can't approve the app install, or Worker outages.
Preset hosting & CORS support
Every fetcher runs in the page, so each host must serve CORS headers. The public default endpoints below verifiably do; self-hosted endpoints usually do not, so their presets fall back to manual injection.
| Prefix | Status | Notes |
|---|---|---|
github> |
fetched in browser | api.github.com (custom endpoint supported) |
gitlab> |
fetched in browser | gitlab.com API v4 (custom endpoint supported) |
gitea> |
fetched in browser | gitea.com API v1 (custom endpoint supported) |
forgejo> |
fetched in browser | codeberg.org API v1 (custom endpoint supported) |
npm> |
fetched in browser | registry.npmjs.org (deprecated upstream) |
bare owner/repo, local> |
via platform context | resolves against the toolbar platform + endpoint you select |
http(s)://… |
manual only | arbitrary endpoints rarely serve CORS |
azure / bitbucket / bitbucket-server / gerrit (via local>) |
not supported | reachable only via a real Renovate run |
codecommit / scm-manager (via local>) |
not supported | Renovate itself does not serve local presets there |
local> and bare owner/repo are not hosts of their own. They resolve against
the platform + endpoint picked in the toolbar's Platform context control
(default github / https://api.github.com), and the trace records which one
each node used.
Any preset a fetcher cannot reach (self-hosted or air-gapped hosts, a
hypothetical preset) can be supplied by hand. Select the failed node in the
resolution tree and paste its JSON into "Provide preset content manually". The
pipeline re-runs with it and flags the node user-supplied.
See roadmap/ for planned features.
