I Built a Subway Nutrition Calculator
Source: Dev.to
Introduction
Just a single HTML file with a ton of JavaScript, a massive JSON‑like data structure, and a stubborn refusal to let a bad UI ruin my lunch.
I’m going to walk you through how I built it, what broke along the way, and what I’d do differently next time.
The Problem
You’d have to manually add:
- bread calories
- meat calories
- veggie calories (most are zero, but olives and avocado aren’t)
- sauce calories
Then double it for a foot‑long, remember that cheese adds fat and sodium, and don’t forget the salt and pepper.
It’s tedious and error‑prone – exactly the kind of problem a simple web tool can solve.
Data Collection
I needed a complete, consistent dataset covering every possible ingredient:
| Category | Items |
|---|---|
| Breads | 12 |
| Pre‑made sandwiches | 30+ |
| Proteins (a la carte) | 20+ |
| Cheeses | 5 |
| Vegetables | 15 |
| Condiments | 25+ (Normal & Light) |
| Seasonings | 3 |
| Sides | 2 |
| Salads | 25+ |
| Wraps | 25+ |
| No‑Bready Bowls | 25+ |
| Protein Pockets | 4 |
| Soups | 3 |
| Desserts | 7 |
| Sidekicks | 8 |
| Total | ≈ 300+ |
Sources
- Subway’s official US nutrition PDF (2026 version)
- Their online menu
- Third‑party aggregators
- Store‑level ingredient sheets
Lesson: Never trust a single source. Cross‑reference everything and be transparent about where the data comes from and what’s estimated.
Data Structure
I created one massive JavaScript object called subwayMenu. It contains arrays for each category (breads, proteins, cheeses, vegetables, condiments, etc.).
Every item follows the same schema:
{
id: 'artisan-italian-bread-6inch',
name: '6" Artisan Italian Bread',
servingSize_g: 71,
calories: 210,
totalFat: 2,
saturatedFat: 1,
transFat: 0,
cholesterol: 0,
sodium: 380,
totalCarbs: 39,
dietaryFiber: 1,
sugars: 3,
addedSugars: 2,
protein: 8,
vitaminA_mcg: 0,
vitaminC_mg: 0,
calcium_mg: 1040,
iron_mg: 16.2,
category: 'bread'
}All nutrition fields are present for every item, which makes looping, calculating, and filtering straightforward.
Core Logic
Size Multiplier
// quantity system (simplified)
const sizeMultiplier = isFootlong ? 2 : 1;calculateTotalNutrition
- Initialise a totals object with zeros for every nutrition field.
- Apply the size multiplier (2 for foot‑long) to categories that scale with sandwich size.
- Loop through every selected category (bread, proteins, cheeses, vegetables, condiments, …) and add the item’s nutrition multiplied by quantity × multiplier.
- Return the final totals and a list of ingredients (used for the “ingredients” panel).
Rounding is deferred until the very end, where values are rounded to one decimal place for a clean display.
UI Design
No external libraries – just plain HTML, CSS, and vanilla JavaScript.
| Area | Description |
|---|---|
| Left side | Builder with collapsible categories (Bread, Proteins, Cheeses, …) and a tab switcher for menu types (Sandwich, Salads, Wraps, …). |
| Right side | Results section with a nutrition label, calorie progress bar, current selection list, and action buttons. |
Tab System & Collapsible Categories
<!-- simplified markup -->
<div class="category-header" onclick="subwayToggleDropdown(this)">
<span>Breads</span>
<svg><!-- arrow icon --></svg>
</div>
<div class="category-items">
<!-- list of breads -->
</div>subwayToggleDropdown adds/removes an expanded class and rotates the SVG arrow.
Search Inside Categories
A simple input field filters the visible items in the currently opened category.
“Current Selection” Panel
Lists every chosen item with quantity and calories.
Each entry has an X button that calls
subwayRemoveItemFromSelection, which:- Updates the
currentSelectionobject. - Re‑renders the affected category (to uncheck the item).
- Re‑renders the selection panel itself.
- Updates the
This small quality‑of‑life feature prevents the user from having to scroll back up to see what they’ve added.
Exporting Data
function subwaySaveNutritionInfo() {
const { totals, ingredients } = calculateTotalNutrition();
const lines = [
`Meal: ${selectedMenuType}`,
`Size: ${isFootlong ? 'Foot‑long' : '6‑inch'}`,
'',
'Ingredients:',
...ingredients.map(i => `- ${i.name} ×${i.qty}`),
'',
'Nutrition Facts:',
`Calories: ${totals.calories} kcal`,
// …other fields…
];
const blob = new Blob([lines.join('\n')], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'subway-nutrition.txt';
a.click();
URL.revokeObjectURL(url);
// temporary “Saved!” feedback
showSaveFeedback();
}Plain‑text export works everywhere and lets users paste the data into notes or spreadsheets.
Known Issues & Gotchas
- Bread radio group – only one bread can be selected at a time.
- Foot‑long multiplier – must be applied to every size‑dependent ingredient.
- Search resetting – changing an item resets the search box; need to preserve the query.
- Save button – doesn’t work in some older browsers (requires Blob support).
Takeaways
- Data first: Clean, consistent data is ~80 % of the work.
- Logic before UI: Get the calculation engine working in the console before building the interface.
- User‑centred design: A “Current Selection” panel and easy export make the tool genuinely useful.
Final Thoughts
The code is a little messy and the data could be more complete, but the calculator works. You can build a custom Subway meal, see exactly what you’re eating, and save the results for later.
If you’re a developer thinking about building a similar tool for another restaurant chain, my advice is:
- Start with the data.
- Build the calculation engine first.
- Add UI features that solve real pain points (search, selection list, export).
Happy coding! 🚀
Testing with Real Users
I handed this to a few friends and watched where they got confused. That’s how I learned about the foot‑long multiplier bug.
Keep It Simple
You don’t need a backend, a database, or a build system. A single HTML file with inline CSS and JS is fine for a tool like this.
Practical Use
Now, whenever I walk into Subway, I know exactly what I’m ordering. And if I’m not sure, I open this calculator on my phone and build it before I get to the counter.
The Goal
That’s the whole point—tools should make your life easier, not harder.
Closing Thoughts
I hope this walkthrough helps someone else build something useful. And if you just wanted to use the calculator, well, now you know what’s under the hood.