Skip to content
Guide
12 min read
October 17, 2026

boot.py vs main.py in MicroPython: What Runs, When and Why

Understand the difference between boot.py and main.py in MicroPython, startup order, Wi-Fi setup, project structure, and recovery from boot errors.

boot.py vs main.py in MicroPython: What Runs, When and Why

On a MicroPython board such as an ESP32, Raspberry Pi Pico, or Pico W, the files boot.py and main.py have different jobs. Both can run automatically after a normal boot, but they are intended for different parts of the startup process. Understanding that distinction makes projects easier to maintain, especially when Wi-Fi, sensors, displays, MQTT, or a web server are involved.

In general, boot.py is for brief setup that should happen during startup, while main.py contains the main application. On many MicroPython ports, boot.py runs first and main.py runs afterward, if present. Exact behavior can depend on the board port, firmware version, startup configuration, and how the script is launched.

This guide explains the normal startup flow, gives practical examples, shows how to test execution order, and covers common mistakes such as putting an endless loop in boot.py or saving a script under the wrong name. Examples use placeholders rather than real Wi-Fi credentials. Test changes incrementally so a startup mistake does not leave the board difficult to access.

1. What does boot.py do?

boot.py is intended for code that should run early during startup. Depending on the port and project, it may configure startup behavior, set up a filesystem or hardware state, initialize a minimal service, or prepare the environment before the main application begins. It should usually finish quickly and hand control over to the main program.

For example, an application might need to ensure a status LED starts in a known state, load a small configuration file, or configure a network interface before launching its application logic. These tasks can sometimes live in boot.py, but many simple projects do not need a custom boot script at all. The default firmware startup behavior is often sufficient.

Avoid placing your entire application in boot.py just because it runs automatically. Long delays, blocking network connections, or infinite loops there can prevent main.py from running. A boot script should be small, predictable, and easy to recover from. If the board becomes inaccessible because startup code loops forever, you may need to interrupt execution, use a safe mode supported by the firmware, or reflash the device.

2. What does main.py do?

main.py is the usual entry point for the application's main behavior. It may read sensors, update a display, control GPIO outputs, publish MQTT messages, serve a web page, or run a loop that coordinates the project. It is where most learners should put their first working program.

A simple main.py might blink an LED, read a button, or periodically sample a sensor. As a project grows, the main script can import helper modules such as sensors.py, networking.py, or display.py. Keeping the entry point focused makes it easier to test and maintain the project.

main.py does not have to contain every line of application logic. It is the script that starts the application; reusable functions and classes can live in other files. For instance, main.py can import a sensor-reading function and call it in a loop. If the script is renamed or saved to a different location, however, it may no longer be started automatically by the board's normal boot process.

3. Typical startup order

On a typical MicroPython installation, the startup sequence is broadly:

  1. The board powers on or resets.
  2. MicroPython firmware initializes the runtime and available hardware support.
  3. The port's startup process executes boot.py if present.
  4. The startup process executes main.py if present.
  5. The main program runs until it finishes, raises an uncaught exception, or is interrupted.

This is a useful mental model, but it is not a guarantee for every port or custom firmware. Some boards, boot configurations, frozen scripts, or application launch methods can change details. The REPL is also different from an automatic boot: typing code into the REPL runs that code in the current interpreter session, not necessarily as a startup file.

A normal reset can be a better test than pressing Run in an editor. When you click Run in an IDE, it may send the currently open file to the device and execute it directly. That proves the code can run, but it does not necessarily prove that the file is stored on the board under the correct name or that it runs after a power cycle.

4. First experiment: prove the order

The easiest way to understand startup is to print a different message from each file. In boot.py, save:

print("boot.py: startup setup")

Then save this as main.py:

print("main.py: application started")

Make sure both files are saved to the board's filesystem, not only to your computer. Open the MicroPython REPL and reset the board using the IDE's supported soft-reset option or the board's reset control. On a typical setup, the console should show the boot message before the main message.

If you see only one message, check the filenames and the destination shown by your IDE. The files must be on the board's filesystem with the exact expected names. If the device reports a syntax error or exception in boot.py, the startup sequence may not reach main.py. If your port uses a different startup configuration, consult the documentation for that board and firmware build.

After confirming the order, remove the diagnostic prints or replace them with deliberate logging. Excessive output can slow a small device and make useful error messages harder to spot.

5. A minimal main.py example for an ESP32

The following example blinks an external LED connected through a suitable current-limiting resistor. It uses GPIO 2 as an example only; the available pins and onboard LED wiring vary between ESP32 boards. Check your exact board's pinout before connecting hardware. Never connect an LED directly to a GPIO without a current-limiting resistor.

from machine import Pin
from time import sleep

led = Pin(2, Pin.OUT)

while True:
    led.on()
    sleep(0.5)
    led.off()
    sleep(0.5)

Save the code to the device as main.py. When the board resets, it should start the loop automatically if the port's standard startup process is in use. If your board has no LED on GPIO 2, the code may run without visible blinking. Use the board schematic or a known-safe external LED circuit to verify behavior.

Notice that the endless loop belongs in the application script. If you put it in boot.py, startup may never advance to main.py. This separation is especially useful once the application needs Wi-Fi setup, sensor sampling, and other long-running tasks.

6. Where should Wi-Fi connection code go?

For a small project, Wi-Fi connection logic can live directly in main.py. That is often the easiest approach while learning because all behavior is visible in one file. For a larger project, a short boot.py may load configuration or prepare startup, while a dedicated module handles network connection and retry logic.

Do not put an unbounded wait for Wi-Fi in boot.py. If the access point is unavailable, a blocking startup loop could stop the application from starting or make the REPL harder to reach. Prefer a connection function with a timeout and a clear failure path. The application can then continue in offline mode, retry periodically, or show an error state.

A minimal connection example belongs in the main application and should use placeholders rather than actual credentials:

import network
import time

SSID = "YOUR_WIFI_NAME"
PASSWORD = "YOUR_WIFI_PASSWORD"

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(SSID, PASSWORD)

timeout_ms = 15000
start = time.ticks_ms()
while not wlan.isconnected() and time.ticks_diff(time.ticks_ms(), start) < timeout_ms:
    time.sleep_ms(250)

if wlan.isconnected():
    print("Connected:", wlan.ifconfig())
else:
    print("Wi-Fi connection timed out; continuing offline")

The code illustrates a timeout, but exact networking APIs and behavior vary by MicroPython port and firmware version. Keep real credentials out of public code repositories and screenshots. For deployed devices, consider a configuration file with appropriate access controls and a recovery path for changing the network.

7. Should boot.py import modules?

It can, but imports execute module-level code. If a module connects to Wi-Fi, starts an infinite loop, or performs long blocking work as soon as it is imported, importing it from boot.py can make startup unpredictable. Prefer modules that define functions and classes without starting the entire application as a side effect.

A maintainable pattern is for a module to define a function such as connect_wifi(), while main.py decides when to call it and how to handle failure. This separates definitions from actions. It also allows the same module to be tested in the REPL without unexpectedly starting a server or looping forever.

MicroPython devices have limited memory, and some ports use frozen modules or different import paths. Avoid importing large or unnecessary modules during boot if they are not needed. When debugging an ImportError, check that the module file is stored on the device, the spelling and capitalization match, and the library is compatible with your firmware. The filesystem may be case-sensitive even if your desktop environment is not.

8. Common mistakes and recovery

The file runs in the IDE but not after reset. It may have been run from the computer instead of saved to the device, or it may be saved under a different name. Confirm the remote/device filesystem and exact filename.

main.py never starts. Check for an exception or infinite loop in boot.py. Temporarily simplify the boot script and reset again. If the firmware offers a safe startup or interrupt procedure, follow the board-specific documentation.

The script starts twice. Some IDEs may run the current file when you click Run, and a later reset runs main.py again. Distinguish IDE execution from device startup.

A module cannot be imported. Confirm that the file exists on the device, its name is correct, and it does not rely on CPython-only features unavailable in MicroPython.

The REPL is inaccessible. A blocking boot loop can flood output or monopolize the runtime. Try the IDE's interrupt/soft reset controls. If recovery fails, use the documented boot mode or firmware reflash procedure for the exact board.

Wi-Fi startup freezes. Add timeouts, avoid infinite blocking waits, and keep network failures from preventing basic device access.

9. A sensible project structure

A small sensor project might use this structure:

/ (device filesystem)
  boot.py          # optional, short startup preparation
  main.py          # application entry point
  config.py        # local configuration, if used
  sensors.py       # reusable sensor-reading functions
  display.py       # display helpers

Not every project needs all these files. For a beginner LED blink, main.py alone is enough. For a Wi-Fi sensor node, main.py can import sensors.py and display.py, then manage connection state and the application loop. If you keep credentials in a local configuration file, do not commit that file to a public repository.

A helpful rule is to make boot behavior predictable and application behavior explicit. Keep boot.py short, avoid side effects when importing modules, and make main.py responsible for deciding what the device should do. Add logging that reports useful state without printing sensitive values. This structure is easier to debug when the project gains MQTT, a web server, deep sleep, or remote updates.

10. Practical checklist

  • Confirm which MicroPython firmware and board port are installed.
  • Save files to the device filesystem, not just the local computer.
  • Use the exact names boot.py and main.py when relying on standard startup behavior.
  • Test the startup order with short print statements.
  • Keep boot.py brief and avoid endless loops or unbounded network waits.
  • Put the application's main loop and device behavior in main.py.
  • Use functions in helper modules rather than triggering large actions at import time.
  • Add timeouts and failure paths to Wi-Fi or peripheral initialization.
  • Keep credentials out of public repositories and logs.
  • Verify behavior after a reset or power cycle, not only when pressing Run in an IDE.

boot.py and main.py are simple conventions, but using them deliberately makes MicroPython projects more robust. The boot script prepares the runtime; the main script starts the application. The exact startup implementation depends on the board port and firmware, so use the official MicroPython documentation for your target when behavior differs from the common sequence.

Signal In

Built from what makers search for

The lookups piling up in SolderHub's search bar every day shape what we publish next — new component pages, board guides, and firmware notes, sent out as they go live.

By subscribing you agree to our Privacy Policy. Unsubscribe anytime.