Build a Lightning-Fast, Filterable Car Inventory Page in HubSpot CMS
By Tomislaw Dalic
October 31, 2025
200+ cars. Real-time search. Dynamic IMAGIN images. Zero backend.
One HubL template. One JavaScript file. Done.
I built a leasing platform with 370+ vehicles — all filterable, searchable, and powered by IMAGIN.studio images. No Node.js. No database. Just HubDB + HubL + vanilla JS.
This is the complete blueprint — from CSV to live page.
The Full Architecture
CSV File (Car Data)
↓
HubDB (Structured Database)
↓
HubL Template (Server-Side Rendering)
↓
JavaScript (Client-Side Filtering)
↓
Beautiful Car Inventory Page
No external APIs. No React. No jQuery. Just HubSpot.
Step 1: The Perfect HubDB Schema
| Field | Type | Example | Why? |
|---|---|---|---|
hs_name |
Text | bmw-3-series-2024-001 |
Unique ID |
brand |
Text | BMW |
Filterable |
model |
Text | 3 Series Touring |
Full name |
body_style |
Text | Station Wagon |
Filter + IMAGIN variant |
fuel_type |
Text | Hybrid |
Filter + IMAGIN powerTrain |
price_per_month |
Number | 499 |
Sorting + range filter |
api_make |
Text | bmw |
IMAGIN-ready |
api_modelfamily |
Text | series-3 |
IMAGIN-ready |
Pro Tip: Keep api_* fields separate — your display name can be “BMW 3 Series Touring”, but IMAGIN needs series-3.
Step 2: Import CSV → HubDB (Automated)
const hubspot = require('@hubspot/api-client');
async function importCars(csvData) {
const client = new hubspot.Client({ accessToken: 'YOUR_TOKEN' });
const tableId = '12345';
for (const car of csvData) {
await client.cms.hubdb.rowsApi.createTableRow(tableId, {
path: car.slug,
name: car.hs_name,
values: { /* all fields */ }
});
}
await client.cms.hubdb.tablesApi.publishDraftTable(tableId);
console.log('Imported & published!');
}
Step 3: The HubL Template (Copy-Paste Ready)
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<div class="container-fluid py-5">
<div class="row">
<!-- Filters -->
<div class="col-lg-3">
<div class="filters-sidebar">
<h4>Filter Cars</h4>
<input type="text" id="searchInput" class="form-control" placeholder="Search...">
<select id="brandFilter" class="form-select mt-3">...</select>
<select id="fuelFilter" class="form-select mt-3">...</select>
<select id="bodyStyleFilter" class="form-select mt-3">...</select>
<input type="range" id="priceRange" class="form-range mt-3">
<button id="clearFilters" class="btn btn-outline-secondary w-100 mt-3">Clear</button>
</div>
</div>
<!-- Car Grid -->
<div class="col-lg-9">
<div id="carCardsContainer" class="row g-4">
</div>
</div>
</div>
</div>
Step 4: Client-Side Filtering (Vanilla JS)
<script>
document.addEventListener('DOMContentLoaded', () => {
const cards = document.querySelectorAll('.car-card');
const applyFilters = () => { /* full logic */ };
document.getElementById('searchInput').addEventListener('input', applyFilters);
// ... all event listeners
applyFilters();
});
</script>
Mobile-First UX
- Collapsible filters on mobile
- Touch-friendly range slider
- Lazy-loaded images
- URL sync for sharing filters
Performance Checklist
- Lazy load images
- Debounce search
- Batch DOM updates
- Cache filter state
- No jQuery
Final Result
- 200+ cars
- Real-time filters
- Dynamic IMAGIN images
- Mobile-ready
- Zero backend
Drop your HubDB schema in the comments — I’ll build your IMAGIN mapping for free.
Join the Conversation
Share your thoughts and connect with other readers