Package authoring

A package extends Package — the base class for reusable, importable hardware modules. Its build() creates the parts and wires them once; consumers get the whole sub-circuit from one constructor call. Consuming packages is covered in Packages.

command

typecad-pcb add package

Scaffolds a package in ./src: package.json, the package class, and (for component packages) a generated component file. Run it from the hw/ directory.

Example first

terminal
cd hw
typecad-pcb add package --component=true --kicad=true \
  --symbol=MCU_Microchip_ATtiny:ATtiny3227-M \
  --footprint=Package_SO:SOIC-20W_7.5x12.8mm_P1.27mm \
  --name=my-tiny-board
FlagMeaning
--empty=true | --component=trueExactly one — an empty utility package or one wrapping a component
--name=<name>Package and class name
--kicad=true | --local=true | --jlcpcb=trueWhere the component comes from (component packages)
--symbol= / --footprint=KiCAD library paths for --kicad
--c=<C-number>JLCPCB part number for --jlcpcb
--folder=<path>Output directory (default ./src)

What's generated

PathWhat it is
package.jsonnpm metadata — publish like any other package
src/index.tsThe package class — what consumers import
src/<component>.tsThe generated component file (component packages)
./build/Symbols, footprints, and 3D models the package ships
base class

Package

The abstract base class. Extend it with your own options as the generic parameter; build() does the work. Construction handles everything else: offset positioning from x/y, part collection, grouping, and the build/lib sync.

Example first

index.ts
import { Package, Power } from '@typecad/pcb';
import { ATtiny3227_M } from './ATtiny3227_M';

export interface TinyBoardOptions {
  vin?: Power;          // caller's supply — the package wires itself to it
}

export class TinyBoard extends Package<TinyBoardOptions> {
  declare u1: ATtiny3227_M;
  declare c1: Component;
  declare power: Power;

  build(options: TinyBoardOptions) {
    this.u1 = new ATtiny3227_M();
    this.c1 = new this.passives.Capacitor({ value: '100nF' });

    this.net(this.u1.VCC, this.c1.pin(1));
    this.net(this.u1.GND, this.c1.pin(2));

    if (options.vin) {
      this.net(options.vin.power, this.u1.VCC);
      this.net(options.vin.gnd, this.u1.GND);
    }
  }
}

Inside build()

MemberPurpose
build(options)required — create the parts, wire them, position them. Runs once at construction
this.passivesPassive classes bound to one size — passiveSize option, ’0603’ default
this.net(...pins)Connects pins inside the package
this.track()Manual tracks — register them with this.add(...) so they’re collected
this.via(at, size?, drill?)A via inside the package
this.add(...items)Add anything the auto-collector can’t see (tracks)
this.componentsEverything the package placed — consumers spread it into create()

Collection

Every Component or TrackBuilder assigned to a property of this during build() is collected automatically, offset by the consumer's x/y, and grouped under the package name:

collection.ts
export class TinyBoard extends Package {
  declare r1: Component;

  build(options) {
    this.r1 = new this.passives.Resistor({ value: '10kohm' });
    // no this.add() needed — assigned properties are collected automatically
  }
}

The consumer's view

use.ts
let board = new TinyBoard({ pcb: typecad, x: 40, y: 30, vin: usb_power });
typecad.create(...board.components);
workflow

publishing

Packages are ordinary npm packages — version them, tag them, publish them publicly or to a private registry. Semantic versions mean consumers can pin and update hardware like any dependency.

Example first

terminal
npm version patch
npm publish --access public

Utility packages — BOM post-processors, fab exporters, lint rules — are the same story with no hardware: plain npm packages whose code imports the consumer's PCB programmatically.

© 2026 typeCAD — code-defined electronics github