Electronics Design AU
KiCad

How Do You Automate KiCad with Python Scripting and the pcbnew API?

Last updated 24 July 2026 · 9 min read

Direct Answer

KiCad exposes two automation paths: kicad-cli, a headless command-line tool (KiCad 7+) for running ERC, DRC, and export jobs from a CI pipeline without writing any Python, and the pcbnew Python module, a SWIG-generated API for scripting checks and generation tasks the built-in tools don't cover — custom design-rule logic, BOM field validation, and parametric footprint generation. Most CI pipelines use kicad-cli for standard checks and drop into pcbnew Python only when a rule or output format is genuinely custom.

Detailed Explanation

KiCad supports two distinct automation paths, and picking the right one for a given job matters more than mastering either in isolation.

kicad-cli is a headless command-line tool bundled with KiCad since version 7, expanded in KiCad 8. It runs ERC, DRC, and every major export (Gerbers, drill files, BOM, netlist, pick-and-place, PDF, STEP) directly from a shell command, with no Python required and no GUI session to launch. For CI pipelines, this is the default tool: it is stable across releases, documented, and returns a process exit code that a build system can gate on directly.

The pcbnew Python API is a SWIG-generated binding onto KiCad's C++ PCB editor internals, exposed as a pcbnew Python module. It gives script-level access to the board's object model (footprints, pads, tracks, zones, text items, and their properties) and is the tool for jobs kicad-cli doesn't cover: rule logic specific to your design standards, BOM fields kicad-cli's built-in exporter doesn't group the way you need, and generating footprints programmatically rather than drawing them by hand.

The practical rule: use kicad-cli for anything it already does (ERC, DRC, standard exports), and drop into pcbnew Python only for genuinely custom logic. Writing a Python script to replicate what kicad-cli already does headlessly is extra maintenance for no benefit. See the KiCad topic for the full set of KiCad-specific guides, including the schematic-to-fabrication workflow this scripting layer sits on top of.

Setting Up the Python Environment

KiCad ships its own Python interpreter with the pcbnew module pre-installed, accessible from inside the application via Tools → Scripting Console in the PCB Editor. For CI and standalone scripts run outside the KiCad GUI, the same module needs to be importable from a system Python interpreter, which requires the interpreter to be able to locate KiCad's bundled pcbnew package.

The mechanics differ by platform and change between KiCad releases, so treat exact paths as something to verify against your installed version rather than a fixed constant:

  • Linux: KiCad packages typically install the module under a path like /usr/lib/kicad/lib/python3/dist-packages/, importable once that directory is on PYTHONPATH.
  • macOS: the bundled interpreter is inside the .app bundle (for example /Applications/KiCad/KiCad.app/Contents/Frameworks/Python.framework/...); scripts generally need to run under that bundled interpreter rather than a Homebrew Python.
  • Windows: KiCad installs its own Python distribution alongside the application; scripts run via the KiCad-provided python.exe rather than a system-wide Python install.

For CI, the most reliable approach is running inside the official kicad/kicad Docker image, which ships both kicad-cli and a working pcbnew Python environment pre-configured. This sidesteps the path-discovery problem entirely and is the setup most CI pipelines converge on in practice.

The pcbnew Object Model

A script starts by loading a board file into a BOARD object, then walks its contents:

import pcbnew

board = pcbnew.LoadBoard("project.kicad_pcb")

for footprint in board.GetFootprints():
    ref = footprint.GetReference()
    value = footprint.GetValue()
    x_nm, y_nm = footprint.GetPosition()
    print(f"{ref}: {value} at ({pcbnew.ToMM(x_nm)}, {pcbnew.ToMM(y_nm)}) mm")

Two conventions catch most people writing their first pcbnew script:

  • Coordinates are internal units (nanometres), not millimetres. Every position, dimension, and clearance returned by the API is an integer in KiCad's internal unit. Convert with pcbnew.ToMM() and pcbnew.FromMM() rather than assuming a raw value is already in millimetres.
  • The object model mirrors the PCB Editor's own structures, not a simplified export format. BOARD.GetFootprints(), FOOTPRINT.Pads(), FOOTPRINT.GraphicalItems(), and BOARD.GetTracks() are the entry points used in most scripts; each object exposes the same properties visible in the editor's own properties panels (layer, net, size, rotation).

Scripting Custom Checks Into CI

Standard DRC and ERC gate the build with a single command each and need no Python:

kicad-cli sch erc --exit-code-violations project.kicad_sch
kicad-cli pcb drc --exit-code-violations project.kicad_pcb

Exact flag names have changed between KiCad CLI releases, so confirm the current syntax against kicad-cli pcb drc --help for your installed version before wiring it into a pipeline; treat this as a versioned detail to re-check on upgrade, not a fixed constant.

A pcbnew Python script earns its place when the check is something the DRC/ERC rule engines don't express: a data-completeness or naming-convention rule rather than a geometric one. A common example is verifying that every footprint intended for the BOM has manufacturer part number data populated, since no built-in DRC rule checks this:

import pcbnew
import sys

board = pcbnew.LoadBoard("project.kicad_pcb")
missing = []

for footprint in board.GetFootprints():
    if footprint.IsExcludedFromBOM():
        continue
    mpn_field = footprint.GetFieldByName("MPN")
    if mpn_field is None or mpn_field.GetText().strip() == "":
        missing.append(footprint.GetReference())

if missing:
    print(f"Missing MPN field: {', '.join(missing)}")
    sys.exit(1)

print("BOM field completeness check passed")

Field access methods on FOOTPRINT are an area that has evolved across KiCad 7 and 8 releases, so verify the exact method name against your installed version's API before relying on it in a pipeline. Wiring this into CI alongside the built-in checks looks like:

jobs:
  kicad-checks:
    runs-on: ubuntu-latest
    container: kicad/kicad:8.0
    steps:
      - uses: actions/checkout@v4
      - name: Run ERC
        run: kicad-cli sch erc --exit-code-violations project.kicad_sch
      - name: Run DRC
        run: kicad-cli pcb drc --exit-code-violations project.kicad_pcb
      - name: Run BOM field completeness check
        run: python3 scripts/check_bom_fields.py

Each step fails the build independently, so a violation in any single check is attributable to the step that caught it rather than a single opaque "checks failed" job. Because none of these steps involve a GUI session, this runs cleanly inside a standard container-based CI runner: no xvfb or virtual display is needed for LoadBoard() or the CLI export/check commands.

Automating BOM and Footprint Generation

BOM generation is schematic-derived data, not layout data, so the schematic is the correct source even though the CI checks above operate on the board file. kicad-cli sch export bom produces a CSV or XML BOM directly from the .kicad_sch file using a configurable field-to-column mapping, and is the more reliable route for standard BOM output: it doesn't require populating footprint fields on the PCB side at all. Reach for a custom pcbnew or netlist-parsing script only when the required grouping or output format genuinely isn't achievable through kicad-cli's field mapping, for example consolidating rows by a company-specific part-numbering scheme that spans multiple schematic fields, or emitting a format a procurement system requires that the built-in exporter can't produce. KiCad's Plugin and Content Manager (Tools → Plugin and Content Manager) also distributes community BOM plugins such as Interactive HTML BOM, worth checking before writing a custom exporter. See How to Export Gerber Files from KiCad for PCB Fabrication for where BOM generation fits in the fabrication output workflow.

Footprint generation through the Python API uses a FootprintWizardPlugin subclass rather than pcbnew.LoadBoard(), since this is a parametric generator, not a board-scripting task. A wizard defines a parameter set (pin count, pitch, pad size) and a BuildFootprint() method that constructs pads and outline geometry from those parameters, then registers itself so it appears as a footprint generator option inside the Footprint Editor. This is worth building for footprint families that vary by a small parameter set across many variants, such as a connector series with pin counts from 2 to 40, where hand-drawing each variant risks the pad-count and pitch errors that a parametric generator eliminates by construction. For one-off custom parts, the manual workflow in How Do You Create and Manage KiCad Footprint and Symbol Libraries? is simpler and doesn't carry the maintenance cost of a wizard script.

Design Considerations

  • Default to kicad-cli; reserve pcbnew Python for genuinely custom logic. Every CI check reimplemented in Python is a script that has to be maintained and re-verified against future KiCad releases. If kicad-cli already produces the check or export you need, use it and skip the scripting layer entirely.
  • Pin the KiCad version in CI. The pcbnew Python API and kicad-cli's flag set have both changed across major KiCad releases. Pin the exact KiCad version in your CI container (kicad/kicad:8.0, not kicad/kicad:latest) so a KiCad update doesn't silently change check behaviour or break a script between builds.
  • Fail fast and per-check. Structuring CI as separate ERC, DRC, and custom-check steps (as in the pipeline example above) means a build failure immediately identifies which check failed, rather than requiring a log scroll through a single combined step.
  • Treat custom check scripts as part of the design-review process, not a replacement for it. A scripted BOM field check catches missing data reliably; it does not catch an incorrect part number entered against the wrong field. Automated checks narrow the space of errors a human reviewer needs to look for, but they don't remove the need for review on a production board.
  • Teams standardising a CI-gated KiCad workflow across multiple boards, or building parametric footprint tooling for a large connector or component family, are exactly the kind of production process Zeus Design's PCB design service helps set up alongside the board work itself.

Common Mistakes

  • Writing a custom DRC replica in pcbnew Python instead of using kicad-cli. Reimplementing clearance or annular-ring checks that kicad-cli pcb drc already performs adds a maintenance burden with no benefit, and risks silently drifting out of sync with the DRC engine's actual rule set as KiCad is upgraded.
  • Assuming board coordinates are already in millimetres. pcbnew's internal units are nanometres. Skipping pcbnew.ToMM() / pcbnew.FromMM() conversion produces values that are off by a factor of a million, an error that is obvious once printed but easy to miss inside a larger script until the output is inspected.
  • Not pinning the KiCad container version in CI. A script or CLI flag that works against KiCad 8.0.3 can fail silently or error out against a later point release if the API or CLI surface has changed. Pin the version explicitly rather than tracking latest.
  • Building BOM data from the PCB file instead of the schematic. Footprint fields on the PCB side can drift out of sync with the schematic if the "Update PCB from Schematic" step is skipped after a schematic edit. kicad-cli sch export bom reads directly from the schematic, avoiding this class of stale-data bug entirely.
  • Skipping the display-free verification step. A script that calls pcbnew.LoadBoard() successfully on a developer's desktop machine can still fail in a minimal CI container if the container image is missing a dependency the desktop KiCad install happened to already have. Test the exact CI container image locally before relying on it in a pipeline.

Frequently Asked Questions

Do I need the pcbnew Python API if I only want DRC and ERC in CI?
No. For standard DRC and ERC gating, kicad-cli pcb drc and kicad-cli sch erc cover the built-in rule engines without any Python involved, and both commands return a non-zero exit code on violations, which is all a CI job needs to fail the build. Reach for the pcbnew Python API only when the check is something the DRC/ERC rule engines cannot express — a BOM field completeness check, a naming-convention audit, or a topology rule specific to your design standards.
Can pcbnew scripts run without a display, inside a Docker container?
Yes. pcbnew.LoadBoard() and the object-model calls used for scripted checks do not require a display server — they operate on the board file directly, the same way kicad-cli does. The official kicad/kicad Docker images bundle both kicad-cli and the pcbnew Python module and are built for headless CI use. GUI-dependent operations, such as invoking an Action Plugin through the PCB Editor's toolbar, do need a running KiCad session and are not suited to headless CI.
Does KiCad's Python API also cover the schematic editor, not just pcbnew?
Historically, pcbnew has had by far the more complete Python API; native schematic (Eeschema) scripting has been comparatively limited across KiCad 7 and 8. For schematic-derived outputs like BOM and netlist, kicad-cli sch export bom and kicad-cli sch export netlist are the more reliable route rather than reaching for direct schematic-object scripting. Check the KiCad developer documentation for your installed version before committing to an Eeschema scripting approach, since this is an area of active development between releases.

References

Related Questions

Related Forum Discussions