Why I built this
I run whatdogscaneat.com, a site that answers one deceptively simple question: what human foods can dogs actually eat? Every guide on the site ends with the same follow-up from readers — “Okay, it’s safe. But how much can I give?”
That question is a software problem. “Safe” is a yes/no lookup. “How much” is a calculation, and it depends on the dog’s weight, age, neuter status, and activity level. So I built a calorie counter for dogs: enter the dog, pick the food, get a portion that fits the dog’s daily budget instead of quietly blowing it.
This post is the full build notes — the veterinary formulas underneath it, a working JavaScript implementation you can lift, the food database I started with, and a worked example you can test your own code against.
The problem isn’t the math. It’s the model
A first instinct is linear: twice the weight, twice the calories. That’s wrong for dogs, and it’s wrong in a way that matters. Metabolic rate scales with body surface area and metabolically active tissue, not raw mass — which is why veterinary nutrition uses an allometric (power) formula instead of a straight line.
Get the model wrong and the errors compound at the extremes: you massively overfeed small dogs and underfeed large ones. The good news is the correct model is only three steps, and each step maps cleanly to a function.
Step 1 — Resting Energy Requirement (RER)
The Resting Energy Requirement (RER) is the energy a dog needs at complete rest in a comfortable environment — basically, the cost of staying alive: breathing, circulation, cell repair, brain function.
Formula: RER (kcal/day) = 70 × (body weight in kg) ^ 0.75
That 0.75 exponent is the whole trick. In code it’s just Math.pow(weightKg, 0.75), but conceptually it’s what stops a 40 kg dog from being assigned four times the calories of a 10 kg dog. (For reference, the 40 kg dog’s RER is about 2.8× the 10 kg dog’s — not 4×.)
Two implementation notes before you go further. First, always calculate in kilograms internally and convert at the UI boundary — most owners, especially in the US, think in pounds. Second, use the dog’s ideal weight, not necessarily today’s weight, if the dog is overweight. Feeding an overweight dog for the weight it already is just maintains the problem. More on that in the edge cases below.
Step 2 — Daily Energy Requirement (DER) multipliers
Nobody’s dog actually lives at rest. The Daily Energy Requirement (DER) adjusts RER with a multiplier for life stage and lifestyle:
Formula: DER (kcal/day) = RER × life-stage multiplier
Dog profile
Multiplier
Notes
Weight loss (vet-supervised)
1.0 × RER
Calculated on ideal weight
Inactive / obesity-prone adult
1.2 – 1.4
Senior and low-activity dogs
Neutered adult (typical pet)
1.6
The default for most pets
Intact adult
1.8
Higher baseline metabolism
Light work / very active
2.0
Regular long exercise
Puppy, over 4 months
2.0
Growth on top of maintenance
Puppy, under 4 months
3.0
Fastest growth phase
Gestation (late) / lactation
up to 3.0+
*Varies widely — vet guidance needed
*
Treat these multipliers as starting points, not promises. Two neutered adults of identical weight can maintain on noticeably different intakes, which is why any responsible tool tells the user to monitor body condition over 2–4 weeks and adjust by roughly 10% at a time. In product terms: DER is your default value, not your ground truth.
Step 3 — The 10% treat budget rule
Commercial dog food is formulated to be “complete and balanced” — every vitamin and mineral in the right ratio, assuming the dog eats essentially all of its calories from that food. Human-food extras, treats, chews, and training rewards dilute that balance.
The widely used veterinary rule of thumb is simple: treats and extras should make up no more than 10% of daily calories. The other 90% should come from the complete diet.
Rule: Treat budget (kcal/day) = DER × 0.10
This is the feature that makes the tool actually useful. “Your dog can have broccoli” is trivia. “Your dog has 116 kcal of treat budget today, and this portion uses 17 of them” is a decision. That second sentence is the product.
The JavaScript implementation
Here is the whole engine. No dependencies, no framework — the interesting part is the validation and the units, not the arithmetic.
// dog-calorie-counter.js
// RER = 70 * (kg ^ 0.75) | DER = RER * multiplier
const DER_MULTIPLIERS = {
weightLoss: 1.0,
inactive: 1.4,
neuteredAdult: 1.6,
intactAdult: 1.8,
active: 2.0,
puppyOlder: 2.0, // over 4 months
puppyYoung: 3.0, // under 4 months
};
const LB_TO_KG = 0.45359237;
function toKg(weight, unit = 'kg') {
if (unit === 'lb') return weight * LB_TO_KG;
if (unit === 'kg') return weight;
throw new Error(Unsupported unit: ${unit});
}
function calculateDogCalories({ weight, unit = 'kg',
profile = 'neuteredAdult' }) {
const weightKg = toKg(weight, unit);
if (!Number.isFinite(weightKg) || weightKg <= 0) {
throw new Error('Weight must be a positive number.');
}
if (weightKg < 0.5 || weightKg > 100) {
throw new Error('Weight outside plausible dog range (0.5-100 kg).');
}
const multiplier = DER_MULTIPLIERS[profile];
if (multiplier === undefined) {
throw new Error(`Unknown profile: ${profile}`);
}
const rer = 70 * Math.pow(weightKg, 0.75);
const der = rer * multiplier;
return {
weightKg: round1(weightKg),
rer: Math.round(rer),
der: Math.round(der),
treatBudget: Math.round(der * 0.10), // the 10% rule
};
}
function caloriesForPortion(food, grams) {
// food.kcalPer100g comes from the database below
return Math.round((food.kcalPer100g / 100) * grams);
}
const round1 = (n) => Math.round(n * 10) / 10;
// Example: 50 lb neutered adult
console.log(calculateDogCalories(
{ weight: 50, unit: 'lb', profile: 'neuteredAdult' }));
// -> { weightKg: 22.7, rer: 728, der: 1164, treatBudget: 116 }
A few deliberate choices in there:
Validate the weight, hard — everything downstream is wrong if this is wrong, so fail loudly instead of returning NaN-flavoured nonsense.
Round for display, not for storage — owners do not need decimal calories. Whole numbers read as guidance; 1,164.3 kcal reads as false precision.
Keep multipliers as named data — a string key like neuteredAdult is self-documenting in analytics and survives a UI redesign. A bare 1.6 buried in a component does not.
Separate the dog from the food — portion math is a separate pure function, so the food database can grow without touching the calorie engine.
The food database: calories per 100g
The database format is boring on purpose: one record per food, calories per 100 grams, plus a safety flag. Per-100g is the standard unit in nutrition data (it’s how USDA FoodData Central reports values), which makes new foods a one-line addition instead of a conversion exercise.
Values below are typical raw or plainly cooked values, rounded. Always check the full safety notes for a food before surfacing it to users — calorie-safe is not the same as dog-safe, and preparation (added salt, oil, onion, garlic, xylitol in peanut butter) changes the answer.
Food (per 100 g)
kcal
Budget note
Pumpkin purée (plain, canned)
26
Very budget-friendly
Green beans (raw)
31
Very budget-friendly
Broccoli (raw)
34
See worked notes below
Carrots (raw)
41
Crunchy, low cost
Apple (raw, no seeds/core)
52
Remove seeds and core
Sweet potato (raw)
86
Usually served cooked
Banana
89
Sugary — small portions
White rice (cooked)
130
Bland-diet staple
Chicken breast (cooked, skinless)
165
Lean protein
Cheddar cheese
403
Tiny cubes only
Peanut butter (unsweetened)
588
Highest cost — check for xylitol
Broccoli deserves a callout because it’s the food readers ask about most. It’s low-calorie (34 kcal per 100 g) and generally safe in small amounts, but the florets contain isothiocyanates that can cause gastric irritation if a dog eats a lot of it — another reason portion math matters more than a bare yes/no. The full breakdown, including stem vs. floret and raw vs. cooked, is in the site’s guide: can dogs eat broccoli?.
Worked example — a 50 lb dog, end to end
If you implement this yourself, use these numbers as your unit test. A 50 lb dog is a good test case: common size, and it forces the pound-to-kilogram conversion.
Step
Calculation
Result
Convert weight
50 lb × 0.4536
22.68 kg
RER
70 × 22.68 ^ 0.75
728 kcal/day
DER (neutered adult, ×1.6)
728 × 1.6
1,164 kcal/day
Treat budget (10%)
1,164 × 0.10
116 kcal/day
Main food allowance (90%)
1,164 × 0.90
1,048 kcal/day
Now spend that 116 kcal budget three different ways, using the database above:
50 g of broccoli = 17 kcal — about 15% of the day’s treat budget. Plenty of room left — this is what a “cheap” treat looks like.
1 tablespoon of peanut butter (about 16 g) = 94 kcal — over 80% of the budget in a single spoonful. Calorie-dense foods are where owners accidentally overfeed.
30 g of cheddar = 121 kcal — more than the entire treat budget, before a single training treat. Cheese is a garnish, not a snack.
That contrast is the insight the tool exists to deliver. The foods themselves are all “safe in moderation” — moderation just turns out to mean wildly different gram amounts, and nobody can eyeball the difference between 17 kcal and 121 kcal on a plate.
Edge cases developers miss
Ideal weight vs. current weight — for weight-loss and weight-gain profiles, calculate DER from the target weight and say so in the UI. Silently using current weight produces a tool that tells an overweight dog to stay overweight.
Medical cases are out of scope — one multiplier table is a starting point. Puppies, pregnant and nursing dogs, working dogs, and dogs with kidney, pancreatic, or endocrine conditions need a veterinarian’s numbers, not an app’s. Put that disclaimer next to the result, not in a footer nobody reads.
Unit confusion is your #1 bug — a Great Dane puppy and a senior Chihuahua both hit this code. Plausible-range validation (I used 0.5–100 kg) catches the classic fat-finger errors: 500 instead of 50, kilograms entered into a pounds field.
Food data is approximate — calorie counts for the same food vary by source, ripeness, and cooking method. Show “about” numbers, cite your source in the data file, and version the database so a value change doesn’t silently rewrite a user’s history.
Toxic beats low-calorie, every time — some human foods — grapes, raisins, onions, garlic, macadamia nuts, chocolate, and anything sweetened with xylitol — are toxic at any portion. The safety flag must veto the food before the calorie math ever runs. Never let a portion calculator imply a safe amount of a toxic food.
What I would build next
A daily log — so a day’s extras accumulate against the budget instead of each one being judged in isolation.
Ideal-weight mode — body condition score (a simple 1–9 visual scale) mapped to a suggested ideal-weight range, so the ideal-weight calculation isn’t a guess.
One database, everywhere — the same per-100g record drives the counter, the individual food guides, and a printable “fridge chart” — one source of truth, three surfaces.
If you want to see the food side of this in production, the guides on the site pair each food’s safety verdict with exactly this kind of portion thinking — starting with the homepage and working through foods one by one.
The takeaway
A dog calorie counter is about 40 lines of JavaScript. The work isn’t the code — it’s choosing the right model (allometric, not linear), the right defaults (named multipliers, ideal weight), and the right guardrails (the 10% budget, toxic-food vetoes, honest rounding).
Build those in, and you’ve got a tool that answers the question owners actually ask. Not “can my dog eat this?” — but “how much of this can my dog eat, today, without it costing tomorrow?”
Please note: This article explains the standard formulas used in veterinary nutrition for educational purposes. It isn’t a substitute for advice from your own veterinarian, who knows your dog’s health history. If your dog has a medical condition, is pregnant or nursing, or needs to lose significant weight, get a feeding plan from your vet.
Top comments (0)