Skip to main content

Overview

KiCad uses S-expression based text formats for all design files. This makes them human-readable, version-control friendly, and easy to parse programmatically. Understanding these formats enables custom tool development and advanced automation.

File Format Philosophy

KiCad’s file formats follow these principles:
  • Human-readable: Text-based S-expressions
  • Self-documenting: Tag names describe content
  • Forward-compatible: Unknown tags are preserved
  • Hierarchical: Nested structure reflects logical relationships
  • Version-tracked: Format version in header

PCB File Format (.kicad_pcb)

File Structure

Based on /demos/cm5_minima/CM5_MINIMA_3.kicad_pcb:
(kicad_pcb
  (version 20250513)
  (generator "pcbnew")
  (generator_version "9.99")
  
  (general
    (thickness 1.5296)
    (legacy_teardrops no)
  )
  
  (paper "A5")
  
  (title_block
    (title "My Board")
    (date "2024-01-15")
    (rev "1.0")
    (comment 1 "Author Name")
  )
  
  (layers ...)
  (setup ...)
  (net ...)
  (footprint ...)
  (gr_line ...)
  (segment ...)
  (via ...)
  (zone ...)
)

Layer Definition

(layers
  (0 "F.Cu" signal)
  (4 "In1.Cu" signal)
  (6 "In2.Cu" signal)
  (2 "B.Cu" signal)
  (5 "F.SilkS" user "F.Silkscreen")
  (7 "B.SilkS" user "B.Silkscreen")
  (1 "F.Mask" user)
  (3 "B.Mask" user)
  (13 "F.Paste" user)
  (15 "B.Paste" user)
  (25 "Edge.Cuts" user)
  (27 "Margin" user)
  (31 "F.CrtYd" user "F.Courtyard")
  (29 "B.CrtYd" user "B.Courtyard")
  (35 "F.Fab" user)
  (33 "B.Fab" user)
  (39 "User.1" user)
)
Layer format: (id "name" type ["display_name"])

Stackup Definition

(setup
  (stackup
    (layer "F.SilkS"
      (type "Top Silk Screen")
    )
    (layer "F.Paste"
      (type "Top Solder Paste")
    )
    (layer "F.Mask"
      (type "Top Solder Mask")
      (thickness 0.01)
    )
    (layer "F.Cu"
      (type "copper")
      (thickness 0.035)
    )
    (layer "dielectric 1"
      (type "prepreg")
      (thickness 0.2104)
      (material "FR4")
      (epsilon_r 4.5)
      (loss_tangent 0.02)
    )
    (layer "In1.Cu"
      (type "copper")
      (thickness 0.0152)
    )
    (copper_finish "None")
    (dielectric_constraints no)
  )
)

Footprint Definition

(footprint "Resistor_SMD:R_0805_2012Metric"
  (layer "F.Cu")
  (uuid "12345678-1234-1234-1234-123456789abc")
  (at 100 50 90)
  (descr "Resistor SMD 0805")
  (tags "resistor")
  (property "Reference" "R1"
    (at 0 -1.65 90)
    (layer "F.SilkS")
    (uuid "abcd1234-5678-90ab-cdef-1234567890ab")
    (effects
      (font
        (size 1 1)
        (thickness 0.15)
      )
    )
  )
  (property "Value" "10k"
    (at 0 1.65 90)
    (layer "F.Fab")
    (uuid "efgh5678-90ab-cdef-1234-567890abcdef")
    (effects
      (font
        (size 1 1)
        (thickness 0.15)
      )
    )
  )
  (fp_line
    (start -0.227064 -0.735)
    (end 0.227064 -0.735)
    (stroke
      (width 0.12)
      (type solid)
    )
    (layer "F.SilkS")
    (uuid "wxyz-0123-4567-89ab-cdef01234567")
  )
  (pad "1" smd rect
    (at -0.9125 0 90)
    (size 1.025 1.4)
    (layers "F.Cu" "F.Paste" "F.Mask")
    (net 1 "GND")
    (uuid "1111-2222-3333-4444-555566667777")
  )
  (pad "2" smd rect
    (at 0.9125 0 90)
    (size 1.025 1.4)
    (layers "F.Cu" "F.Paste" "F.Mask")
    (net 2 "+3V3")
    (uuid "8888-9999-aaaa-bbbb-ccccddddeeee")
  )
)

Track and Via

(segment
  (start 100 50)
  (end 120 50)
  (width 0.25)
  (layer "F.Cu")
  (net 1)
  (uuid "track-uuid-here")
)

(via
  (at 120 50)
  (size 0.8)
  (drill 0.4)
  (layers "F.Cu" "B.Cu")
  (net 1)
  (uuid "via-uuid-here")
)

Zone (Copper Pour)

(zone
  (net 1)
  (net_name "GND")
  (layer "F.Cu")
  (uuid "zone-uuid")
  (hatch edge 0.508)
  (connect_pads
    (clearance 0.508)
  )
  (min_thickness 0.254)
  (filled_areas_thickness no)
  (keepout
    (tracks not_allowed)
    (vias not_allowed)
    (pads not_allowed)
    (copperpour not_allowed)
    (footprints allowed)
  )
  (fill
    (thermal_gap 0.508)
    (thermal_bridge_width 0.508)
  )
  (polygon
    (pts
      (xy 90 40)
      (xy 130 40)
      (xy 130 60)
      (xy 90 60)
    )
  )
)

Schematic File Format (.kicad_sch)

Basic Structure

(kicad_sch
  (version 20231120)
  (generator "eeschema")
  
  (uuid "root-sheet-uuid")
  
  (paper "A4")
  
  (title_block
    (title "My Schematic")
    (date "2024-01-15")
    (rev "1.0")
  )
  
  (lib_symbols ...)
  (symbol ...)
  (wire ...)
  (junction ...)
  (label ...)
  (global_label ...)
)

Symbol Instance

(symbol
  (lib_id "Device:R")
  (at 100 100 0)
  (unit 1)
  (exclude_from_sim no)
  (in_bom yes)
  (on_board yes)
  (uuid "symbol-uuid")
  (property "Reference" "R1"
    (at 102 99 0)
    (effects
      (font (size 1.27 1.27))
      (justify left)
    )
  )
  (property "Value" "10k"
    (at 102 101 0)
    (effects
      (font (size 1.27 1.27))
      (justify left)
    )
  )
  (property "Sim.Device" "R"
    (at 0 0 0)
    (effects (font (size 1.27 1.27)) hide)
  )
  (property "Sim.Params" "r=10k"
    (at 0 0 0)
    (effects (font (size 1.27 1.27)) hide)
  )
  (pin "1" (uuid "pin1-uuid"))
  (pin "2" (uuid "pin2-uuid"))
)

Wire and Connections

(wire
  (pts
    (xy 100 100)
    (xy 120 100)
  )
  (stroke
    (width 0)
    (type default)
  )
  (uuid "wire-uuid")
)

(junction
  (at 120 100)
  (diameter 0)
  (color 0 0 0 0)
  (uuid "junction-uuid")
)

(label "SIGNAL"
  (at 110 100 0)
  (effects
    (font (size 1.27 1.27))
    (justify left bottom)
  )
  (uuid "label-uuid")
)

Project File Format (.kicad_pro)

{
  "board": {
    "design_settings": {
      "defaults": {
        "board_outline_line_width": 0.1,
        "copper_line_width": 0.2,
        "copper_text_size_h": 1.5,
        "copper_text_size_v": 1.5
      },
      "diff_pair_dimensions": [
        {
          "gap": 0.25,
          "via_gap": 0.25,
          "width": 0.2
        }
      ],
      "drc_exclusions": [],
      "rules": {
        "min_copper_edge_clearance": 0.0,
        "solder_mask_clearance": 0.0,
        "solder_mask_min_width": 0.0
      },
      "track_widths": [0.25, 0.5, 1.0],
      "via_dimensions": [
        {
          "diameter": 0.8,
          "drill": 0.4
        }
      ]
    },
    "layers": {
      "boardThickness": 1.6,
      "copperLayers": 2
    }
  },
  "schematic": {
    "legacy_lib_dir": "",
    "legacy_lib_list": []
  },
  "sheets": [
    ["root", "Main Sheet"]
  ],
  "text_variables": {}
}

Parsing Files with Python

S-Expression Parser

import re
from typing import List, Union, Any

SExpression = Union[str, List[Any]]

def parse_sexpr(text: str) -> List[SExpression]:
    """
    Parse KiCad S-expression format
    
    Args:
        text: S-expression string
    
    Returns:
        Parsed nested list structure
    """
    # Tokenize
    tokens = re.findall(r'\(|\)|"[^"]*"|[^\s()]+', text)
    
    def parse_tokens(tokens, pos=0):
        result = []
        while pos < len(tokens):
            token = tokens[pos]
            if token == '(':
                # Start of new list
                sublist, pos = parse_tokens(tokens, pos + 1)
                result.append(sublist)
            elif token == ')':
                # End of current list
                return result, pos
            else:
                # Atom (string or number)
                if token.startswith('"'):
                    result.append(token[1:-1])  # Remove quotes
                elif token.replace('.', '').replace('-', '').isdigit():
                    result.append(float(token) if '.' in token else int(token))
                else:
                    result.append(token)
            pos += 1
        return result, pos
    
    parsed, _ = parse_tokens(tokens)
    return parsed

def find_sections(sexpr: List, tag: str) -> List[List]:
    """
    Find all sections with a specific tag
    
    Args:
        sexpr: Parsed S-expression
        tag: Tag name to search for
    
    Returns:
        List of matching sections
    """
    results = []
    
    def search(node):
        if isinstance(node, list):
            if len(node) > 0 and node[0] == tag:
                results.append(node)
            for item in node:
                search(item)
    
    search(sexpr)
    return results

# Usage
with open('board.kicad_pcb', 'r') as f:
    content = f.read()

parsed = parse_sexpr(content)

# Find all footprints
footprints = find_sections(parsed, 'footprint')
for fp in footprints:
    print(f"Footprint: {fp[1]}")  # Library reference

# Find all nets
nets = find_sections(parsed, 'net')
for net in nets:
    net_code = net[1]
    net_name = net[2]
    print(f"Net {net_code}: {net_name}")

Extract Board Information

class KiCadBoardParser:
    def __init__(self, filepath):
        with open(filepath, 'r') as f:
            content = f.read()
        self.sexpr = parse_sexpr(content)[0]  # Root kicad_pcb node
    
    def get_version(self):
        """Get file format version"""
        for item in self.sexpr:
            if isinstance(item, list) and item[0] == 'version':
                return item[1]
        return None
    
    def get_layers(self):
        """Extract layer definitions"""
        layers = find_sections(self.sexpr, 'layers')
        if not layers:
            return []
        
        layer_list = []
        for item in layers[0][1:]:
            if isinstance(item, list):
                layer_id = item[0]
                layer_name = item[1]
                layer_type = item[2] if len(item) > 2 else None
                layer_list.append({
                    'id': layer_id,
                    'name': layer_name,
                    'type': layer_type
                })
        return layer_list
    
    def get_stackup(self):
        """Extract stackup configuration"""
        stackup = find_sections(self.sexpr, 'stackup')
        if not stackup:
            return None
        
        layers = []
        for item in stackup[0][1:]:
            if isinstance(item, list) and item[0] == 'layer':
                layer_info = {'name': item[1]}
                for prop in item[2:]:
                    if isinstance(prop, list):
                        layer_info[prop[0]] = prop[1]
                layers.append(layer_info)
        return layers
    
    def get_footprints(self):
        """Get all footprints with positions"""
        footprints = find_sections(self.sexpr, 'footprint')
        result = []
        
        for fp in footprints:
            fp_data = {
                'library': fp[1],
                'layer': None,
                'position': None,
                'rotation': 0,
                'reference': None,
                'value': None
            }
            
            for item in fp[2:]:
                if isinstance(item, list):
                    if item[0] == 'layer':
                        fp_data['layer'] = item[1]
                    elif item[0] == 'at':
                        fp_data['position'] = (item[1], item[2])
                        if len(item) > 3:
                            fp_data['rotation'] = item[3]
                    elif item[0] == 'property' and item[1] == 'Reference':
                        fp_data['reference'] = item[2]
                    elif item[0] == 'property' and item[1] == 'Value':
                        fp_data['value'] = item[2]
            
            result.append(fp_data)
        return result

# Usage
parser = KiCadBoardParser('my_board.kicad_pcb')

print(f"Version: {parser.get_version()}")
print(f"\nLayers: {len(parser.get_layers())}")
for layer in parser.get_layers():
    print(f"  {layer['id']}: {layer['name']} ({layer['type']})")

print(f"\nFootprints: {len(parser.get_footprints())}")
for fp in parser.get_footprints()[:5]:  # First 5
    print(f"  {fp['reference']}: {fp['library']} at {fp['position']}")

Modifying Files

Generate Custom Footprint

def generate_footprint(name, pads):
    """
    Generate footprint S-expression
    
    Args:
        name: Footprint name
        pads: List of (number, x, y, width, height) tuples
    
    Returns:
        S-expression string
    """
    import uuid
    
    fp_uuid = str(uuid.uuid4())
    
    lines = [
        f'(footprint "{name}"',
        '  (layer "F.Cu")',
        f'  (uuid "{fp_uuid}")',
        '  (at 0 0)',
    ]
    
    for i, (num, x, y, w, h) in enumerate(pads):
        pad_uuid = str(uuid.uuid4())
        lines.extend([
            f'  (pad "{num}" smd rect',
            f'    (at {x} {y})',
            f'    (size {w} {h})',
            '    (layers "F.Cu" "F.Paste" "F.Mask")',
            f'    (uuid "{pad_uuid}")',
            '  )'
        ])
    
    lines.append(')')
    return '\n'.join(lines)

# Generate 4-pad footprint
pads = [
    ("1", -1.0, -1.0, 0.6, 0.6),
    ("2", 1.0, -1.0, 0.6, 0.6),
    ("3", 1.0, 1.0, 0.6, 0.6),
    ("4", -1.0, 1.0, 0.6, 0.6),
]

footprint = generate_footprint("Custom_QFN_4", pads)
print(footprint)

Best Practices

  1. Preserve UUIDs: Never reuse or modify existing UUIDs
  2. Maintain version: Keep format version up to date
  3. Validate syntax: Ensure balanced parentheses
  4. Backup before editing: Always keep originals
  5. Use version control: Track changes with git
  6. Test incrementally: Verify file loads after each change
  7. Follow conventions: Match KiCad’s formatting style

See Also

Build docs developers (and LLMs) love