Building v-Core: one platform for many visa websites
How one backend and one admin panel run a group of branded visa websites, from the eligibility check to the government's decision.

Every visa website needs the same machinery: an eligibility check, prices, a long application form, document uploads, payments, and a team behind the scenes that reviews each case and submits it to the government. Building that again for every new website doesn't scale. v-Core is my answer: one platform, with each website as a configuration on top of it.
One backend, many websites
When a request comes in, v-Core looks at the hostname and works out which website it belongs to. From then on, everything is scoped to that site: its destinations, packages, prices, eligibility rules, application form, emails, languages, branding and payment account.
Some things are shared on purpose. Countries and visa types are defined once for every site, and a client who applies on two websites is still one client.
Adding a website doesn't need new code. You add the site in the admin panel, configure it, and point its domain at the server.

The life of an application
A traveler sees six steps: check eligibility, choose a package, fill in the form, upload documents, pay, and track the status. The form saves as you type and handles a whole family in one application. If you'd rather not type your passport details, scan a QR code with your phone, photograph the passport, and the form fills itself in.
Once an application is paid, the work moves to the admin panel. It's assigned to an agent automatically and moves through six stages: initial review, data collection, final check, manager approval, submission, and waiting for the result. The decision is recorded per traveler, because one family member can be approved while another is refused.

A clock that knows when to stop
Each package promises a turnaround time, so every case has an SLA clock. The clock is honest about whose turn it is. It pauses while the team waits for the traveler to send a missing document, and again while the government decides, so the clock only counts time the case was actually with the team.
The inbox is built around that clock. Tabs show cases at risk and cases already late, and a forecast shows how many submissions are due each day this week.

Money that never changes behind your back
The price a traveler sees is saved with the order at checkout and never recalculated, even if prices change later. Payment confirmations from Stripe are stored first and then processed by a background job that skips anything it has already handled, so a repeated notification can't confirm an order twice. Upgrades and downgrades are separate orders linked to the original, and the difference is charged or refunded through Stripe.
Content in many languages
Each website has its own pages, blog, and landing pages for destinations and nationalities, built with a block page builder. Pages can be translated in bulk by machine and then edited, and right-to-left languages work too.
How I built it
v-Core is a big codebase. Since the first commit in late January it has grown to more than 400 commits and over 100 tagged releases, with 124 data models, 188 database migrations and about 200 automated test files.
I built it with Claude as my pair. A set of long-lived documents describes the architecture, so every session starts with the same picture of the system. Big features start with an audit of the code they'll touch and a written plan, and only then the code.
What's next
A partner API is in progress, so approved systems can read the visa catalog, eligibility rules and document requirements with their own keys.
About the screenshots
The admin screenshots come from a copy of the admin panel fed with invented sample data. The websites, people, IDs and numbers are all made up, and no real customer or business data appears here.
- v-core
- nestjs
- nextjs
- multi-tenant