---
title: "Materials"
description: "Naming the stock a design is made from — pointing at a catalogue family and narrowing it to a board that is actually milled."
url: https://kaicad.barpom.xyz/docs/schema/materials
schema: "0.1"
source: packages/spec/src/design.ts
---

# Materials

A `materials` entry is a **named stock**, not a species and not a board. `from` points at a
catalogue family and the remaining keys narrow it to one real, purchasable size.

```yaml
materials:
  - id: rail_stock
    from: lumber.hardwood.s4s
    species: "=species"
    thickness: 38
    width: 190
```

Parts then say `material: rail_stock`, and expressions can read `rail_stock.thickness`,
`rail_stock.width`, `rail_stock.density` and `rail_stock.modulus_of_elasticity`.

| Key | | |
| --- | --- | --- |
| `id` | required | The name parts and expressions use. |
| `from` | required | A catalogue family id — see [the catalogue](/docs/catalogue/how-the-catalogue-works). |
| `species` | | Required for families that are milled in more than one species. |
| `thickness` | | Millimetres, **actual**, never nominal. |
| `width` | | Millimetres, actual. Omit for sheet goods. |
| `label` | | Overrides the id on the cut list. |

## Declare a stock per role, not per species

The mistake to avoid is one `walnut` material used for everything. Posts, rails and cleats are
different boards; giving each its own entry is what makes the cut list group correctly, lets
the rails change thickness without moving the posts, and gives expressions something specific
to reference.

```yaml
materials:
  - { id: post_stock,  from: lumber.hardwood.s4s, species: "=species", thickness: 75, width: 75 }
  - { id: rail_stock,  from: lumber.hardwood.s4s, species: "=species", thickness: 38, width: 190 }
  - { id: cleat_stock, from: lumber.softwood.s4s, species: pine,       thickness: 25, width: 40 }
  - { id: panel_stock, from: sheet.plywood.birch, thickness: 18 }
```

Note `species: "=species"` — a **text** field, so an expression needs the leading `=`. Three of
those stocks follow the design's species parameter; the cleats are pine because nobody sees
them.

## The size must be one that is milled

The catalogue lists the thicknesses and widths a family is actually available in. A
combination that is not on the list is [`RES-009`](/docs/validation/error-codes#res-009), and
the finding names the nearest sizes that are.

```
RES-009  lumber.hardwood.s4s is not milled 35 mm thick.
         Nearest available: 32 mm, 38 mm.
```

This is a design rule masquerading as a lookup failure. A 35 mm rail is a rail you cannot buy,
and finding that out at the timber yard is worse than finding it out here.

## What a resolved material carries

Narrowing a family gives the rest of the pipeline more than two numbers. The species
properties come with it, and the validator reads them:

- **density** — for the weight on the BOM and the load checks
- **modulus of elasticity** — for shelf and rail deflection
- **movement, tangential and radial** — for the wood-movement checks on panels and tops
- **screw withdrawal, face and end** — for the fastener checks
- **rough allowance** — extra on each dimension for the cut list's rough sizes

A pine cleat and a walnut rail take the same screw and hold it differently, and the report
says so because the material knows which it is.

## Sheet goods

Sheet materials have a thickness and a sheet size, not a width:

```yaml
- id: panel_stock
  from: sheet.plywood.birch
  thickness: 18
```

They also carry their own properties rather than inheriting from a species, because a panel
behaves the way it does because of how it is built, not what tree it came from. Plywood barely
moves in plane; MDF holds a screw poorly in the edge and very poorly in the end.

`sheet.veneered.mdf` is the exception that takes a `species`, because the veneer is
directional and the sheet has a show face.

## Sheet goods still need a grain direction

Plywood is stiff along the face grain and noticeably less so across it, and veneered panels
have a visible direction. Set `grain:` on the [part](/docs/schema/parts), not the material —
two parts cut from the same sheet can run different ways, and the nesting is worked out from
the parts.

## Rough sawn versus surfaced

`lumber.hardwood.rough` states the size you **start** with; `lumber.hardwood.s4s` states the
size you **finish** at. The rough family carries larger allowances — 6 mm on thickness against
3 mm — so the cut list asks for enough board to flatten.

If you are unsure, use the surfaced family. It is what a merchant hands over.

## The families available

| Family | Class | |
| --- | --- | --- |
| [`lumber.hardwood.s4s`](/docs/catalogue/solid-lumber) | solid | Nine species, 19–75 mm, 50–290 mm wide |
| [`lumber.softwood.s4s`](/docs/catalogue/solid-lumber) | solid | Five species, 18–75 mm, 40–245 mm wide |
| [`lumber.hardwood.rough`](/docs/catalogue/solid-lumber) | solid | Rough sawn, larger allowances |
| [`sheet.plywood.birch`](/docs/catalogue/sheet-goods) | sheet | 4–30 mm, three sheet sizes |
| [`sheet.plywood.poplar`](/docs/catalogue/sheet-goods) | sheet | 6–25 mm, cheaper and softer |
| [`sheet.mdf`](/docs/catalogue/sheet-goods) | engineered | 6–30 mm |
| [`sheet.veneered.mdf`](/docs/catalogue/sheet-goods) | engineered | 18–25 mm, five veneers |

Full specification tables, including every milled size and the per-species properties, are in
the [catalogue section](/docs/catalogue/how-the-catalogue-works).
