Glasses frame sizing is an unsolved problem in e-commerce. The standard online approach — a ruler image on screen, a virtual try-on photo filter — either requires calibration steps most shoppers abandon or produces a visual result that tells you nothing about physical fit. Frames that look good on a photo overlay can pinch the temple, sit crooked on the nose, or slip because the bridge is too wide.
Physical fit is a function of head and face geometry: how wide the head is at the temple, how broad the face is across the cheekbones, and how long the ear-to-nose path is. These are the dimensions that determine whether a frame stays in place. They’re also dimensions most people don’t know about themselves — but they’re predictable from height and weight with high accuracy using the HEAD_FACE bundle.
This guide builds a frame size recommender that works without a physical measurement and without a try-on. The result is not a photo overlay; it’s a size recommendation — “this frame will fit your face width” — which is both more honest and more useful.
Scope note: This guide covers frame fit (physical dimensions). It does not cover optical lens fitting, pupillary distance (PD) for prescription lenses, or progressive lens corridor optimisation — those require clinical measurement and are outside the scope of a body dimension API.
The four dimensions that govern frame fit
| Dimension | Frame parameter | How it governs fit |
|---|---|---|
head_breadth | Total frame width | Frame must not press the temples or leave visible gaps |
face_breadth | Lens bridge width + lens width | Proportional balance of frame to face width |
face_length | Lens height | Vertical proportion — tall frames on short faces look imposing |
head_length | Temple (arm) length | Temple must reach behind the ear comfortably |
These four are all in the HEAD_FACE bundle — a lighter, lower-cost call than FULL_BODY for eyewear-only use cases.
Step 1: Server endpoint
// glasses-sizing.js
const express = require('express');
const router = express.Router();
const API_URL = 'https://dimensionspot-bodysize-engine.p.rapidapi.com/v1/predict';
const RAPIDAPI_KEY = process.env.RAPIDAPI_KEY;
const cache = new Map();
// ── Frame sizing logic ───────────────────────────────────────────────────────
//
// Glasses frames use a three-number notation: lens width – bridge width – temple length
// Example: 52-18-145 means 52 mm lens, 18 mm bridge, 145 mm temples
//
// Total frame width ≈ (lens_width × 2) + bridge_width
// The frame should match head_breadth at the temple. A 5 mm range is normal tolerance.
function frameSizing(dims) {
const headBreadth = dims.head_breadth ?? 150; // mm — widest point of skull
const faceBreadth = dims.face_breadth ?? 140; // mm — cheekbone width
const faceLength = dims.face_length ?? 118; // mm — chin to brow bridge
const headLength = dims.head_length ?? 195; // mm — front to back of skull
// ── Total frame width recommendation ────────────────────────────────────────
// Frame width should match head_breadth within ±5 mm
// Frame width = lens_width × 2 + bridge
// Target frame_width range: headBreadth − 5 to headBreadth + 5
const frameWidthMin = headBreadth - 5;
const frameWidthMax = headBreadth + 5;
// ── Lens width estimate from face breadth ────────────────────────────────────
// Lens width typically = (face_breadth − bridge_width) / 2
// Typical bridge range: 14–22 mm. Mid bridge 18 mm is safe default.
const bridge = faceBreadth < 130 ? 14 : faceBreadth < 145 ? 16 : 18;
const lensWidth = Math.round((faceBreadth - bridge) / 2);
// ── Temple length recommendation ─────────────────────────────────────────────
// Temple (arm) length: distance from frame front to hinge + ear path
// Empirically: head_length × 0.95 ≈ total ear-path distance
// Standard temple lengths: 135, 140, 145, 150 mm
const rawTemple = Math.round(headLength * 0.95);
const TEMPLE_SIZES = [135, 140, 145, 150];
const templeLength = TEMPLE_SIZES.reduce((prev, curr) =>
Math.abs(curr - rawTemple) < Math.abs(prev - rawTemple) ? curr : prev
);
// ── Lens height / frame style guidance ──────────────────────────────────────
// face_length governs vertical proportion
// Short face (≤110 mm): narrow/shallow frames recommended
// Medium face (110–130 mm): standard
// Long face (>130 mm): taller or oversized frames maintain proportion
const faceVertical = faceLength <= 110 ? 'short'
: faceLength <= 130 ? 'medium'
: 'long';
const LENS_HEIGHT_GUIDANCE = {
short: { range: '28–34 mm', note: 'Avoid deep/oversized lenses — they visually elongate a shorter face.' },
medium: { range: '34–40 mm', note: 'Standard lens height works well. Round and square shapes both suit medium face length.' },
long: { range: '40–48 mm', note: 'Taller lenses balance a longer face. Consider oversized or statement frames.' },
};
// ── Frame width category ─────────────────────────────────────────────────────
const FRAME_WIDTH_LABELS = [
{ label: 'Narrow', maxMm: 128 },
{ label: 'Medium', maxMm: 138 },
{ label: 'Wide', maxMm: 148 },
{ label: 'Extra Wide', maxMm: Infinity },
];
const frameCategory = FRAME_WIDTH_LABELS.find(f => headBreadth <= f.maxMm)?.label ?? 'Extra Wide';
return {
frame_width: {
category: frameCategory,
min_mm: frameWidthMin,
max_mm: frameWidthMax,
note: 'Filter frames by total frame width (lens × 2 + bridge) within this range.',
},
suggested_lens_width_mm: lensWidth,
suggested_bridge_mm: bridge,
temple_length_mm: templeLength,
lens_height: LENS_HEIGHT_GUIDANCE[faceVertical],
face_dimensions: {
head_breadth_mm: headBreadth,
face_breadth_mm: faceBreadth,
face_length_mm: faceLength,
},
example_notation: `${lensWidth}-${bridge}-${templeLength}`,
};
}
// ── Main endpoint ─────────────────────────────────────────────────────────────
router.post('/glasses', async (req, res) => {
const { gender, height_cm, weight_kg, region } = req.body;
if (!gender || !height_cm || !weight_kg) {
return res.status(400).json({ error: 'gender, height_cm, and weight_kg are required' });
}
const heightMm = Math.round(parseFloat(height_cm) * 10);
const weightKg = parseFloat(weight_kg);
const targetRegion = region ?? 'GLOBAL';
const cacheKey = `${gender}-${heightMm}-${weightKg}-${targetRegion}`;
if (!cache.has(cacheKey)) {
const apiRes = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-rapidapi-key': RAPIDAPI_KEY,
'x-rapidapi-host': 'dimensionspot-bodysize-engine.p.rapidapi.com',
},
body: JSON.stringify({
input_data: {
input_unit_system: 'metric',
subject: { gender, input_origin_region: targetRegion },
anchors: { body_height: heightMm, body_mass: weightKg },
},
output_settings: {
calculation: { calculation_model: 'AUTO', target_region: targetRegion, body_build_type: 'CIVILIAN' },
requested_dimensions: { bundle: 'HEAD_FACE' },
output_format: { unit_system: 'metric', confidence_score_threshold: 0, include_range_95: false, include_iso_codes: false },
},
}),
});
if (!apiRes.ok) return res.status(502).json({ error: 'Prediction service unavailable' });
const data = await apiRes.json();
cache.set(cacheKey, Object.fromEntries(
Object.entries(data.body_dimensions).map(([key, d]) => [key, d.value])
));
}
const dims = cache.get(cacheKey);
res.json(frameSizing(dims));
});
module.exports = router;
Step 2: Product catalogue integration
Use the frame width range to filter your product catalogue before presenting results:
// Filter product catalogue by frame width compatibility
function filterFrames(products, sizing) {
const { min_mm, max_mm } = sizing.frame_width;
return products
.filter(p => {
// Products should have lens_width and bridge_width attributes
const totalWidth = (p.lens_width_mm * 2) + p.bridge_mm;
return totalWidth >= min_mm && totalWidth <= max_mm;
})
.map(p => ({
...p,
fit_note: p.temple_length_mm === sizing.temple_length_mm
? 'Temple length matches'
: `Temple: ${p.temple_length_mm}mm (recommended: ${sizing.temple_length_mm}mm)`,
}))
.sort((a, b) => {
// Rank by how close temple length is to recommended
const aTempleDiff = Math.abs(a.temple_length_mm - sizing.temple_length_mm);
const bTempleDiff = Math.abs(b.temple_length_mm - sizing.temple_length_mm);
return aTempleDiff - bTempleDiff;
});
}
Step 3: Presenting the recommendation to shoppers
Rather than showing raw numbers, translate the recommendation into guidance shoppers can act on:
Your face profile
─────────────────────────────────────
Face width: Medium (≈ 143 mm)
Face length: Medium
Frame recommendations
─────────────────────────────────────
Frame width: 135–145 mm total
→ Look for frames marked "Medium" or check: lens × 2 + bridge
→ Example: a 50-15-145 frame = 115 mm total ✗ (too narrow)
a 54-18-145 frame = 126 mm total ✓
Temple length: 145 mm
→ Most standard frames ship at 140 or 145 mm. Either works.
Lens height: 34–40 mm
→ Standard depth. Round, rectangle, and oval shapes all suit your
face proportions.
The frame notation (54-18-145) is marked on the inside of every glasses temple. Showing shoppers how to read it turns an abstract recommendation into something they can verify at the optician or when the frames arrive.
How the HEAD_FACE bundle differs from FULL_BODY
For eyewear-only applications, request HEAD_FACE instead of FULL_BODY:
"requested_dimensions": { "bundle": "HEAD_FACE" }
The HEAD_FACE bundle returns the head and face dimensions without computing lower body measurements. The API call is the same; the bundle parameter controls which dimensions are calculated and returned. For a pure eyewear product category, this is the appropriate scope.
What this system does and doesn’t replace
What it does: Narrows frame selection from hundreds of options to a handful that will physically fit. This meaningfully reduces returns from frame-fit issues and reduces decision fatigue.
What it doesn’t do: It does not replace a full optical fitting at an optician. Optical fitting involves PD measurement, vertex distance, pantoscopic tilt, and prescription-specific adjustments — none of which are in scope for a frame selection tool. The recommendation here is for frame fit, not for optical performance.
For non-prescription and fashion eyewear (sunglasses, blue-light glasses, reading glasses at fixed diopters), this distinction is less relevant. For single-vision or progressive prescription lenses, the recommendation covers physical fit only.