Create or Use a Custom Autorouter
Use algorithmFn when you want to create or use a custom autorouter in your
tscircuit project without running a separate autorouter server. The function
receives the board's SimpleRouteJson routing problem and returns a
GenericLocalAutorouter.
We recommend using the default autorouter; custom autorouters are for advanced use cases.
This example recreates the
tscircuit/example-custom-autorouter
project. The board is a four-wire crossover adapter. The custom router draws
Manhattan routes, cycles traces across the available layers, and uses obstacles
from the SimpleRouteJson input to route around the center mounting hole.
import { SimpleCustomAutorouter } from "./src/simple-custom-autorouter"
const LEFT_HEADER_X = -13
const RIGHT_HEADER_X = 13
export default () => (
<board
width="34mm"
height="22mm"
layers={4}
minTraceWidth={0.18}
nominalTraceWidth={0.22}
minViaPadDiameter={0.55}
minViaHoleDiameter={0.3}
pcbStyle={{
viaPadDiameter: 0.55,
viaHoleDiameter: 0.3,
}}
autorouter={{
algorithmFn: async (simpleRouteJson) =>
new SimpleCustomAutorouter(simpleRouteJson),
}}
>
<pinheader
name="J_LEFT"
pinCount={4}
pitch="2.54mm"
pcbX={LEFT_HEADER_X}
pcbY={0}
pcbOrientation="vertical"
schX={-5}
schY={0}
schFacingDirection="right"
/>
<pinheader
name="J_RIGHT"
pinCount={4}
pitch="2.54mm"
pcbX={RIGHT_HEADER_X}
pcbY={0}
pcbOrientation="vertical"
schX={5}
schY={0}
schFacingDirection="left"
/>
<hole name="CENTER_MOUNT" diameter="3.2mm" pcbX={0} pcbY={0} />
<silkscreentext
text="CUSTOM ROUTER"
pcbX={0}
pcbY={9}
fontSize="1mm"
anchorAlignment="center"
/>
<trace from=".J_LEFT > .pin1" to=".J_RIGHT > .pin4" />
<trace from=".J_LEFT > .pin2" to=".J_RIGHT > .pin3" />
<trace from=".J_LEFT > .pin3" to=".J_RIGHT > .pin2" />
<trace from=".J_LEFT > .pin4" to=".J_RIGHT > .pin1" />
</board>
)
Use an Autorouter in a Board
Pass an object with algorithmFn to the autorouter prop. The function can
construct your own router, call a library, or wrap an existing routing
implementation:
<board
autorouter={{
algorithmFn: async (simpleRouteJson) =>
new SimpleCustomAutorouter(simpleRouteJson),
}}
>
{/* components and traces */}
</board>
algorithmFn receives a SimpleRouteJson object. Return an object that
implements GenericLocalAutorouter, usually with these methods:
start()to begin asynchronous routing and emit acompleteeventstop()to cancel routingon("progress" | "complete" | "error", callback)to report router statesolveSync()to returnSimplifiedPcbTrace[]synchronously when supported
Create a Router
The router output is a list of simplified PCB traces. Each trace should use the
source trace ID when one is present, then return a route made of wire and
optional via points.
The example's SimpleCustomAutorouter is intentionally small, but it shows the
full shape: event handlers, sync solving, trace IDs, route widths, layer
selection, obstacle checks, and progress/error reporting.
Customize Implicit Breakout Points
Use implicitBreakoutPointSolverFn when the normal autorouter should still
route traces, but your application needs to choose where connections leave
<breakout /> regions. The callback runs during
PCB layout and must synchronously return the PCB-world positions and layers of
the generated breakout points.
import type { ImplicitBreakoutPointSolverFn } from "@tscircuit/props"
const placeBreakoutPoints: ImplicitBreakoutPointSolverFn = (input) => {
const connections = input.connections.flatMap((connection) =>
"type" in connection ? connection.connections : [connection],
)
return {
breakoutPoints: input.regions.flatMap((region) => {
const regionConnections = connections.filter((connection) =>
connection.endpoints.some(
(endpoint) => endpoint.regionId === region.regionId,
),
)
return regionConnections.map((connection, index) => {
const offset = input.boundaryPointSpacing * (index + 1)
return {
regionId: region.regionId,
connectionId: connection.connectionId,
layer: "top",
x:
region.edge === "left"
? region.bounds.minX
: region.edge === "right"
? region.bounds.maxX
: region.bounds.minX + offset,
y:
region.edge === "bottom"
? region.bounds.minY
: region.edge === "top"
? region.bounds.maxY
: region.bounds.minY + offset,
}
})
}),
}
}
export default () => (
<breakout
autorouter={{ implicitBreakoutPointSolverFn: placeBreakoutPoints }}
>
{/* components */}
</breakout>
)
A production solver should also check boundary length, bus ordering, differential pairs, and layer constraints.
The input contains:
| Field | Description |
|---|---|
regions | Breakout IDs, rectangular bounds, and selected boundary edges. Coordinates are PCB world-space millimeters. |
connections | Ordinary connections or differential pairs. Each endpoint identifies its region and position. The type includes optional externalDestination, but Core does not currently populate it. |
buses | Bus IDs, member connection IDs, and optional ordered target layers. |
boundaryPointSpacing | Recommended spacing between generated boundary points, in millimeters. |
Return { breakoutPoints } synchronously. Every point must provide regionId,
connectionId, layer, x, and y on the matching region boundary, with
exactly one point per (regionId, connectionId) pair; differential-pair
members count separately. Missing, unknown, or duplicate pairs and Promise
returns all cause layout to fail.
Run the Custom Autorouter Example Locally
git clone https://github.com/tscircuit/example-custom-autorouter
cd example-custom-autorouter
bun install
bun run typecheck
bun run build
Use bun run dev to open the interactive tscircuit viewer.
For a custom autorouter that runs as an HTTP service instead of inside the tscircuit process, see the Autorouting API.