Getting started
pcbjs needs Node 22.12 or newer. @beta is the npm tag until 0.1; latest holds a placeholder with no code, so name the tag.
Make a project
npm install -D @pcbjs/pcbjs@beta
npx pcbjs create my-board
cd my-board
npm installpcbjs create writes a board, pcb.tsx, a config, pcb.config.ts, that lists it as the project's entry, a tsconfig.json with JSX pointed at @pcbjs/core, and a package.json with dev, build and preview scripts. Quick start has every file.
The loop
npx pcbjs check # compile, route, DRC and ERC; one line per board, then the findings
npm run dev # the viewer on http://localhost:5173, recompiled on every save
npx pcbjs inspect U1 # where a part and its pads ended up, in absolute mm
npm run build # the fab set in .pcbjs/fab/my-board and the site in .pcbjs/distEdit, run check, read the findings, repeat. A finding gives the rule, the position, the parts and the source line to change. When check says ok, build writes the Gerbers, drills, BOM, placement file and a zip to send to the fab.
From source
For contributors: clone https://github.com/pcbjs/pcbjs, run npm install and npm run build at its root, and make projects under examples/, which join the workspace. Development has the layout and the checks.
A larger board
The template is one LED. Blinky, a demo board in examples/demo, shows how a board grows: the schematic module blinky.sch.tsx holds the nets, the parts and the schematic picture, and the layout blinky.pcb.tsx places them by name.
The schematic: three LED channels are instances of one circuit(), so each gets its own resistor, LED and private net, and findings say R2 (CH2/R).
import { nets, Schematic, Sheet, Power, Text, wire } from '@pcbjs/core'
import { PinHeader, C0603 } from '@pcbjs/lib'
import { SOT23 } from './lib/MyPart'
import { ledChannel } from './lib/led-channel.sch'
export const { GND, VCC, SIG, SW } = nets()
export const J1 = PinHeader(3) .connect(GND, VCC, SIG)
export const Q1 = SOT23('MMBT3904', { MPN: 'MMBT3904LT1G', Manufacturer: 'onsemi' }).connect(SIG, GND, SW)
// decoupling on the supply
export const C1 = C0603('100n') .connect(VCC, GND)
// the channels: CH1.R is a resistor, CH1.nets.led the net between it and CH1.D
export const CH1 = ledChannel({ VCC, SW }, 'CH1')
export const CH2 = ledChannel({ VCC, SW }, 'CH2')
export const CH3 = ledChannel({ VCC, SW }, 'CH3')
// the picture: a part inside <Schematic> is its symbol; where each sits and the wires drawn by hand, in grid units of 2.54 mm;
// GND and VCC get power symbols from the compiler, and C1 is placed but wired by it too
const channel = (ch: typeof CH1, y: number) => (
<Sheet group={ch} at={[23, y]}>
<ch.R at="2 2" />
<ch.D at="8 2" />
{wire(ch.R[2]).Z(ch.D.A)}
</Sheet>
)
export const drawing = (
<Schematic>
<J1 at="4 7" />
<Q1 at="13 10" />
<C1 at="6 18" rot={270} />
{wire(J1[3]).Z(Q1[1])}
<Power net={GND} at="14 12" kind="gnd" />
{channel(CH1, 5)}
{channel(CH2, 12)}
{channel(CH3, 19)}
{wire(CH1.D.K).h(2).v(14).Z(CH3.D.K)}
{wire(CH2.D.K).h(2)}
{wire(Q1[3]).V(3).H(35).V(7)}
<Text at="4 26">Three LED channels switched low by Q1</Text>
</Schematic>
)The layout: schematic={sch} puts every export of the module in scope, so <J1 /> and route(Q1[2]) need no import. Tracks are drawn by hand where they matter and left to the autorouter with .X() where they do not.
import PCB, { route, pour, via } from '@pcbjs/core'
import { Brand, rect, roundedRect } from '@pcbjs/lib'
import * as sch from './blinky.sch'
import './blinky.sourcing'
import { LedChannelRow } from './lib/led-channel.layout'
const W = 30, H = 20
export default (
<PCB name="Blinky" revision="0.1.0" copper={2} schematic={sch}
rules={{ clearance: 0.2, minTrackWidth: 0.2, strictNets: true, requireSourcing: true }}
autoroute={{ bend: '45', viaCost: 4 }}>
<path layer="Edge.Cuts" d={roundedRect(W, H, 2)} />
{/* J1, Q1 and C1 by hand, each channel by its circuit's row layout */}
<J1 at="3 7.46" />
<Q1 at="26 10" />
<LedChannelRow g={CH1} at="0 5" label="LED1" />
<LedChannelRow g={CH2} at="0 10" label="LED2" />
<LedChannelRow g={CH3} at="0 15" label="LED3" />
<C1 at="15 10" side="B" />
<text layer="B.SilkS" at="15 8.3" size={0.8}>C1</text>
{/* tracks as path strings, a via, and a chain whose last hop .X() leaves to the autorouter */}
{route(VCC, { width: 0.3, d: 'M 3 10 H 9 M 9 5 V 15 M 9 11.6 H 15.8' })}
{via(VCC, '15.8 11.6')}
{route(VCC, { layer: 'B.Cu', width: 0.3, d: 'M 15.8 11.6 V 10' })}
{route(SW, { width: 0.3, d: 'M 22.2 5 V 16.5 H 27 V 10' })}
{route(J1[3]).V(14).L(5, 16).via('B.Cu').H(24).V(7.5).via('F.Cu').X(Q1[1]).width(0.25)}
{/* GND: a pour over the back, Q1.2 autorouted into it */}
{route(Q1[2]).X('25 12.5 B.Cu').width(0.3)}
{pour(GND, 'B.Cu', rect(W, H))}
<text layer="F.SilkS" at="3 4.3" size={0.8}>GND</text>
<text layer="F.SilkS" at="15 18.5" size={1}>Blinky · pcbjs</text>
<Brand at="15 4" height={5} layer="B.SilkS" />
</PCB>
)Both files are the checked-in demo, kept clean by pcbjs check. Run it in examples/demo to see the board pass, then move a part onto another or misspell a pin to see a finding or a type error with its line.
Next
- Quick start: the project files, the config and where things go in a board file.
- Schematic and Layout: nets, parts and the elements a board is drawn with.
- Routing and Autorouter: manual tracks, automatic legs, pairs and lengths.
- Findings: every DRC and ERC rule and what makes a board orderable.
- Examples: the demo boards and the review boards, with their numbers.