Skip to main content

folder2uf2

What it does

Ships a CircuitPython project as one file, so customers copy once.

Install

pip install folder2uf2

Python 3.8 or newer, standard library only. No dependencies.

Three ways to ship

Output Goes onto Board must be
code.py CIRCUITPY running CircuitPython
.uf2 RPI-RP2, RP2350 in the UF2 bootloader
.bin flashed by esptool ESP32, any state

Self-extract: one file, no bootloader

Pack the project. Output must be code.py, the name CircuitPython runs.

folder2uf2 --self-extract -o code.py myproject/

What your customer does

1. drag code.py onto CIRCUITPY
2. CIRCUITPY disappears for about 4 seconds
3. board reboots with code.py, lib/ and assets in place

Ports

Any board with zlib and binascii. Firmware does the writing.

Chip Tested on Self-extract
RP2040 Metro RP2040 yes
RP2350 Metro RP2350 yes
ESP32-S2 Metro ESP32-S2 yes
ESP32-S3 Metro ESP32-S3 yes
SAMD51 Metro M4 AirLift yes
nRF52840 Feather nRF52840 yes
STM32F405 Feather STM32F405 yes
SAMD21 Metro M0 Express no, lacks zlib

Every row was run on hardware, not inferred.

SAMD, nRF and STM keep the filesystem on external QSPI. That changes nothing.

.uf2 and .bin output stay RP2 and ESP32 only.

If it fails

The drive is handed back with an error. Nobody gets locked out.

How it works

storage.unsafe_disable_usb_drive() makes CIRCUITPY writable from code.py.

# payload rides in trailing comments, inflated one file at a time
# peak RAM is the largest single file, not the whole tree
# remount() cannot: MSC holds the block device lock while mounted

Limits

Needs CircuitPython installed already. CIRCUITPY vanishes while it runs.

zlib required: default wherever CIRCUITPY_FULL_BUILD is set
60KB incompressible single file verified on a 264KB RP2040

UF2, for RP2 boards

# filesystem only
folder2uf2 --board adafruit_metro_rp2350 -o fs.uf2 myproject/

# firmware and filesystem together
folder2uf2 --board adafruit_metro_rp2350 \
           --combine firmware.uf2 -o product.uf2 myproject/

# wipe residual data rather than writing only used sectors
folder2uf2 --board adafruit_metro_rp2350 --full -o fs.uf2 myproject/

# a board with no built-in profile
folder2uf2 --flash-offset 0x100000 --flash-size 0x1000000 \
           --family-id 0xe48bff59 -o fs.uf2 myproject/

folder2uf2 --list-boards

ESP32

tinyuf2 writes only ota_0, so ESP32 gets a raw image for esptool.

folder2uf2 --board esp32_16mb -o fs.bin myproject/
esptool --before no-reset --after no-reset write-flash 0x450000 fs.bin

Storage extend

Layouts differ, so read the table off your chip.

esptool --before no-reset --after no-reset read-flash 0x8000 0xc00 pt.bin
folder2uf2 --board esp32_16mb --partition-table pt.bin --storage-extend -o fs.bin src/
storage_extended = (_partition[0]->size < fatfs_bytes());
/* S3 16MB has that slot: 28032 sectors. S2 4MB does not: 1920. */

Download mode

One-shot: one esptool run per entry, then power cycle.

Bootloader

Install the tinyuf2 bootloader first, per your board's learn guide.

Browser instead of esptool

Takes a file and an offset, so customers need no Python.

https://adafruit.github.io/Adafruit_WebSerial_ESPTool/

How it was tested

Self-extract, three boards

RP2040 is the tight one.

Adafruit CircuitPython 10.3.0 on 2026-08-31; Adafruit Metro RP2040 with rp2040
Adafruit CircuitPython 10.3.0 on 2026-08-31; Adafruit Metro RP2350 with rp2350b
Adafruit CircuitPython 10.3.0 on 2026-08-31; Adafruit Metro ESP32S3 with ESP32S3

60144 bytes of source packed into 84356 bytes
assets.bin 60000 bytes incompressible, md5 matched
lib/mypkg/__init__.py created, nested dir
code.py replaced by the product, printed its marker

Drive returned unaided. All boards restored, identical.

What the RP2040 taught

Two MemoryErrors before it fit, both invisible on roomier chips.

"".join(chunks)        -> allocating 80037 bytes, failed
growing a bytearray    -> allocating 49680 bytes, failed
preallocate + memoryview -> fits

Combine, Metro RP2350

Before, then after one UF2 carrying the 10.3.0 release.

Adafruit CircuitPython 10.3.0-alpha.4 on 2026-07-23; Adafruit Metro RP2350 with rp2350b

Adafruit CircuitPython 10.3.0 on 2026-08-31; Adafruit Metro RP2350 with rp2350b
code.py output:
FOLDER2UF2-COMBINE-OK marker 8f3a21
lib import works

restored by a second combined UF2, 108 files identical

ESP32 image, Metro ESP32-S3

Partition table read from the chip, not assumed.

ffat  data fat  0x450000  0xBB0000  11968K
wrote 43520 bytes, hash verified
volume 28032 sectors, files intact

Safety

Firmware is untouched unless you pass --combine.

--self-extract writes only your files, leaving others alone
an ESP32 image sized wrong overflows, so read the table

Credit

Idea from jepler's archived mkfatimg. Thanks @todbot for the requests.

https://github.com/adafruit/circuitpython/issues/10074

License

MIT

Metadata

Release files for folder2uf2 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for folder2uf2 0.1.2
File Size Uploaded
folder2uf2-0.1.2.tar.gz 21.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for folder2uf2 0.1.2
File Interpreter ABI Platform
folder2uf2-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 37.9 kB

Release files / folder2uf2-0.1.2.tar.gz

Download URL folder2uf2-0.1.2.tar.gz
Size 21.0 kB
Tags Source
SHA-256 checksum
How to use checksums
353d12d08c576c016b518b8b3ec56d76f40a2dbff7ff83fdc405be225617f4b4
BLAKE2b-256 checksum
How to use checksums
8af818f18249647871573452bb18a3febd70a1f1fe2e60b2a836e7a70eff58b7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release files / folder2uf2-0.1.2-py3-none-any.whl

Download URL folder2uf2-0.1.2-py3-none-any.whl
Size 16.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
42c4fbbe3f55defb94b861c48cd5aa94dc07a8a8d408f27ee2fa9ae9d4fc18bf
BLAKE2b-256 checksum
How to use checksums
69e48a59bd2287f0cac6985ed85e1843a4c376983a05ac52f8f3277f83399459
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page