# How to Use Panic Handlers in Zig for ESP32: A Complete Guide to ESP-IDF Integration

> Learn to use panic handlers in Zig for ESP32 by overriding the global panic symbol and integrating with ESP-IDF logging. A complete guide for robust embedded development.

- Repository: [Matheus C. França/zig-esp-idf-sample](https://github.com/kassane/zig-esp-idf-sample)
- Tags: how-to-guide
- Published: 2026-03-05

---

**To use panic handlers in Zig for ESP32, override the global `panic` symbol with a custom implementation that routes messages to the ESP-IDF logging system, then wire it into your root module via `pub const panic = idf.esp_panic.panic;`.**

The `kassane/zig-esp-idf-sample` repository demonstrates how to override Zig's default `@panic` behavior for embedded ESP32 development. By implementing custom **panic handlers in Zig for ESP32**, you can redirect critical error messages to the ESP-IDF logging infrastructure instead of the default stderr, ensuring visibility over UART or RTT during hardware debugging.

## Implementing the Custom Panic Handler

The core implementation resides in **`imports/panic.zig`**. This file defines a function that matches Zig's expected panic signature while integrating with ESP-IDF's `esp_log_write` system.

The handler accepts a message, optional stack trace, and return address. It writes a timestamped panic line using `sys.esp_log_write`, conditionally dumps stack trace addresses, then halts the CPU in an infinite loop:

```zig
pub fn panic(msg: []const u8,
             stack_trace: ?*@import("std").builtin.StackTrace,
             _: ?usize) noreturn {
    // Write a timestamped panic line to the ESP‑IDF log
    sys.esp_log_write(log.default_level, "PANIC",
        "[%lu ms] PANIC: %.*s\n", sys.esp_log_timestamp(),
        msg.len, msg.ptr);

    // If a stack trace is available, dump each address
    if (stack_trace) |st| {
        var i: usize = st.index;
        if (i > st.instruction_addresses.len) i = st.instruction_addresses.len;
        var idx: usize = 0;
        while (idx < i) : (idx += 1) {
            sys.esp_log_write(log.default_level, "PANIC",
                "  #%u: 0x%08lx\n", idx,
                st.instruction_addresses[idx]);
        }
    }

    // Spin forever – the CPU is effectively halted
    while (true) {
        asm volatile ("" ::: "memory");
    }
}

```

## Wiring the Panic Handler to Your Application

To activate the custom handler, you must expose it as the root-level `panic` symbol. The repository uses an umbrella module pattern in **`imports/idf.zig`** to organize ESP-IDF bindings.

### The Umbrella Module Pattern

The `imports/idf.zig` file aggregates all ESP-IDF imports and re-exports the panic implementation as `idf.esp_panic`. This centralizes ESP-IDF dependencies and provides a clean namespace for Zig applications.

### Root-Level Symbol Export

Each application entry point must re-export the panic function to override Zig's default. This is achieved by declaring `pub const panic` at the root of your main file:

```zig
pub const panic = idf.esp_panic.panic;

```

This pattern appears in **`main/examples/wifi-station.zig`** and **`main/examples/smartled-rgb.zig`**. Because the symbol is named exactly `panic` and is placed in the root namespace of the program, any `@panic("error message")` emitted by Zig's standard library or user code resolves to this ESP-IDF-specific implementation.

## Practical Usage Examples

### Wi-Fi Station Example

In **`main/examples/wifi-station.zig`**, the panic handler is wired at the top level before any networking logic:

```zig
const idf = @import("idf");

pub const panic = idf.esp_panic.panic;

pub fn main() void {
    // Wi-Fi initialization code...
    const result = someEspIdfCall() catch |err| @panic(@errorName(err));
}

```

When a Wi-Fi configuration fails, the `@panic` call invokes the custom handler, writing the error to the ESP-IDF log with a millisecond timestamp before halting.

### LED Strip Example

Similarly, **`main/examples/smartled-rgb.zig`** demonstrates the same pattern for LED control tasks. The handler captures panics from task creation failures or hardware initialization errors, routing them through the ESP-IDF logging system rather than silently crashing.

## Why Override the Default Panic Handler?

Implementing a custom panic handler provides three critical advantages for ESP32 development:

- **Visibility:** The ESP-IDF logging system routes output to UART, RTT, or other configured sinks, making panic information visible during development without requiring a debugger attachment.
- **Consistency:** All panic messages share the same format (`[timestamp] PANIC: …`) and are tagged with the `"PANIC"` identifier, which can be filtered in ESP-IDF log viewers like `idf.py monitor`.
- **Debugging:** Optional stack-trace dumping helps locate the failure point by printing instruction addresses, aiding post-mortem analysis when a full debugger is unavailable.

## Summary

- Implement the panic handler in a dedicated file (e.g., `imports/panic.zig`) using the signature `pub fn panic(msg: []const u8, stack_trace: ?*StackTrace, _: ?usize) noreturn`.
- Route messages to ESP-IDF using `sys.esp_log_write` with `log.default_level` and the `"PANIC"` tag.
- Halt the CPU with an infinite `while (true)` loop containing `asm volatile ("" ::: "memory")` to prevent optimization.
- Re-export the handler in your root module via `pub const panic = idf.esp_panic.panic` to override Zig's default behavior.
- Reference the pattern in `main/examples/wifi-station.zig` and `main/examples/smartled-rgb.zig` for production-ready implementations.

## Frequently Asked Questions

## How do I enable stack traces in Zig panics for ESP32?

Stack traces are automatically passed to your panic handler via the `stack_trace` parameter when available. The `imports/panic.zig` implementation checks `if (stack_trace) |st|` and iterates through `st.instruction_addresses` to print each address. Ensure your build mode is not `ReleaseSmall` or `ReleaseFast` if you need full debug information.

## What happens after the panic handler logs the message?

After writing the panic message and optional stack trace to the ESP-IDF log, the handler enters an infinite `while (true)` loop with a memory clobber asm volatile statement. This effectively halts the CPU, preventing further execution and allowing you to read the logged error state.

## Can I use the standard library panic instead of ESP-IDF logging?

While you can use the standard library's default panic handler, it writes to stderr which may not be visible on ESP32 hardware without specific configuration. The ESP-IDF logging approach in `kassane/zig-esp-idf-sample` ensures output appears in `idf.py monitor` and other ESP-IDF tools, making it superior for embedded debugging.

## Where should I place the `pub const panic` declaration?

The declaration must appear in the **root namespace** of your program (typically `main.zig` or your entry point file). Zig searches for a public symbol named exactly `panic` in the root module to determine which function to call when `@panic` is invoked. Placing it in sub-modules will not override the default handler.