@beerwright/core
    Preparing search index...
    @beerwright/core: the brewing math behind Beerwright

    API reference · Method notes · Beerwright

    npm License: Apache-2.0 Build Docs: API reference

    This is the brewing math behind Beerwright: gravity, bitterness, color, ABV, water chemistry and mash pH, carbonation, yeast pitching, decoctions and blending. It's the same code the app runs, copied here from the app's repository every time a new version goes live.

    I'm publishing it because I want to share a solid base for anyone who wants to build brewing software, so we don't all have to reinvent the wheel. If you're writing a brewing app, a calculator or a tool for your own brewery, you're welcome to use it.

    From grain to glass:

    Area What it works out
    Gravity and ABV Extract from the grist, mash and brewhouse efficiency, OG and FG, attenuation, calories, and ABV by the Zymurgy formula or the simple (OG − FG) × 131.25
    Bitterness Tinseth, Rager and Garetz, whirlpool and hop-stand additions corrected for temperature, and the alpha acid a hop loses in storage
    Color Morey, Daniels and Mosher, in SRM and EBC, with a hex swatch for the beer
    Water and mash pH Salt additions ion by ion, acid additions, a solver that picks the salts for a target profile, and a mash pH prediction from the grist
    Volumes Strike and sparge water, losses and boil-off, worked from the batch size or back from what you measured, plus strike temperature and infusions
    Corrections Refractometer and hydrometer readings, and how much to dilute or boil down to hit a gravity
    Yeast Pitch rates, viability by age, starters and dry-yeast sachets
    Decoction How much to pull for each decoction, and a schedule for the whole mash
    Carbonation and priming CO₂ volumes from keg pressure and temperature, the CO₂ left from fermentation, and priming sugar for bottles
    Blending Sours and barrel-aged blends: ABV, gravity, pH and acidity by volume, and priming that counts the CO₂ already in each component
    Mead, cider and spirits Honey or sugar to a target gravity, TOSNA nutrient schedules, sulfite and sorbate doses, and proofing and sweetening a spirit
    Recipe checks One pass before brew day for undersized equipment, a short pitch, mash pH out of range and the like

    It's a TypeScript library with no runtime dependencies. It doesn't touch the DOM, the network or the disk: you give it a recipe and it gives you numbers. That's why the same code runs in the browser, on the server and in the tests without any changes.

    Every headline number comes with an explanation: the formula it used, the inputs it plugged in, and the papers or books the formula is from. The app shows these when you open the working behind a number, and you can read them here too.

    npm install @beerwright/core
    # or
    pnpm add @beerwright/core
    # or
    yarn add @beerwright/core

    It's ES modules with the TypeScript types included, and it needs Node 22 or later, or any bundler. Everything is exported from @beerwright/core, and each area is also its own entry point if you only want one, like @beerwright/core/carbonation or @beerwright/core/water.

    It's at 0.x because I'm still settling the API, so a new minor version (0.1 to 0.2) can rename or change things. npm's default ^0.1.0 range stays within 0.1.x, so an update won't surprise you. Once the names stop moving I'll call it 1.0 and follow semver strictly from there.

    import { calculateRecipe } from '@beerwright/core';

    const result = calculateRecipe({
    batchVolumeL: 20,
    boilTimeMinutes: 60,
    fermentables: [
    { name: 'Pale ale malt', amountKg: 4.5, class: 'grain', use: 'mash',
    extractFineGrindDryBasisPercent: 80, moisturePercent: 4, colorLovibond: 3 },
    { name: 'Crystal 60', amountKg: 0.4, class: 'steeping-grain', use: 'mash',
    extractFineGrindDryBasisPercent: 74, moisturePercent: 4, colorLovibond: 60 },
    ],
    hops: [
    { name: 'Centennial', amountKg: 0.02, alphaAcidPercent: 9.5, form: 'pellet',
    use: 'boil', timeMinutes: 60 },
    { name: 'Cascade', amountKg: 0.03, alphaAcidPercent: 6, form: 'pellet',
    use: 'boil', timeMinutes: 15 },
    ],
    yeasts: [{ name: 'US-05', type: 'ale', form: 'dry',
    attenuationMinPercent: 78, attenuationMaxPercent: 82 }],
    });

    result.vitals.og; // 1.0508
    result.vitals.fg; // 1.0098
    result.vitals.abv; // 5.47
    result.vitals.ibu; // 33.0
    result.vitals.srm; // 9.4

    result.explain.ibu.formula; // 'tinseth-1997'
    result.explain.ibu.sources; // Tinseth 1997, Malowicki 2005, …

    Here's the whole explanation behind that 33 IBU, as result.explain.ibu returns it:

    {
    "label": "Bitterness",
    "formula": "tinseth-1997",
    "expression": "IBU = 1.65 × 0.000125^(Gb−1) × (1 − e^(−0.04t))/4.15 × (AA% × g × 1000 / L)",
    "inputs": {
    "volumeL": 21.3,
    "volumeBasis": "post-boil",
    "boilGravity": 1.0508,
    "gravityBasis": "post-boil",
    "whirlpoolModel": "mibu",
    "hopCount": 2
    },
    "value": 33.03692212896646,
    "unit": "IBU",
    "notes": ["Dry hops contribute 0 IBU."],
    "sources": [
    {
    "title": "Hop Utilization",
    "author": "Glenn Tinseth",
    "year": 1997,
    "url": "http://realbeer.com/hops/research.html"
    },
    {
    "title": "Hop bitter acid isomerization and degradation kinetics in a model wort-boiling system",
    "author": "Mark G. Malowicki, MSc thesis, Oregon State University",
    "year": 2005
    },
    {
    "title": "A Modified IBU Calculation, especially for late hopping and whirlpool hops",
    "author": "John-Paul Hosom (alchemyoverlord)",
    "year": 2018,
    "url": "https://alchemyoverlord.wordpress.com/"
    }
    ],
    "family": "bitterness"
    }

    The OG, FG, ABV, color, BU:GU, calories, efficiency and volumes come with one of these too.

    Everything is in metric internally (kilograms, liters, °C, specific gravity), and src/units converts at the edges. When you leave something out, like the equipment profile above, the engine uses a documented default. Those are all in src/defaults.ts, each with a note on where its number came from.

    • src/recipe.ts has calculateRecipe, which is the place to start. It calls everything else.
    • Each area has its own module: gravity, ibu, color, abv, water, mashPh, waterSolver, carbonation, yeast, starter, decoction, blending and a few more.
    • src/sources.ts is the bibliography. Every citation in the explanations comes from there.
    • docs/ explains the methods in prose: which formula runs where, what it assumes, and what it leaves out.
    • The API reference is generated from the comments in the code.

    Where there's more than one published answer, you can choose. Tinseth is the default for IBU, with Rager and Garetz available. Morey is the default for color, with Daniels and Mosher available. For ABV, the default is the more accurate formula from Zymurgy, and the simple (OG − FG) × 131.25 is available too. The result always says which one it used.

    If you've cloned this repository rather than installed the package:

    pnpm install
    pnpm test
    pnpm typecheck
    pnpm run docs # the API reference, into site/
    pnpm run build # the npm package, into dist/

    The tests check the math against figures worked out by hand, against published recipes, and against properties that should always hold (scaling a recipe shouldn't change its gravity, for example).

    If you think a number is wrong, please open an issue. The most useful ones say what you expected, what the engine gave you, and where your number comes from: a book, a paper, a lab sheet or a measurement.

    I'm not taking pull requests here for now. This repository is a copy of code that lives in Beerwright's own repository, so a change made here would be overwritten on the next sync. If you've found a fix, open an issue with it and I'll make the change in the main repository and credit you as a co-author. It'll show up here on the next release.

    Apache 2.0. See LICENSE.

    Chris Main