Make a label

The labelling rules Labelwise applies

This is the record of every labelling rule Labelwise applies, with the Food Standards Code section for each and the test that pins it. It is the same file our engineers work from.

Scope: packaged food for retail sale in Australia

The app makes labels for packaged food sold at retail in Australia. This section overrides anything else in the project that says otherwise.

Part of the label Status in Australia Law In the app
Ingredient list and allergen declarations Mandatory Food Standards Code 1.2.3, 1.2.4, 1.2.10 Step 1
Nutrition information panel Mandatory Food Standards Code 1.2.8 Step 2
Country of origin label Mandatory for most food Country of Origin Food Labelling Information Standard 2016 Step 3, on by default
Supplier, date mark, lot, storage, net quantity Mandatory Food Standards Code 1.2.2, 1.2.5, 1.2.6; trade measurement law Step 4
Health Star Rating Voluntary HSR system Step 5

A label is "ready to print" only when every mandatory part is present. Country of origin can be turned off for food the Information Standard does not apply to, e.g. food made and packaged on the premises where it is sold (s 14(2)(b)), and the app then warns.

New Zealand is out of scope. The Food Standards Code is shared, so the nutrition panel, ingredient list and allergen rules below are the same there, but the app isn't offered for food sold in New Zealand: it doesn't implement New Zealand's country of origin rules, its trade measurement rules or its consumer law, and nothing here was checked for that market. New Zealand appears only as a country that food or ingredients can come from.

Also out of scope, and refused. The Food Standards Code governs these products under standards the app doesn't implement, so the app refuses them rather than print a label that looks finished. These are Labelwise policy rules, not Code wording; each refusal cites the standard that governs the product so the business knows why (labelwise/scope.py, tests/test_scope.py), e.g. "Labelwise doesn't make labels for kava. Standard 2.6.3 sets its own rules."

Product Governing standard What the app does
Infant formula products Standard 2.9.1 (its own composition and labelling rules) Refuses
Food for infants (under 12 months) Standard 2.9.2 Refuses
Formulated meal replacements and formulated supplementary foods Standard 2.9.3 Refuses
Formulated supplementary sports foods Standard 2.9.4 Refuses
Food for special medical purposes Standard 2.9.5 Refuses
Alcoholic drinks (more than 1.15% alcohol by volume) Part 2.7 and 1.2.8—5 (no standard nutrition panel; alcohol content and standard drinks statements instead) Refuses
Kava Standard 2.6.3 Refuses
Formulated caffeinated beverages (energy drinks) Standard 2.6.4 (extra statements and limits) Refuses
Any product making a nutrition content claim or health claim on the label Standard 1.2.7 (claims have conditions the app doesn't check yet; PLANS.md Phase 4) Refuses
Unpackaged food, food for catering, food sold for immediate consumption 1.2.1—6 to 1.2.1—9 (different labelling duties, or none) Refuses: the app only does packaged food for retail sale
  • The business's tick is the record. On the product step the business ticks any of these that apply (Recipe.product_scope), or confirms that none do (Recipe.scope_confirmed). A ticked product is an error on the product step, which stops the label printing, and comes first in the warnings. Nothing ticked and nothing confirmed is a warning asking the business to confirm; it doesn't stop printing.
  • The detectors are a safety net, not the decision. The product name, the ingredient names and the details lines are searched (whole words, any case) for words that suggest a refused product: infant, baby, "stage 1" (2.9.2); formula (2.9.1); meal replacement, toddler (2.9.3, which covers formulated supplementary foods for young children); sports, protein powder, pre-workout (2.9.4); beer, wine, cider, spirit, vodka, gin, whisky, rum, liqueur, mead, "% alc", ABV (Part 2.7; the product name and ingredient names only, since the details lines name places such as Gin Gin QLD); kava (2.6.3); "energy drink", or guarana together with caffeine (2.6.4; guarana alone only needs its advisory statement). These everyday foods are skipped before matching: cider vinegar, wine vinegar, spirit vinegar, rice wine vinegar, baby spinach, baby corn, baby carrots, baby beetroot, baby leaf. A match, until the business confirms, is a warning naming the words seen; it never refuses on its own.
  • Claims guardrail (1.2.7). The product name and the details lines (not the ingredient names) are searched for these phrases, whole phrases, any case, a space or a hyphen between words: low fat, fat free, reduced fat, low sugar, sugar free, no added sugar, low salt, low sodium, reduced salt, high fibre, source of fibre, high protein, source of protein, good source, high in, rich in, low GI, gluten free, lactose free, contains omega, healthy, nutritious, boosts, immunity, lowers cholesterol, supports. A match, until the business confirms, is a warning that the label may be making a nutrition content or health claim, which Labelwise doesn't support yet: remove the words, or tick the claims box, which then refuses the label.
  • Not claims. "Free range", "organic", "natural", "handmade", "vegan", "vegetarian", "halal" and "kosher" describe how the food is made or who it suits. They aren't nutrition content or health claims under 1.2.7 and never trigger the guardrail.

How the rules are recorded

This file lists every labelling rule the app implements, with the section of the Australia New Zealand Food Standards Code (or other law) it comes from. Each rule was checked against the latest compilations on the Federal Register of Legislation as at 27 Sep 2026 (see the table at the end). Every rule has a test in tests/.

Before launch: each reference label is cross-checked against the official government tools (the FSANZ Nutrition Panel Calculator, the official HSR Calculator and the government's country of origin labelling tool), and any difference becomes a fix here and a test (see PLANS.md §7). This is planned, not yet done. Keep the table at the end up to date when the Code is amended.

Each warning names the components it concerns (nutrition panel, ingredient list, Health Star Rating, country of origin). A component prints on its own only when its own errors are clear; the full label, and the artwork download, only when there is no error at all (a Labelwise policy rule; label.py, _printable() in main.py).

Nutrition Information Panel (Standard 1.2.8, Schedule 12)

Rule Source Where
Format: "NUTRITION INFORMATION", "Servings per package:", "Serving size:", then per-serving and per-100 g (or 100 mL) columns 1.2.8—6(2), S12—2 nutrition.py, pdf.py
Headings state that values are averages: "Average quantity per serving" / "Average quantity per 100 g" 1.2.8—7(1)(a); wording from the example panel in S12—7 serving_texts()
Rows in order: Energy, Protein, Fat total, – saturated, Carbohydrate, – sugars, Sodium S12—2 nip_rows()
Dietary fibre (optional here) goes after the carbohydrate rows and before sodium S12—3 nip_rows()
Energy in kJ, optionally with Cal (kJ ÷ 4.18) 1.2.8—6(1)(d)(i), S11—2(4) energy()
Values to not more than 3 significant figures (we also cap grams at 1 decimal place, and show none from 100 g up); halves round up 1.2.8—7(3) declared(), fmt()
Optional "LESS THAN 40 kJ", "LESS THAN 1 g" (protein, fat, fatty acids, carbohydrate, sugars, fibre), "LESS THAN 5 mg" (sodium) 1.2.8—7(4)–(6) use_less_than
Values are per 100 g of the final food: the recipe's total nutrients ÷ weight after cooking FSANZ NPC calculation method calculate()
Per 100 mL uses the food's density 1.2.8—6(2) calculate()

Lab values. The panel may be printed from values for the finished food reported by a lab or taken from a supplier's specification of the finished product (Recipe.lab_values, per 100 g or 100 mL), instead of from the ingredients. The average quantity may be worked out from an analysis of the food (method (a)) as well as from the ingredients (method (b), the recipe totals above); the business chooses the method that best represents it, allowing for lot-to-lot and seasonal variation (1.1.1—6(2), (3)(a); "average quantity" defined in 1.1.2—2(3)). Lab values are rounded and declared exactly as recipe totals are, and rated for the Health Star Rating the same way; the ingredients' values are then not used. The source is recorded in the label data (Label.lab_source) and in the artwork README. The app doesn't check the lab's method or whether the values represent every lot: that judgement is the business's (a Labelwise policy rule). calculate(), check().

Not implemented yet (the app must not be used for these products):

  • Nutrition, health or related claims. These trigger extra rows: fibre when fibre or carbohydrate claims are made (1.2.8—6(5)), fatty-acid rows for fat claims (6(3)), potassium for salt claims (6(12)), galactose for lactose claims (6(11)).
  • Variable-weight packs, where servings are stated per kilogram (1.2.8—6(1)(a)(ii)).
  • Alternative wording such as "slice" or "pack" instead of "serving" (7(2)(a)).
  • %DI (percentage daily intake) columns.
  • Infant food, formula and other special-purpose foods.

Energy check (Schedule 11)

A data-entry check only, not used for the panel values: protein 17, fat 37, carbohydrate 17, dietary fibre 8 kJ/g (S11—2(2)). The estimate is 17 × protein + 37 × fat + 17 × carbohydrate + 8 × fibre per 100 g; ingredients estimated at 50 kJ or less aren't checked.

  • Fibre known: an ingredient whose stated energy differs from the estimate by more than 25% gets a warning.
  • Fibre unknown (the FSANZ data has none): fibre can only add energy, so the ingredient gets a warning when its stated energy is more than 25% below the estimate, or more than 25% of the estimate above the ceiling estimate + 8 × (100 − protein − fat − carbohydrate), i.e. as if everything else in 100 g were fibre. Unchanged FSANZ foods (chillies, garlic, tomatoes, paprika, cinnamon, coriander seed) pass. For watery foods the ceiling is loose (tomatoes pass at double their energy); a typo that drops a digit is still caught.

Ingredients with negligible nutrition (a Labelwise data-entry rule, not a Code rule): an ingredient marked negligible, used in a tiny amount with no nutrition data (cultures, a pinch of citric acid), gets no "has no nutrition values" warning and counts as fibre 0 for the Health Star Rating, so it doesn't make the rating provisional. It is still listed and still counted in the weights. A negligible ingredient that is more than 2% of the ingredients' total ingoing weight gets a warning: too much to ignore; enter its nutrition values.

Ingredients removed after cooking (a Labelwise data-entry rule): bones, a bouquet garni or tea leaves strained out after cooking take nutrients with them that the recipe can't account for, since the calculation can only treat lost weight as evaporated water. While any ingredient is marked removed after cooking the label has an error: enter the strained liquid as an ingredient with its own nutrition values (from a lab report or supplier), or wait for lab-values input. The removed ingredients are still declared in the ingredient list (see "Ingredients removed after cooking are declared" under Ingredient list).

Ingredient list (Standard 1.2.4)

Rule Source
Descending order of ingoing weight 1.2.4—5(1)
Added water counted as the amount remaining after cooking (lost moisture is taken from added water first) 1.2.4—5(4)
Added water under 5% of the food is not listed; all the added water is counted together 1.2.4—3(2)(c)(iii)
Compound ingredients: name followed by its components in brackets 1.2.4—5(5)
The "Ingredients:" heading is conventional, not required by law none

Not implemented:

  • The rule that compound ingredients under 5% of the food need only list their allergens and additives (1.2.4—5(6)). A compound ingredient's components are free text, printed as typed: an additive among them is typed in its declared form ("emulsifier (322)") and the app doesn't check it. An ingredient that is itself an additive is declared and checked as in "Food additives" below.
  • Reconstitution water; volatile ingredients other than water; processing aids.

Ingredients removed after cooking are declared. Bones in a broth, tea leaves in a brewed drink and herbs in a bouquet garni are ingredients in the ordinary sense (1.1.2 does not define the word), and every ingredient is listed (1.2.4—3(1)). What they contribute (extract, flavour, nutrients, allergens) is in the food for sale, so they are not processing aids (1.2.4—3(2)(d), (e) with 1.1.2—13(1), (2)) and not volatile ingredients completely removed during processing (1.2.4—3(2)(b)). They are listed by their usual name in descending order of ingoing weight (1.2.4—5(1)), at their full ingoing weight: 1.2.4—5(4) adjusts only water and volatile ingredients, not the part strained out. An allergen they carry (fish frames: fish) is declared under 1.2.3 as for any ingredient. The app lists them like any other ingredient; the nutrition panel for such a food is an error (see "Ingredients removed after cooking" above).

Not offered yet: added water in a food declared or named as a broth, brine or syrup need not be listed (1.2.4—3(2)(c)(ii)). Labelwise always applies the general added-water rules above.

Food additives (1.2.4—7, —8; Schedules 7 and 8)

An ingredient the business confirms as a food additive (Ingredient.additive) is printed in its declared form instead of its name, in its place by ingoing weight (1.2.4—5(1)). Code numbers are printed as Schedule 8 prints them, with no "E" prefix.

Rule Printed Source
An additive with a Schedule 7 class (prescribed or optional) is listed by the class name followed in brackets by its code number or its name as in Schedule 8 "Acidity regulator (330)", "Acidity regulator (citric acid)" 1.2.4—7(1)(a)
Otherwise, by its Schedule 8 name "Monk fruit extract" 1.2.4—7(1)(b)
Where Schedule 8 joins alternative names with "or", the label uses the one the business entered; the numerical listing's name is printed when the two listings differ (954, 307c), and the alphabetical variant is accepted as input "Humectant (glycerol)", "Sweetener (saccharin)" 1.2.4—7(1), S8—2
An additive with no code number in Schedule 8 is listed by name "Sweetener (monk fruit extract)" 1.2.4—7(1)(a)
An enzyme may be listed as "enzyme" without its specific name "Enzyme" 1.2.4—7(3)
A flavouring is listed as "flavouring" or "flavour", or by a more specific name "Flavouring", "Natural vanilla flavour" 1.2.4—7(4)
Glutamates (620–625), disodium guanylate (627), disodium inosinate (631) and disodium-5′-ribonucleotides (635) added as or in a flavouring are named as in 7(1); a generic "flavouring" is an error "Flavour enhancer (621)" 1.2.4—7(5)
Added caffeine is listed as caffeine; under "flavouring" or another class name it is an error "Caffeine" 1.2.4—7(6)
An added vitamin or mineral may use the class name "vitamin" or "mineral"; under these class words the name is printed unless the business chooses the code "Vitamin (ascorbic acid)" 1.2.4—8
An allergen the additive carries is declared as for any ingredient: bold where its required name is in the printed text, otherwise added in brackets "Emulsifier (322) (soy)" 1.2.3—7(2)(c)

Checked when the label is built (each an error that stops printing): a class name not in Schedule 7 and not "flavouring", "flavour", "enzyme", "vitamin" or "mineral" (the closest is offered); a code not in Schedule 8, with "E330" or "INS 330" answered with the number alone; a name not in Schedule 8 (exact, case-insensitive; the closest is offered); a code and a name of different additives; a code shared by several Schedule 8 additives with no name, where the name is to be printed; a class name with nothing in brackets; an additive the app knows has a class, entered without one (7(1)(a), (2)); the 7(5) substances or caffeine under a generic flavouring. A vitamin or mineral under 1.2.4—8 may use a name not in Schedule 8, since most added vitamins and minerals are not listed there.

The most appropriate class is the producer's choice (1.2.4—7(2)). Citric acid is an acid in a jam and an acidity regulator where it only adjusts pH; the business decides. The app only suggests: from a Schedule 8 name or a code in the ingredient's name ("330", "E330", "INS 330"), with a class from a class name typed in the name, or else from a curated map of about 30 additives small producers commonly use (additives.CURATED_CLASS, each with the reason for its class). An ingredient whose name suggests an additive but isn't confirmed as one gets a warning naming 1.2.4—7, the same guardrail as allergens and statements. Turmeric, saffron, amaranth and acacia are not suggested: as an ingredient's name they are usually the food.

Data: labelwise/additives_s8.json, transcribed from Schedule 8 (344 rows, numerical listing, cross-checked against the alphabetical listing) and Schedule 7 (19 prescribed and 6 optional class names); see additives_s8.README.md and "Compilations checked".

Additives inside a compound ingredient's components are typed by the user and not checked.

Not modelled: processing aids (1.2.4—3(2)(d), Standard 1.3.3) are not listed and the app doesn't tell them apart; whether an additive is permitted in that food at that level (Schedules 15 and 16, Standard 1.3.1) is not checked.

Characterising ingredients (Standard 1.2.10)

Rule Source
% = ingoing weight of the ingredient ÷ total ingoing weight, excluding added water removed in manufacture 1.2.10—4(1), 4(2)
Rounded to the nearest whole number, or the nearest 0.5 below 5% 1.2.10—8(3)
The % immediately follows the ingredient's name 1.2.10—8(2)

Not implemented: the optional final-weight basis for products that lose moisture (1.2.10—5), minimum percentages (8(4)), and a percentage in the product name instead of the ingredient list. The user decides which ingredients are characterising.

Allergens: Plain English Allergen Labelling (Standard 1.2.3 Div 3, Schedule 9 S9—3)

Rule Source
Required names in bold in the ingredient list, in the same size as the other ingredient text 1.2.3—7(1)
If the ingredient's name doesn't contain the required name, add it next to the name in brackets, e.g. "Butter (milk)" 1.2.3—7(2)(c)
Tree nuts named individually (almond, Brazil nut, cashew, hazelnut, macadamia, pecan, pine nut, pistachio, walnut) S9—3
Wheat: "wheat" in the ingredient list; "wheat" and "gluten" in the summary S9—3
Barley, oats, rye (when they contain gluten): the cereal name in the ingredient list; "gluten" in the summary S9—3
Soy: "soy", "soya" or "soybean" in the ingredient list; "soy" in the summary S9—3
Summary statement starts with "Contains", lists only required names, is bold, and sits directly next to the ingredient list 1.2.3—7(3)–(5)
Sulphites only when added at 10 mg/kg or more (the user confirms) S9—3
Singular or plural forms both accepted 1.2.3—8

Required names (S9—3), as declaration.py (REQUIRED_NAME, ALTERNATIVE_NAMES) prints them. The test named for each asserts the printed ingredient list with the name in bold and the "Contains:" statement (tests/test_declaration.py).

Allergen In the ingredient list (bold) Also accepted there In the "Contains:" statement Test
Wheat wheat wheat, gluten test_order_bold_and_contains
Barley (containing gluten) barley gluten test_gluten_cereals
Oats (containing gluten) oats gluten test_gluten_cereals
Rye (containing gluten) rye gluten test_every_required_name_printed
Soy soy soya, soybean soy test_soy_alternative_names
Almond almond almond test_tree_nuts_named_individually_and_plurals
Brazil nut Brazil nut Brazil nut test_tree_nuts_named_individually_and_plurals
Cashew cashew cashew test_tree_nuts_named_individually_and_plurals
Hazelnut hazelnut hazelnut test_every_required_name_printed
Macadamia macadamia macadamia test_every_required_name_printed
Pecan pecan pecan test_every_required_name_printed
Pine nut pine nut pine nut test_every_required_name_printed
Pistachio pistachio pistachio test_every_required_name_printed
Walnut walnut walnut test_every_required_name_printed
Peanut peanut peanut test_every_required_name_printed
Milk milk milk test_order_bold_and_contains
Egg egg egg test_every_required_name_printed
Fish fish fish test_every_required_name_printed
Crustacean crustacean crustacean test_every_required_name_printed
Mollusc mollusc mollusc test_every_required_name_printed
Sesame sesame sesame test_every_required_name_printed
Lupin lupin lupin test_every_required_name_printed
Sulphites (added at 10 mg/kg or more) sulphites sulphites test_every_required_name_printed

Guardrails in the app:

  • Every ingredient must be marked "allergens checked" before anything can be printed. The server enforces this: the PDF and artwork downloads are refused while any ingredient is unchecked or the label has any other error. Changing an ingredient's name or components clears its check.
  • The app never says "no allergens" for an ingredient that hasn't been checked.
  • Made products from the database (bread, sauces, sausages, anything "commercial") are marked "allergens unknown: check the spec", because a name can't show what is in them.

The user's responsibility:

  • Confirming each ingredient's allergens from supplier specifications. The app only suggests them from names.
  • Exemptions in S9—3 column 2: glucose syrups from wheat, alcohol distilled from wheat or whey, isinglass, refined soy oil, soy tocopherols and phytosterols.
  • Precautionary "may be present" statements. These are voluntary and not regulated by the Code.

Other mandatory label information (Standard 1.2.1—8)

The app prompts for the supplier's name and business address (1.2.2—4; the Code accepts an address in Australia or New Zealand, and the app asks for the Australian one). The app refuses a postal address (PO box, GPO box, locked or private bag): the address must say where the business is, a date mark (1.2.5—3, not needed if the best-before date is 2 years or more away), lot identification (1.2.2—3), and storage conditions and directions for use (1.2.6—2). It doesn't check the food name (1.2.2—2) or product-specific requirements. Type must be legible and contrast with the background (1.2.1—24). Warning and advisory statements (1.2.1—8(1)(d)) have their own section below.

Warning and advisory statements (Standard 1.2.3, Schedule 9)

The label of packaged food for retail sale must carry the advisory statements, warning statements and declarations the Code requires (1.2.1—8(1)(d)). The statements are made in statements.py from what the business confirms: substances tagged on each ingredient (statement_tags) and what the product is (product_flags). They print after the ingredient list, the "Contains" statement and any "may contain" statement, in the sticker PDFs (whole label, or ingredients only) and in the pack artwork's ingredients block. Tests: tests/test_statements.py.

The warning statement is prescribed word for word. Advisory statements are required "to the effect that" (S9—2), so the app prints each one as a plain sentence that keeps the schedule's meaning.

Trigger Statement printed Source
Royal jelly, or a food that has it as an ingredient (warning statement) This product contains royal jelly which has been reported to cause severe allergic reactions and in rare cases, fatalities, especially in asthma and allergy sufferers. 1.2.3—3
Bee pollen, or a food that has it as an ingredient This product contains bee pollen, which can cause severe allergic reactions. 1.2.3—2(1), S9—2 item 1
A drink made from cereals, nuts or seeds with less than 3% m/m protein This product is not suitable as a complete milk replacement for children under 5 years. 1.2.3—2(1), S9—2 item 2
Such a drink with at least 3% protein and no more than 2.5% fat; milk or a soy drink with no more than 2.5% fat This product is not suitable as a complete milk replacement for children under 2 years. 1.2.3—2(1), S9—2 item 3
Aspartame or aspartame-acesulphame salt (951, 962) This food contains phenylalanine. 1.2.3—2(1), S9—2 item 4
Quinine This food contains quinine. 1.2.3—2(1), S9—2 item 5
Guarana or guarana extracts This food contains caffeine. 1.2.3—2(1), S9—2 item 6
Added phytosterols, phytostanols or their esters When consuming this product, it should be consumed as part of a healthy diet. / This product may not be suitable for children under 5 years and pregnant or lactating women. / Plant sterols do not provide additional benefits when consumed in excess of 3 grams per day. 1.2.3—2(1), S9—2 item 7
A cola drink with added caffeine, or a food that has one as an ingredient This product contains caffeine. 1.2.3—2(1), S9—2 item 8
Propolis, or a food that has it as an ingredient This product contains propolis, which can cause severe allergic reactions. 1.2.3—2(1), S9—2 item 9
Unpasteurised egg products This product is unpasteurised. 1.2.3—2(1), S9—2 item 10
Unpasteurised milk or unpasteurised liquid milk products This product has not been pasteurised. 1.2.3—2(1), S9—2 item 11
Lactitol, maltitol, maltitol syrup, mannitol or xylitol at 10 g/100 g or more, alone or together Excess consumption may have a laxative effect. 1.2.3—2(2)(a)
Erythritol, isomalt, polydextrose or sorbitol at 25 g/100 g or more, alone or together (the same) 1.2.3—2(2)(b)
A mix of the two groups above at 10 g/100 g or more (the same) 1.2.3—2(2)(c)
Added D-allulose in a bakery product, bubble or chewing gum, breakfast cereal, confectionery other than chocolate, a dessert, ice cream or edible ice, a non-alcoholic water-based flavoured drink, or yoghurt (the same) 1.2.3—2(2)(d)

How the app decides:

  • Royal jelly, bee pollen, propolis, aspartame, quinine, guarana, phytosterols, the polyols and D-allulose are tags on an ingredient. Product flags cover the rest: plant_milk (a cereal, nut or seed drink), milk_or_soy_beverage, cola_added_caffeine, unpasteurised_egg, unpasteurised_milk and allulose_food (the food has added D-allulose and is one of the foods in 1.2.3—2(2)(d)).
  • Items 2 and 3 are decided from the food's protein and fat per 100 g (% m/m), unrounded: the limits are on the food's composition, not the printed panel, so 2.96 g protein (printed "3.0 g") is under 3%. A drink's per 100 mL values are taken back to per 100 g with its density. Below 3% protein a plant_milk drink gets item 2; otherwise item 3 when fat is 2.5% or less; above that, no statement (an info message says why). The evaporated and dried forms (as reconstituted) are left to the business.
  • Polyols are counted from ingoing weights over the final weight, the same per 100 g basis as the panel. A tagged ingredient counts in full (maltitol syrup is counted as if it were all maltitol, which can only add a statement, never drop one), and counts once: in the 10 g group if it is tagged with polyols from both groups. A tagged polyol under every threshold gives an info message with the amount and the thresholds.
  • D-allulose tagged without the allulose_food flag gives an info message listing the foods in 1.2.3—2(2)(d). The flag on its own prints the statement.
  • Items 6 and 8 both say the food contains caffeine; with both, the sentence prints once.

Guardrail: the app suggests tags from the ingredient name (royal jelly; bee pollen; propolis; aspartame, aspartame-acesulphame, 951, 962; quinine; guarana; phytosterol, phytostanol, plant sterol, sterol ester; lactitol 966, maltitol 965, mannitol 421, xylitol 967, erythritol 968, isomalt 953, polydextrose 1200, sorbitol 420; allulose). A suggestion the business hasn't confirmed gives a warning on the allergens step that names the statement and its clause, e.g. "Royal jelly: confirm this ingredient is royal jelly so the warning statement is added (1.2.3—3).", and no statement is printed for it.

Size of type and legibility:

  • A warning statement must be at least 3 mm high, or 1.5 mm on a small package (1.2.1—25). A small package has a surface area under 100 cm² (1.1.2—2); the business enters package_surface_cm2, and without it the app uses 3 mm and says so in an info message.
  • Size of type is "the measurement from the base to the top of a letter or numeral" (1.1.2—2): the cap height. The warning statement is set in bold at the minimum height in points divided by the bold face's cap height (OS/2 sCapHeight, 1409/2048 em for Liberation Sans Bold), rounded up to 0.1 pt (artwork.warning_statement_size_pt()): 12.4 pt for 3 mm, 6.2 pt for 1.5 mm. It is never set smaller than the body type. In the sticker PDFs it keeps this size while the shrink-to-fit loop reduces the rest of the label; if the label then doesn't fit, the PDF is refused with a message that gives the warning statement's size. The pack artwork sets it the same way, and its README gives the size.
  • Advisory statements are printed at the body size, like the rest of the label.
  • Every required statement must be in English, legible, and prominent so as to contrast distinctly with the background of the label (1.2.1—24). The app prints black on the label stock.

Not modelled: declarations other than allergens; infant foods and their statements (Standard 2.9.5); formulated caffeinated beverages (Standard 2.6.4); kava; the evaporated and dried forms of the milk replacement drinks; tags found in a compound ingredient's components (the suggestion reads the ingredient's name only).

Net quantity (trade measurement)

Packaged food sold in Australia must show its net quantity under national trade measurement law (National Measurement Act 1960 and National Trade Measurement Regulations 2009, "NTMR"), which is separate from the Food Standards Code. Checked against NTMR Compilation No. 8 (20 Mar 2024). The checks are in net_quantity_check() and net_quantity_min_height_mm() (label.py); the tests are in tests/test_net_quantity.py. Each message ends with its regulation in brackets.

Rule NTMR App
The package must be marked with a statement of the measurement of the product 4.9(1) Warning when the net quantity is missing
A pack under 15 g or 15 mL needs no statement, unless it holds adhesives, dried or freeze-dried vegetables, herbs, instant tea, spices, therapeutic goods or tobacco; it may still be shown 4.4(1), Sch 4 item 5.3 Info when the stated amount is under 15 g or 15 mL
Metric units only, as Schedule 6 permits: kg always, g only up to 1 000 g; L (also dL, cL) always, mL only up to 1 000 mL 4.11(1)(e), 4.13(6), Sch 6 Error for oz, lb, fl oz, g over 1 000 g and mL over 1 000 mL
A decimal below 1 has a zero before the point (fractions are written as decimals, 4.14(1)) 4.11(2) Error for ".5 kg"
Liquids by volume; solid, semi-solid or partly solid goods by mass, unless Schedule 5 says otherwise 4.13(2), (3) Warning when a liquid (panel per 100 mL) is stated in g or kg, or another food (panel per 100 g) in mL or L
No marking by reference to a minimum measurement (except dressed poultry and preserved sausage) 4.16(1) Error for "min", "minimum", "at least" or "approx" in the statement
The AQS mark "e" (℮) is optional; using it brings the average quantity system's sampling obligations 4.1, 4.16A, 4.34–4.44 Warning when the statement contains "e" or "℮" on its own
"Net" or "nett" is not required (none) Allowed either way; the app's example is "Net 250 g"
On the principal display panel, clear, conspicuous and easily read, in a colour that contrasts distinctly with the background 4.9(2), 4.10(1), 4.12(2)(a) Info on every label: put it on the front in a contrasting colour, and repeat it there if this label goes on the back. The app prints black on the label stock
Minimum character height by the package's largest dimension: 120 mm or less 2.0 mm; over 120 to 230 mm 2.5 mm; over 230 to 360 mm 3.3 mm; over 360 mm 4.8 mm 4.12(3) Printed at least this high in the sticker PDFs and the pack artwork (below). Info when the package size is missing: the 2.0 mm band is used
The largest dimension is, for a rectangular package, the greatest of breadth, height and length; otherwise the greatest of height, length and maximum diameter. The height applies to every character, unit symbols included 4.12(4) The user enters package_max_dimension_mm
Serving size × servings per package should equal the net quantity (consistency check) Warning when they differ by more than 10%; not a legal tolerance

Character height. 4.12(4)(c) applies the minimum to each character, unit symbols included, and neither the Regulations nor any NMI guide found says lowercase letters are measured differently. The app takes the conservative reading: the shortest printed characters, the lowercase unit letters ("g", the "m" in "mL"), must reach the minimum at their x-height. The net quantity's type size is the minimum height in points divided by the font's x-height (OS/2 sxHeight, 1082/2048 em for Liberation Sans), rounded up to 0.1 pt (artwork.net_quantity_size_pt()): 10.8 pt for 2.0 mm, 13.5 pt for 2.5 mm, 17.8 pt for 3.3 mm and 25.8 pt for 4.8 mm. Digits and capitals then stand taller, at the cap height (1409/2048 em). If NMI guidance ever says lowercase is measured by cap height, switch to cap height; sizes would drop by about a quarter. The line is never set smaller than the body type. In the sticker PDFs it keeps this size while the shrink-to-fit loop reduces the rest of the label; if the label then doesn't fit, the PDF is refused with a message that gives the net quantity size. The pack artwork sets the net quantity line in the details block the same way, and its README says so and that the line must be on the front.

Not modelled: Schedule 5, which overrides the volume/mass rule for some goods; wine and other goods with their own rules; the dressed poultry and preserved sausage exceptions to the minimum measurement ban; the average quantity system (the "e" mark's sampling, 4.34–4.44) and the tolerances for short measure; multiple-unit packs and the other exemptions in Schedule 4.

Health Star Rating (voluntary)

HSR is not part of the Food Standards Code. It's a voluntary government scheme (Guide Section 1); the HSR website has a page on a proposal to mandate it, so check its status before launch. Sources:

  • Health Star Rating System Implementation Guide v9.1 ("Guide"), Appendix 1.
  • The official HSR Calculator 4.2 workbook (HSR_Calculator_4.2.xlsm), sheets "HSR Calculator", "Points Table A", "Points Table C", "Non-Dairy Beverages ONLY Calc." and "Lookups".

The engine is labelwise/hsr.py. Tests check it against 58 worked cases taken from the Guide and the workbook. They also run 20,000 random inputs through tests/hsr_workbook_port.py, a line-by-line port of the workbook formulas, and require identical results. A 200,000-case run found no differences.

How it works

  • Categories: 1 (non-dairy drinks, assessed per 100 mL), 1D (dairy drinks), 2 (other foods), 2D (dairy foods), 3 (oils and spreads) and 3D (cheese with calcium above 320 mg/100 g). The user picks the category.
  • Inputs: the values as the nutrition panel declares them (3 significant figures), so the rating matches what the official calculator gives for the panel on the pack.
  • Baseline points: energy, saturated fat, total sugars and sodium, per 100 g or 100 mL as sold (as prepared for drinks diluted with water, below; Guide Tables 1–3). Categories 3 and 3D use Table 2 for saturated fat and sugars. Category 1 uses Table 3, where even 0 kJ scores 1 energy point.
  • V points (fruit, vegetables, nuts and legumes, "FVNL"; Table 4): these come from the HSR group tag on each ingredient. The FVNL percentage uses the same base as characterising-ingredient percentages, i.e. ingoing weight less added water removed in processing. The user can override it.
    • With only concentrated (dried) fruit or veg, the concentrated column is used (≥ thresholds).
    • With a mixture, the Guide's formula is used: 100 × (FVNL + 2 × concentrated) / (FVNL + 2 × concentrated + other), rounded to 2 decimal places, then the FVNL column (> thresholds).
    • The app suggests tags from the ingredient name. Every suggestion should be checked: for example, peanut butter counts but cocoa, coconut milk and oils don't.
  • P and F points (protein and fibre; Table 6): the 2-point protein row is "≥ 3.2 g"; every other row is "greater than". Protein counts only when baseline points are below 13 or V points are 5 or more. Category 1 is scored on energy and sugars less V points only, with no saturated fat, sodium, protein or fibre points (Table 3; the workbook); category 1D earns no fibre points (Table 6). So neither drink category earns fibre points, and a drink is never provisional for missing fibre (test_category_1_scores_energy_sugars_and_v_only, test_drinks_never_provisional_for_fibre). The FSANZ ingredient data has no fibre values. While any ingredient has no fibre value, the rating is marked provisional ("3.5 stars or more") with a warning that names the ingredients; the user enters fibre from supplier specifications.
  • Stars: from the score (baseline − V − P − F) using the category's column in Table 7. Calculated category 1 drinks can reach at most 4 stars.
  • Automatic ratings (Guide 4.1): plain water 5 stars; unsweetened flavoured water 4.5 stars; fresh or minimally processed fruit and vegetables 5 stars.
  • As prepared, diluted with water (Guide v9.1, the "as prepared" provisions): a food the directions say to prepare with water only is rated as prepared; preparing with anything else (milk etc.) hasn't been allowed since the 2020 changes. The user enters the parts of water per 1 part of product by volume (4 for "1 part cordial to 4 parts water"). Each per-100 mL value the panel declares is divided by 1 + N, since water has no nutrients; volumes are taken to add up, and the product's specific gravity is taken as 1 for the mixing. The FVNL % is unchanged. Only drink categories (1 and 1D) can be rated this way; any other category is an error. The nutrition panel stays as sold (1.2.8); an as-prepared column is optional and not offered. An info note reminds the user that the directions on the pack must give the same dilution.

Where the Guide and the workbook differ, the app follows the workbook, because it's the official calculator businesses use:

  • Inputs are rounded before scoring, as the workbook instructs: energy and sodium to whole numbers, grams to 0.1 g. Halves round up, as in Excel.
  • In the FVNL column, the workbook gives 8 V points from 99.51% (the concentrated-only column, from 99.5%). The Guide gives them only at 100%.
  • Category 1 takes a single FVNL percentage. Concentrated FVNL isn't treated separately, per the workbook's amendment history.

Not covered: infant foods, sports foods, alcoholic drinks and kava, which are out of scope for HSR. Also not covered: the "as prepared" rules for foods that are drained before eating (the Guide rates them drained), which need the drained food entered as the recipe.

Artwork (labelwise/hsr_mark.py, tools/extract_hsr_artwork.py; Guide Sections 5 and 6)

Rule Source
The graphic is the government's own artwork: the shapes are read from the official layered artwork file (DT0003621, from the HSR Calculator page) and drawn unchanged, only scaled in proportion. Stylised versions aren't permitted Guide 5.2
Only the stars element is drawn ("Health Star Rating" alone, option 4 of the hierarchy). It must be part of every use of the system; the energy and nutrient icons are optional Guide 5.1, 6, Figure 4.4
All five stars are visible, with shading behind the rating's stars, the rating as a numeral, and the words "Health Star Rating" Guide 5.2, 5.3
No graphic is made while the rating is provisional (fibre values missing) consumer law: a rating on pack must be right
The business chooses the size and colour; it must be legible and contrast with the background. The app offers 12–60 mm, in black Guide 5.2
It goes on the front of the pack: it's in the artwork download, and printed on the label only when asked (with_hsr), for labels that wrap the front Guide 5.2

Not implemented: the energy and nutrient icons (options 1–3 of the hierarchy) and the vertical layouts. Their lettering is live text in Helvetica Neue, a commercial typeface the app can't ship, and another typeface would be a stylised version. The Guide encourages businesses to use as many elements as the pack allows, so a business with room for them should use the official artwork file.

To confirm before launch: with the HSR Unit (hsr@health.gov.au) that software may generate the graphic for a business's own food labels. The Guide says the artwork is trademarked, that using it requires compliance with the Guide, and that copyright approval may be needed for uses "other than for food labelling purposes".

Country of origin (mandatory for most food)

The app turns this on for every new recipe and counts it in the label checklist.

Source: Country of Origin Food Labelling Information Standard 2016 (F2016L00528), made under the Australian Consumer Law and enforced by the ACCC, not part of the Food Standards Code. Current compilation F2017C00920 (12 Aug 2017), which is exempt from sunsetting. The Country of Origin Food Labelling Style Guide v2.1 (Aug 2017) and the government's label templates (business.gov.au, May 2025) were used for how the marks look. Section numbers below are sections of the Information Standard. Engine: labelwise/coo.py; artwork: labelwise/coo_mark.py; tests: tests/test_coo.py and tests/test_coo_mark.py. Where the Standard or Style Guide gives an example, a test uses it.

Where the Standard does not apply. Part 2 applies to retail sales (s 14(1)). Under s 14(2) it does not apply to:

  • (a) food sold for immediate consumption by a restaurant, canteen, school, caterer, prison or hospital;
  • (b) food made and packaged on the premises where it is sold;
  • (c) food delivered packaged and ready for consumption at the express order of the purchaser;
  • (d) food sold at a fund-raising event;
  • (e) food for special medical purposes.

The scope check refuses (a) and (e) (see "Scope"). The off switch for the country of origin label exists for (b) to (d).

What label the food needs

Food Label Sections
Priority food grown, produced or made in Australia from only Australian ingredients Kangaroo logo, full bar chart, e.g. "Grown in Australia", "Made in Australia from 100% Australian ingredients" s18
Priority food made in Australia with some imported ingredients Logo, bar chart, "Made in Australia from at least P% Australian ingredients"; under 10% may say "from less than 10%"; 0% says "from 0%" or "from imported ingredients" s19
Priority food packed in Australia, made in one other country A boxed statement "Made in C", or a mark without the logo: "Made in C from (at least P% / 100% / less than 10%) Australian ingredients", optionally with "Packed in Australia" s22(2)–(4)
Priority food packed in Australia, grown, produced or made in one other country with no Australian ingredients Boxed "X C", or a mark with an empty bar chart: "X C" + "Packed in Australia" s22(5)
Priority food packed in Australia from several countries "Packed in Australia from at least P% / less than 10% / 0% / imported ingredients" with bar chart, no logo s22(6)–(8)
Imported priority food Boxed statement "Made in C", "Product of C" or, from several countries, "Packed in C from imported ingredients"; optionally a mark showing Australian ingredients s15(3)–(4), s26
Non-priority food (seasonings, confectionery, biscuits and snack food, bottled water, soft and sports drinks, tea and coffee, alcoholic drinks, as defined in the Dictionary) A plain statement, e.g. "Made in Australia"; the marks are optional s9, s24, s25
Small package (under 100 cm², Food Standards Code 1.1.2—2) The mark's words in a box, without the logo or bar chart s14(3)

"Grown", "produced" and "made" follow s8: grown or produced means every significant ingredient is from that country and virtually all processing happened there; made means last substantially transformed there. If a "grown" or "produced" food has any ingredient that isn't Australian, s18 doesn't apply and the mark says "Made in Australia" (s19). Packed in Australia uses "Packed" or "Packaged" (the user chooses). The optional phrase naming an ingredient's origin ("with prawns from Thailand") must start with "with" and is only offered where the Standard allows it (s19(5), s22(9), s26(5)).

The Australian percentage

  • Share of Australian ingredients by ingoing weight of all ingredients (s11(1)), calculated with exact fractions. It isn't the same base as characterising-ingredient percentages: water lost in cooking isn't removed.
  • A compound ingredient made from both Australian and imported ingredients counts pro rata (s11(4)(b)); the user enters its Australian percentage (the s11 lasagne example is a test).
  • Added water takes the origin of where it was collected (s12(3)), so water in food grown, produced or made in Australia counts as Australian unless the user sets it otherwise. Food that is only packed here had its water added overseas, so the user must set its origin.
  • 'P%' is "a whole number … not more than the proportion" (e.g. s19(2)(b)): always rounded down, so 99.99% is "at least 99%".
  • The user can type the percentage instead, e.g. from supplier records. The Standard requires records to be kept for a year (s31).

The mark

  • Bar chart (s10 table and Style Guide p3, p8): four markings at 20% steps; filled to the 10% step below P for 10–94%, to 95% for 95–99%, to 5% for "less than 10%" and for P under 10, empty for 0%, full for 100%. Tests cover the Style Guide's 77% and 97% examples.
  • Kangaroo logo: vector paths from the government's label template, used unchanged. The logo can only be used as part of a standard mark (s29; Style Guide p9), so the app never shows it alone.
  • Proportions (box, padding, logo and bar sizes, markings) are taken from the government templates and scaled to the chosen type size. Portrait and landscape layouts, as in the Standard; landscape breaks lines between phrases as in the Standard's figures.
  • Colours: Pantone 341 green (CMYK 100/0/67/29) and Pantone 137 gold (CMYK 0/35/90/0) with 100% K text, or one colour (100% K and a 40% K tint), which the Style Guide allows.
  • Typeface: the Style Guide recommends Interstate Bold, a commercial font. Liberation Sans Bold is used instead; the recommendation isn't a requirement.
  • The Standard only requires the text to be legible and to contrast with the background (s28). The Style Guide recommends 3 mm clear space and not using the base; the artwork README says so.

Not covered

  • Marks for Australian content that varies over time, using an average and a way to get details (s21, s23, s13).
  • Food exported and re-imported with processing overseas (s20).
  • Unpackaged food and fresh fruit and vegetables in transparent packaging (s16, s17), and "Australian [kind of food]" / "[C] [kind of food]" wordings.
  • Deciding whether a process is a "substantial transformation": the app explains the test, the business decides.

To confirm before launch: that generating the standard mark, including the kangaroo logo from the government template, inside a paid product is fine with the Department of Industry and the Australian Made Campaign (which owns the logo trade mark). The Standard lets food businesses use it in their labelling (s29), and the government provides the templates for businesses to create their own labels.

Compilations checked

Instrument Title ID Compilation used
Standard 1.1.1 F2015L00383 F2026C00543 (Compilation No. 17, 9 Jun 2026)
Standard 1.1.2 F2015L00385 Compilation No. 21 (9 Jun 2026)
Standard 1.2.1 F2015L00386 F2026C00538 (9 Jun 2026)
Standard 1.2.2 F2015L00389 F2024C00726
Standard 1.2.3 F2015L00397 F2024C01143 (29 Oct 2024)
Standard 1.2.4 F2015L00392 F2025C00914
Standard 1.2.5 F2015L00401 F2021C00660
Standard 1.2.8 F2015L00395 F2026C00411 (30 Apr 2026)
Standard 1.2.10 F2015L00398 F2018C00945
Schedule 7 F2015L00477 As made (1 Apr 2015), the only version on the register
Schedule 8 F2015L00478 Compilation No. 8 (16 Sep 2025)
Schedule 9 F2015L00479 F2021C00195 (25 Feb 2021)
Schedule 11 F2015L00481 F2024C01138
Schedule 12 F2015L00482 F2017C00342
Country of Origin Food Labelling Information Standard 2016 F2016L00528 F2017C00920 (12 Aug 2017)
National Trade Measurement Regulations 2009 F2009L03479 F2024C00311 (Compilation No. 8, 20 Mar 2024)