This is a technical reference for Bower’s native .bower file format, intended for anyone reading, generating, or reverse-engineering a saved draft. It describes what the current release of Bower writes and reads.
The .bower file is Bower’s own save format. It is not WIF. WIF is a separate plain-text interchange format that Bower imports and exports through a different path; for that format see the WIF 1.1 File Format Specification. Where a WIF round-trip is lossy, a .bower round-trip is lossless.
1. Container format
A .bower file is a single plain UTF-8 JSON document. It is not a ZIP archive and not WIF text. The hand-save path writes it pretty-printed with two-space indentation; the project-library and autosave paths write the same schema compactly. The saved filename takes the form draft_2026-08-19T14-32-05.bower — an ISO 8601 timestamp with colons and periods replaced by hyphens.
The loader identifies a file by parsing it as JSON and reading the version field. On load Bower also accepts .loom and .json alongside .bower; all three are parsed as the same JSON schema.
The top level is a single flat JSON object. There is no plugin-keyed envelope: one store owns the entire document, so a draft is one object with a version and its fields, not a map of per-plugin sections.
2. The version field
version is a plain number, not a semver string. The loader accepts versions 1 through 7; a numeric version outside that set is rejected with an error, which is how a newer file opened by an older build fails cleanly rather than loading corrupt.
The current release writes two of those versions, depending on how you save:
- Version 5 — written by the “Save
.bower” download. The full draft body described here. - Version 7 — written by the project library (the on-device draft browser). Identical to version 5, plus a
metablock and an embeddedthumbnail. - Versions 1–4 and 6 — legacy shapes the loader still reads but never writes.
Parsing is driven by the document’s shape rather than by a version switch. Version 1 stored grids as raw number arrays; version 2 onward stores them as bit-packed base64 strings, and the loader distinguishes the two by inspecting the threading field’s type. Optional features (profile authoring, embedded yarn models) are detected by the presence of their keys, not by version number.
3. Document shape
A version 5 document has the following top-level keys:
{
"version": 5,
"dimensions": { "numWarps": 24, "numShafts": 8, "numTreadles": 10, "numWefts": 24 },
"loomMode": "tieup-treadling",
"authoringMode": "direct",
"threading": "<base64>",
"tieups": "<base64>",
"treadling": "<base64>",
"liftplan": "<base64>",
"warpColors": [ "#ffffff", "..." ],
"weftColors": [ "#5e7707", "..." ],
"yarnThicknesses": { "#ffffff": 1 },
"yarnModels": { "#ffffff": { "..." : "..." } },
"yarnOrder": [ "#ffffff", "#5e7707" ],
"epi": 24,
"ppi": 24,
"multipleShaftsPerWarp": false,
"multipleTreadlesPerWeft": false
}A version 7 document is the same body with two additions: a meta object (title, createdAt, updatedAt) and a base64 thumbnail PNG used by the draft browser.
4. Grids and bit-packing
A weaving draft is a set of boolean grids. Four are stored in the file; the fabric image is derived, not stored:
threading— which shaft each warp end is threaded on (numWarps×numShafts).tieups— which shafts each treadle lifts (numTreadles×numShafts).treadling— which treadle is pressed for each weft pick (numTreadles×numWefts).liftplan— which shafts lift for each pick (numShafts×numWefts), used in liftplan mode.
Each grid is one byte per cell in memory, row-major, and is bit-packed to a base64 string on save. Grid dimensions come from dimensions: numWarps and numWefts range 1–4096, and numShafts and numTreadles range 1–64.
The drawdown (the woven-cloth image) is not in the file. It is recomputed on load from the threading, tie-up, and treadling — or from the liftplan, depending on loomMode, which is either "tieup-treadling" or "liftplan".
5. Colors and the yarn palette
Thread colors are stored per end and per pick as hex strings: warpColors has exactly numWarps entries and weftColors has exactly numWefts. The defaults are #ffffff for warp and #5e7707 for weft.
yarnThicknesses— a map from hex color to a thickness multiplier.yarnOrder— a stable, append-only ordering of the palette colors, used for the yarn panel. If absent, it is reconstructed from warp-then-weft first-seen order.
6. The physical yarn model
yarnModels maps each hex color to a full physical yarn model. This is the same shared model Asunder Knit uses; a convention field ("weaving" or "knitting") is the seam where one model serves both editors.
{
"id": "id_0",
"name": "8/2 Cotton",
"fibers": [ { "fiber": "cotton", "percentage": 100 } ],
"wpiThickness": 30,
"plyCount": 2,
"twistDirection": "S",
"tpi": 8,
"convention": "weaving",
"color": "#5e7707",
"gristTex": 60,
"elasticityMPa": 8,
"frictionMu": 0.3,
"stiffnessEI": 0.0002,
"compressibilityKPa": 50,
"sheen": 0.15
}The model captures fiber content as a blend that sums to 100%, thickness in wraps-per-inch, ply count and twist (S or Z direction, turns-per-inch), and the physical constants the yarn simulation uses — grist (linear density), elasticity, friction, bending stiffness, compressibility, and sheen. Older files that predate individual fields have them back-filled from derivations and defaults on load.
7. Sett and metadata
epi and ppi record the sett — ends and picks per inch (each 1–1000, default 24). They are persisted as metadata and do not change the on-screen geometry.
A title and timestamps live only in the version 7 meta block (title, createdAt, updatedAt as ISO strings). A hand-saved version 5 file has no meta; when such a file is taken into the draft library, its title is derived from the filename and its timestamps from the file modification time.
8. Profile drafting
Bower can author a draft from a profile (block) design rather than by direct threading. authoringMode is "direct" or "profile", and a profile draft additionally carries a blockSubstitution object. It is present only for profile-authored drafts.
On disk the profile is label-keyed for stability (blocks named A, B, …) even though it is index-keyed at runtime. It stores the block definitions, the tie-up column labels, and a packed sub-draft:
{
"blockDefinitions": {
"A": { "label": "A", "threadingUnit": [1, 2, 3, 4] },
"B": { "label": "B", "threadingUnit": [4, 3, 2, 1] }
},
"tieupColumnLabels": ["A", "B"],
"blockDraft": {
"numWarps": 8, "numShafts": 4, "numTreadles": 6, "numWefts": 8,
"threading": "<base64>", "tieups": "<base64>", "treadling": "<base64>"
},
"engine": "custom",
"customTreadlingUnits": { "A": [1, 2] }
}The trailing fields depend on the profile engine (for example a preset id or an overshot variant). Threading units are written for every engine, but only the custom engine’s units are read back — structured engines re-derive their units from the engine parameters on load.
9. What is not in the file
The only embedded asset is the version 7 thumbnail, a base64-encoded PNG preview of the drawdown for the draft browser. There is no other binary payload — the grids are base64 text, not raw binary.
- The drawdown is derived on load, never stored.
- View and UI settings (warp/treadling axis, fabric face, PNG export preset) live in local storage, not in the
.bowerfile. - The direct-edit paint session is a transient editing layer and is not serialized.
10. Load validation and recovery
Loading does more than parse JSON. The parser is pure and validates before any state is mutated, so a rejected file never leaves a half-loaded draft:
- Dimensions must be present, integer, and ≥ 1, or the file is rejected as corrupt.
- Version must be in 1–7, or the file is rejected as unsupported.
- Grid shape must match the version (raw arrays for version 1, packed base64 strings for version 2+).
- Color lengths must match:
warpColors.length === numWarpsandweftColors.length === numWefts. - Yarn models are validated (fiber percentages sum to 100, hex color, valid twist, positive physical constants).
- Profiles are repaired where possible: a profile that fails to migrate is dropped and the draft downgraded to direct mode rather than failing the load.
Loading is transactional at the call site: the current draft is snapshotted first and restored if the load throws. The autosave path uses this same serialized shape.
11. Relationship to WIF
WIF is a separate import/export path, not a container the .bower file wraps. A .bower file never contains WIF text.
- Import parses WIF text into a neutral draft and translates it into a version 1
.bowerobject, then loads it through the same door as a native file. - Export reads the live draft and emits WIF 1.1 text, choosing liftplan or tie-up/treadling sections to match the loom mode.
Because WIF export currently drops data the native format keeps — the physical yarn models, the profile authoring model, the palette ordering, thumbnails and metadata, and yarn thickness and sett — a WIF round-trip is lossy. A .bower round-trip preserves everything.
12. Compatibility and versioning
- Files use the
.bowersuffix;.loomand.jsonare also accepted on load. - The current release writes version 5 (hand save) and version 7 (draft library).
- Versions 1–4 and 6 still load but are never written.
- The format contract is the JSON contents, not the filename suffix.
Open Bower and save a draft to inspect the format directly →