Makes & models The data Free sample Pricing Guides Help Sign in My account & downloads Buy the dataset - $149
Light / dark
Developers · 8 min read

How to build a year, make, model dropdown for Australian cars

Build a cascading year, make, model and variant dropdown for Australian cars with SQL Server queries, vanilla JavaScript, indexing and NVIC capture.

Updated

Almost every Australian website that asks about a car - insurance quotes, finance applications, classifieds, workshop bookings, parts shops - uses the same pattern: pick a year, then a make, then a model, then a variant. Each list depends on the one before it, so users only ever see valid options. This guide shows how to build that cascading selector on top of the CarsData dataset in SQL Server, with a small vanilla JavaScript front end, and how to store the result properly so it stays useful later.

The pattern

The selector walks down from broad to specific:

  1. Year - every year that has vehicles.
  2. Make - makes that sold vehicles in the chosen year.
  3. Model - models for that year and make.
  4. Variant - the exact versions for that year, make and model, each with its own NVIC.

Year comes first because it filters the other lists the most and because people know it with confidence. Some sites put make first instead; the queries below work either way with the order of the filters swapped.

The columns you need

The SQL download creates a table called dbo.CarsData_Vehicles. For the selector you only need a handful of its columns:

ColumnUsed for
YearFirst dropdown
MakeSecond dropdown
ModelThird dropdown
VariantMain label in the final dropdown
SeriesHelps tell similar variants apart
NVICThe value you store - identifies the exact vehicle version

Columns such as Body, Transmission, EngineCC and Fuel are useful extras for the variant label. The data dictionary describes every column.

SQL Server queries

Each step is one simple query. Always use parameters rather than string concatenation.

1. Years

SELECT DISTINCT Year
FROM dbo.CarsData_Vehicles
ORDER BY Year DESC;

Newest first suits most forms, because most people are describing a recent car.

2. Makes for a year

SELECT DISTINCT Make
FROM dbo.CarsData_Vehicles
WHERE Year = @Year
ORDER BY Make;

3. Models for a year and make

SELECT DISTINCT Model
FROM dbo.CarsData_Vehicles
WHERE Year = @Year AND Make = @Make
ORDER BY Model;

4. Variants for a year, make and model

SELECT NVIC, Variant, Series, Body, Transmission, EngineCC, Fuel
FROM dbo.CarsData_Vehicles
WHERE Year = @Year AND Make = @Make AND Model = @Model
ORDER BY Variant, Series, Body, Transmission;

Unlike the earlier steps, this one returns whole rows, because each variant is a separate vehicle with its own NVIC.

Making variant labels readable

Variant names alone can repeat - the same trim might be sold as a sedan and a hatch, or with manual and automatic transmissions. Build a label from several columns so every option is distinct:

SELECT NVIC,
       CONCAT(Variant,
              CASE WHEN Series <> '' THEN ' ' + Series ELSE '' END,
              ' - ', Body, ' ', Transmission,
              CASE WHEN EngineCC > 0 THEN CONCAT(' ', EngineCC, 'cc') ELSE '' END,
              CASE WHEN Fuel <> '' THEN ' ' + Fuel ELSE '' END) AS Label
FROM dbo.CarsData_Vehicles
WHERE Year = @Year AND Make = @Make AND Model = @Model
ORDER BY Label;

Transmission and body use short codes (for example A for automatic, H/BAC for hatchback). You may want to map them to words in your application - see body types, transmission and drive codes explained.

Indexing

The table has a primary key on (NVIC, Year), which is ideal for looking up a stored selection but does not help the dropdown queries. Add an index that matches how you filter:

CREATE INDEX IX_CarsData_Vehicles_Make_Model_Year
    ON dbo.CarsData_Vehicles (Make, Model, Year)
    INCLUDE (Variant, Series, Body, Transmission, EngineCC, Fuel);

CREATE INDEX IX_CarsData_Vehicles_Year_Make
    ON dbo.CarsData_Vehicles (Year, Make);

The first index covers the model and variant lookups (and make-first selectors). The second makes "makes for a year" cheap. With roughly 133,279 rows the table is small by database standards, but these indexes keep each request to a quick seek rather than a scan.

A minimal API

Expose each step as a small endpoint that returns JSON. The exact framework doesn't matter; the shape might be:

GET /api/vehicles/years                                  -> [2026, 2025, 2024, ...]
GET /api/vehicles/makes?year=2024                        -> ["Abarth", "Alfa Romeo", ...]
GET /api/vehicles/models?year=2024&make=Mazda           -> ["BT-50", "CX-3", "CX-5", ...]
GET /api/vehicles/variants?year=2024&make=Mazda&model=CX-5
    -> [{"nvic": "...", "label": "Maxx Sport - WAGON A 2488cc PETROL"}, ...]

Return one level at a time. This keeps responses small and fast, and it keeps you within the data licence, which allows showing individual records to users but not exposing the whole dataset through an open API.

Vanilla JavaScript front end

Four select elements and a little script are all you need:

<select id="year"></select>
<select id="make" disabled></select>
<select id="model" disabled></select>
<select id="variant" name="nvic" disabled></select>
const el = id => document.getElementById(id);
const year = el('year'), make = el('make'), model = el('model'), variant = el('variant');

async function getJson(url) {
  const res = await fetch(url);
  if (!res.ok) throw new Error(res.status + ' ' + url);
  return res.json();
}

function fill(select, items, placeholder) {
  select.replaceChildren(new Option(placeholder, ''));
  for (const item of items) {
    const opt = typeof item === 'object'
      ? new Option(item.label, item.nvic)
      : new Option(item, item);
    select.add(opt);
  }
  select.disabled = items.length === 0;
}

function reset(...selects) {
  for (const s of selects) { s.replaceChildren(); s.disabled = true; }
}

year.addEventListener('change', async () => {
  reset(make, model, variant);
  if (!year.value) return;
  fill(make, await getJson(`/api/vehicles/makes?year=${year.value}`), 'Make');
});

make.addEventListener('change', async () => {
  reset(model, variant);
  if (!make.value) return;
  const q = new URLSearchParams({ year: year.value, make: make.value });
  fill(model, await getJson(`/api/vehicles/models?${q}`), 'Model');
});

model.addEventListener('change', async () => {
  reset(variant);
  if (!model.value) return;
  const q = new URLSearchParams({ year: year.value, make: make.value, model: model.value });
  fill(variant, await getJson(`/api/vehicles/variants?${q}`), 'Variant');
});

getJson('/api/vehicles/years').then(years => fill(year, years, 'Year'));

Using new Option() rather than building HTML strings means make, model and variant names are always treated as text, so there is no risk of markup injection. URLSearchParams takes care of encoding names that contain spaces, hyphens or ampersands.

Caching

The dataset only changes when you load a new release, so the dropdown data is ideal for caching:

  • Server side - cache each query result in memory, keyed by its parameters. Clear the cache when you import a release.
  • HTTP - send a Cache-Control header (for example public, max-age=86400) so browsers and CDNs can reuse responses.
  • Pre-built files - for a fully static site, you could generate one small JSON file per year and make at build time. Keep each file to a single level of the selection, in line with the licence.

The years list rarely changes at all, so you can also render it straight into the page and save a request.

Store the NVIC, not just the names

When the user submits the form, save the NVIC and Year alongside the text they saw. The names are useful for display and for searching your own records; the NVIC and Year let you join back to the vehicle table at any time:

CREATE TABLE dbo.Quotes (
    QuoteID      int IDENTITY PRIMARY KEY,
    VehicleNVIC  varchar(10)   NOT NULL,
    VehicleYear  smallint      NOT NULL,
    VehicleLabel nvarchar(300) NOT NULL,  -- what the user saw, kept for the record
    CreatedAt    datetime2     NOT NULL DEFAULT SYSUTCDATETIME()
);

Later, a join on NVIC and Year returns full specifications and the latest indicative values, and keeps working after you load a new release. Because the variant list can include several versions with similar names, never try to work out which vehicle a user meant from its label alone.

Tip: add an "I'm not sure" option at the variant step. Store the year, make and model, and let staff or the user pick the exact variant later. A guess at the variant is worse than an honest blank.

Get the data

CarsData covers 125 makes and 1,543 models from 1990 to 2026, and the SQL download creates dbo.CarsData_Vehicles ready for the queries above. Try the free sample, check the data dictionary, browse every make, or get the full dataset for $149 once.

Related guides: loading the data into SQL Server, MySQL, PostgreSQL and Python and what an NVIC code is.

Keep reading

Ready when you are

The whole Australian market. One download.

133,279 variants with NVIC codes, specs and values - CSV, Excel, SQL and JSON - for one payment of $149 AUD. Updates every 6 months included.

Free sample first