Kicad-sch-api: Python library for KiCAD schematic manipulation

I built a Python library for reading and writing KiCAD schematic files. I use it for Circuit-Synth, but figured others might find it useful so I made a separate project for just this part.

GitHub: GitHub - circuit-synth/kicad-sch-api: API for manipulating sexpr in KiCAD schematic editor
PyPI: pip install kicad-sch-api


Why I Built This

I needed programmatic schematic generation for Circuit-Synth and wanted to decouple the core schematic logic into a standalone library. Design goals:

  1. Simple - Straightforward API, no complexity
  2. Format-preserving - Output attempts to matche KiCAD exactly
  3. Standalone - No KiCAD installation required, although you need the KiCAD symbol libraries

What It Does

Reads and writes .kicad_sch files directly (no KiCAD installation needed). Here’s a complete working example:

import kicad_sch_api as ksa

# Create schematic
sch = ksa.create_schematic("Voltage Divider")

# Add resistors
r1 = sch.components.add("Device:R", "R1", "10k", position=(100, 100))
r2 = sch.components.add("Device:R", "R2", "10k", position=(100, 110))

# Wire them together
sch.add_wire(start=(100, 105), end=(100, 108))

# Add labels
sch.add_label("VCC", position=(100, 100))
sch.add_label("VOUT", position=(100, 105))
sch.add_label("GND", position=(100, 115))

# Save
sch.save("voltage_divider.kicad_sch")

Output: A valid .kicad_sch file that opens in KiCAD 9.

You can see working examples here: kicad-sch-api/examples at main · circuit-synth/kicad-sch-api · GitHub


Technical Approach

Format Preservation:

  • Parses KiCAD S-expressions
  • Output tested against kicad-cli netlist generation
  • 70+ tests including byte-for-byte format verification
  • Reference schematics created manually in KiCAD, then compared

What Works:

  • Components (add, remove, update)
  • Wires, labels, junctions, power symbols
  • Hierarchical sheets
  • Component rotation (0°, 90°, 180°, 270°)
  • Multi-unit components

What Doesn’t Work (Yet):

  • Graphical elements (arcs, circles, polylines)
  • Some advanced KiCAD features
  • PCB layout (out of scope - schematics only)

See docs/KNOWN_LIMITATIONS.md for details.


MCP Server

Includes an MCP (Model Context Protocol) server with 15 tools. Works well with Claude Code for automating schematic tasks. Just a programmatic interface - no AI magic.


Use Cases

I use it for Circuit-Synth. Other possible uses:

  • Storing circuits as Python code in git (instead of .kicad_sch files)
  • Parametric circuit generation
  • Circuit templates and automation

Feedback Welcome

Curious what people think:

  1. API design - Intuitive enough?
  2. Actual use cases - Would you use this? For what?
  3. Missing features - What would make it more useful?

Happy to answer questions, thanks for reading.

Shane

4 Likes

Are you aware of SKiDL?

SKiDL does not create a KiCad schematic, but creates a netlist that can be directly used in the PCB editor. I have always found this in intriguing project and useful for some schematic types (FPGA projects, or big button / led matrices), but for other sections (such as a power supply section) normal schematic entry is more suitable. I always felt that SKiDL would be more useful if it could be used as an addition to a project, so it could be combined with schematic entry.

Are the position arguments mandatory in your library? I see you have a “parametric position helper”. It seems logical to be able to set a custom grid with auto increment if no position arguments are added during component creation. This saves a lot of typing

Most graphics libraries have a LineTo() function which uses the current cursor position as a starting point. An extreme example would be the turtle library for Python. I think using something like that is useful.

You are using a 50 mil grid for normal coordinates and in the RC filter example I see:

sch.add_text(“RC Filter”, position=p(3, -2), size=1.27)

So apparently you suddenly use mm for the text height. That is not very consistent. In KiCad, entry boxes use the “global settings” for coordinates, but any coordinate can be overridden by appending the units. I.e, “20mm” will always be 20 millimeter, even if the current units are set to mils or other banana units.

When adding a wire (or auto_route_pins) I’m missing an option to directly add a label name to the net.

It’s useful to add a comment line to the schematic that states that your schematic is script generated. I assume your script automatically overwrites old versions, and that will erase changes made by others to your schematic.

A while ago I wrote a Python library that can be used to write CNC programs in Python. It creates G-Code output. It is quite a puzzle to figure out a way to juggle coordinates in a way that does not need too much typing, and is both intuitive and flexible, and compatible with use in loops and lists. G-Code is the oldest “computer language” still in use. It was created in the late '50-ies, and it’s lacking quite a lot. Using variables, loops and functions (subroutines) in a readable way (such as Python can) is quite useful for some types of programs. During programming I had an emulator for CNC programs that had a file watcher function. When it noticed that the file was updated, it automatically loaded and executed the new G-code. (similar for the asciidoc plugin in Firefox). Having such a function inside KiCad would also be a very simple but useful addition when using a library such as this.

It would be nice if your library and SKiDL could use the same syntax. but I bumped into similar problems when creating my G-Code library. There are already a bunch of similar libraries, but I did not like their syntax, important to me sections were missing, etc. Cooperating is often more difficult then doing your own thing, but when you cooperate with another project, there is also a better chance to create a result that is useful for a wider public.

This is incredibly nice work, lack of such a sch API has been a gaping flaw in KiCAD. I have a number of handcrafted scripts for schematic manipulation that I will likely replace with this.

Excited to see what AI applications it can enable as well.

This article from Pat Deegan (a.k.a. Psychogenic) is also very similar.

Thanks for the thoughtful reply @paulvdh !

  • I am aware of SKIDL and that’s how I first fell in love with code based circuit design. I even made the first version of SKIDL’s schematic generation: SKiDL — SKiDL Has Schematics! . I made Circuit-Synth earlier this year because I think schematics are core to electronics design and should take center stage. I agree that the python code is a great addition to projects! That’s why I tried to make Circuit-Synth with a few things in mind:
    • A user should always be able to discard Circuit-Synth when they want. No lock-in.
    • Kicad schematic is the source of truth, you don’t need to trust Circuit-Synth. This is probably the biggest differentiator between Circuit-Synth and other code based PCB solutions like Atopile or Skidl (sch not supported for v9 kicad i believe). TS circuit makes schematics but who wants to learn type-script?
    • Bi-directional schematic to python support is the biggest unlock for code based circuit design, and I’m getting closer day by day to getting it working.
  • I looked at kicad-skip but found it was mostly for CLI and manipulating existing schematics. I wanted a full CRUD based API for KiCad schematics.

kicad-sch-api library is just for making the Kicad schematic. Circuit-Synth is meant to do the actual circuit building. So to respond to your questions:

  • Position arguments are mandatory since we are placing real components. The idea is that the code is straightforward representation of the schematic exactly. No tricks or abstractions (besides the grid)
  • I have a function for pin to pin auto calculation with orthogonal routing. No collision detection though
  • Great call out on the size using mm vs position grid integers! Let me think about this more since the text size doesn’t really relate to the grid
  • Right now the script just generates schematics. The synchronization and updating logic is in Circuit-Synthn, which updated kicad files and instead of generating new ones.

Take a look at Circuit-Synth and tell me what you think. I think a lot of your responses to kicad-sch-api library are probably addressed in Circuit-Synth.

@halachal thank you! Try out the MCP server and slash commands to try and generate circuits. It can work ok some times but the bot still struggles. You can have Claude generate a SVG or PDF to try and get a feedback loop running but Claude really sucks at parsing circuit images in my experience.

1 Like

For the text size, it would be compatible with python to assign a text string such as: size = "1.27mm" And this can be sorted out on a lower level in some library.

I’m unlikely to use this library myself. For the type of projects I do, the normal schematic entry method is just fine. I guess the most useful would be an API for integrating Python directly in the schematic editor. That’s being worked on, but I do not know the current status of that.

Another useful project is to extract pin names and numbers from FPGA synthesis tools, but I decided a few years ago that using FPGA’s would be stretching my brain too far.

1 Like

Fair enough about not using this library, I’m not using it either in my daily engineering yet lol. I sometimes use Circuit-Synth to import a kicad project to python, then have a llm analyze it or compare one revision vs another of a board; ie compare circuits of v3 of a schematic vs v5.

I agree with you on the units: if the user changed the default units to grid based (from mm based) then we should only use grid based assumption for the code. Then maybe add 2 functions like grid_to_mm and mm_to_grid just in case someone wants to dictate properties in the opposite measurement system. Thanks for the discussion!

For the grid thing…
I’m not sure why you invented your own grid. KiCad’s default grid has 100 “units” between pins and it took me a bunch of years to realize those were “mils”. What about a way to set the “default units”. For example call your own grid “blocks”. Then you can do things like:

setgrid( "blocks")
sch.add_label( "shane", position= ( 10, 10)  // These are in "blocks", each "block is 50 mil (1.27mm)
sch.add_label( "asdff",  position=("30mm", "800mil")  // Units that are not the default as text.
setgrid( "mm")
sch.add_label( "NewLabel", position = (400, 800) // Default units are now in mm.

I was using mm initially and the LLM gets confused. I think the a big factor is the inverted y-axis in KiCAD schematics is the opposite of LLM training data for grids so even with explicit direction it gets confused. And doing big number decimal math is harder for them. Using integers for the grids makes the placement easier for the LLM. And realistically I only think in grid increments (1.27mm or 50mil) for schematics unless I’m making a part or adjusting text or something.

So I like the idea of adding setgrid as it allows users flexibility while keeping the LLM friendly syntax. Or users can just write the code in mm (not sure if i implemented mil though…)