Skip to content

Extracting a Structured Ingredient and Dosage Table From a Supplement Facts Panel

9 min read · updated August 11, 2026

Half the cells you would call missing on a Supplement Facts panel are required to be empty. A schema that treats them as extraction failures will report a defect rate that is really a compliance rate.

It is not a Nutrition Facts panel

They look alike and they are governed by different sections of the same regulation. In the United States, the Nutrition Facts label on conventional food is specified at 21 CFR 101.9, while the Supplement Facts panel on a dietary supplement is specified separately at 21 CFR 101.36. The differences are not cosmetic and they change the extraction.

The supplement panel may list dietary ingredients that have no established Daily Value at all — botanicals, amino acids, various compounds — which the food panel effectively never does. It requires the part of the plant to be declared for botanical ingredients. It permits proprietary blends with a single total weight. And it puts a category of ingredients below a horizontal rule, in a section headed “Other ingredients”, which sits outside the panel proper and is a plain list rather than a table. Treating the document as a Nutrition Facts panel with different words gets every one of those wrong.

A blank %DV is data

The panel has an amount-per-serving column and a percent Daily Value column. For an ingredient with an established Daily Value — vitamin C, calcium, iron — the %DV is calculated and printed. For an ingredient with no established Daily Value, there is nothing to calculate, and the convention the regulation sets out is a symbol, conventionally a dagger, keyed to a footnote reading that a Daily Value is not established.

So the cell is not empty because the printer ran out of ink or because the manufacturer omitted something. It is empty because the value does not exist. That distinction has to survive into your schema, because the downstream consumer of a null cannot tell an absent value from a value you failed to read:

{
  "serving_size": "2 capsules",
  "servings_per_container": 30,
  "amount_column_header": "Amount Per 2 Capsules",
  "ingredients": [
    { "name": "Vitamin C (as ascorbic acid)",
      "amount": 250, "unit": "mg",
      "dv_percent": 278, "dv_status": "established" },
    { "name": "Turmeric (Curcuma longa) root extract",
      "amount": 500, "unit": "mg",
      "dv_percent": null, "dv_status": "not_established",
      "footnote_symbol": "dagger",
      "plant_part": "root" },
    { "name": "Bioperine",
      "amount": null, "unit": null,
      "dv_percent": null, "dv_status": "not_established",
      "amount_absent_reason": "in_proprietary_blend",
      "blend_ref": "Absorption Blend" }
  ],
  "other_ingredients": ["hypromellose", "rice flour", "magnesium stearate"]
}

Three values where a lazy schema has one null. dv_status of not_established is a positive statement about the world; amount_absent_reason of in_proprietary_blend is a positive statement about the label; and a genuine read failure is a third thing that should be flagged as one. If you can only afford one extra field on this document, make it the reason a value is absent — which is the general lesson of handling a missing required field, sharpened here by a regulation that makes some absences mandatory.

Units, and labels that predate the current rule

Units on this panel are heterogeneous and a couple of them changed. The 2016 US labelling rule revised how several nutrients are declared: folate moved to micrograms of dietary folate equivalents with folic acid declared parenthetically, vitamin A and vitamin E moved away from International Units to weight units, and Daily Values themselves were updated. Products on shelves and in image archives include labels printed under both regimes.

  • Never normalise across the boundary silently. A conversion from International Units to micrograms depends on the chemical form, so it is not a constant factor. If you convert, record the form you assumed and the factor you used as fields, and keep the printed value untouched.
  • mcg and mg differ by a thousand and by one glyph. The micro sign renders inconsistently across OCR engines and can come back as u, µ or nothing at all, which turns 400 mcg into 400 mg. Range-check every amount against a plausible range for that ingredient and flag rather than correct.
  • Compound declarations are one ingredient. “Folate (as L-5-MTHF) 680 mcg DFE (400 mcg folic acid)” is a single row with a qualified unit and a parenthetical. Splitting it into two ingredients doubles the folate in your data.
  • The amount column header is not always “per serving”. It frequently names the serving explicitly. Extract the header text, because a reader comparing two products needs to know whether the numbers are per capsule or per two.
Daily Values, permitted units and the rounding conventions for this panel are set by regulation and revised from time to time. Any table of Daily Values embedded in your pipeline should be versioned and dated against the regulation rather than treated as a constant, and the current text at eCFR is the authority rather than any summary, including this one.

Proprietary blends withhold amounts by design

A proprietary blend appears as a named row with a total weight, followed by an indented list of ingredients with no individual amounts. The regulation permits this, and it requires the blend’s components to be listed in descending order of predominance by weight. That last detail is the only quantitative information available about the components, and it is carried entirely by list order.

Which means order is data on this document. An extraction that returns the blend’s ingredients as an unordered set, or that sorts them alphabetically on the way out, has destroyed the one fact the label was required to convey. Model a blend as its own object with a total, a unit and an ordered array, and give each component an explicit rank.

Nesting is also the part most likely to be lost at the layout level, and it is the case that decides whether the schema is nested or flat for you. The indentation that distinguishes a blend component from a top-level ingredient is a few millimetres, and it does not survive a text-extraction pass that returns lines without coordinates. If your pipeline flattens the panel to text before the model sees it, blend membership is gone before extraction starts — a reading-order problem rather than a prompting one, of the kind PDF parsing covers in general.

The edges of the panel

Two boundaries cause trouble. The first is the bottom of the panel: the footnotes and the “Other ingredients” list sit below the rule and are not part of the ingredient table, but they are physically adjacent and models pull them in. Excipients arriving as dietary ingredients with null amounts is the signature. Ask for them as a separate list and the problem mostly disappears.

The second is the panel’s physical form. These labels are printed on curved bottles in small type, photographed by hand, and often wrapped so the panel bends away from the camera. Character height at the edge of a wrapped label can drop below what any engine reads reliably, and the honest response is a per-row legibility flag rather than a best guess. A supplement label is a small, dense, high-stakes table where a single misread digit changes a dose by an order of magnitude, which is a strong argument for keeping a fixed regression set of real panels — blends, daggers, IU-era labels, wrapped photographs — rather than trusting that a prompt that worked last month still does.