File module
The File module reads and writes text files and manages folders: open, read, write, copy, rename and delete files, and create and delete folders. On a board, the files go in its flash memory (LittleFS) or on an SD card; on Windows, in two project folders that play the part of the flash and the card.
use File
Platforms
| Windows | ESP32 / ESP32-S3 | ESP32-P4 | ESP32-S31 | |
|---|---|---|---|---|
flash: | The .jxr's folder: Build\flash in an IDE project | The board's flash memory (LittleFS) | Same | Same |
sd: | The sd folder next to it: Build\sd | The SD card | Same | Same |
File.setUnit("sd:") with no card | Accepts it | Gives an error | Same | Same |
The b in the mode ("rb", "wb"…) | Doesn't translate line breaks, and lets seek go to any position | Changes nothing | Same | Same |
| A mode that isn't in the table | The VM shuts down | Opens for writing if it has w, a or +, and for reading if not | Same | Same |
writeLine ends the line with | \r\n (\n if the mode has a b) | \r\n | \n | \n |
isEOF() | true after a read tries to go past the end | true as soon as there's nothing left to read | Same | Same |
The board's flash memory is filled with the project's files when you press Run, and the SD card is set up in the board profile: it's all in Display, sound, SD and network.
Each Run on a board writes its whole file memory again with the project's files, and whatever your program wrote to flash: is lost (also with Upload Program). The SD card isn't touched. It's covered in Flash the firmware and run.
Paths and units
A path can start with the unit, flash: or sd:. Without a unit, it goes to the active unit, which is flash: when the program starts and is changed with File.setUnit.
use File
var f = File.open("flash:data.txt", "w") // in the flash
f.close()
var g = File.open("sd:log.txt", "a") // on the SD card
g.close()
println(File.exists("data.txt")) // true: without a unit, it goes to the flash
- Folders are separated with
/. With or without a leading slash, the path starts at the root of the unit:"data.txt"and"/data.txt"are the same file. - In a path without a unit, a
flash/or ansd/at the start is dropped:"flash/Images/background.bmp"is"Images/background.bmp". - Error messages carry the full path: on Windows, the whole folder on disk; on a board, the path from the root, such as
/data.txt.
Functions
| Function | What it does | Returns |
|---|---|---|
File.open(path, mode) | Opens a file | The open file |
File.exists(path) | Checks whether a file or a folder exists | true or false |
File.delete(path) | Deletes a file | true |
File.rename(path, newPath) | Renames a file or moves it, within the same unit | true |
File.copy(path, destination) | Copies a file, also from one unit to another | true |
File.isDir(path) | Checks whether the path is a folder | true or false |
File.mkDir(path) | Creates a folder | true |
File.rmDir(path) | Deletes an empty folder | true |
File.size(path) | The size of a file | The bytes, an integer |
File.setUnit(unit) | Changes the active unit | true |
File.getUnit() | The active unit | "flash:" or "sd:" |
If something goes wrong, they throw an exception, which you catch with try (see Errors and exceptions). Besides the errors of each one, all of them except getUnit give ERR_INVALID_ARG_NUMBER if you don't pass the arguments they ask for, and ERR_TYPE_MISMATCH if a path isn't a string.
open(path, mode)
Opens the file path and returns the open file, with the methods in Returned objects.
| Mode | What for |
|---|---|
"r" | Read. The file has to exist. |
"w" | Write. It creates it, or empties it if it already exists. |
"a" | Append at the end. It creates it if it doesn't exist. |
"r+" | Read and write. The file has to exist. |
"w+" | Read and write. It creates it, or empties it if it already exists. |
"a+" | Read and append at the end. |
Any of them can have a b ("rb", "wb", "r+b"…): on Windows, the file is read and written as it is, without changing the line breaks, and seek can go to any position. On the boards, the b changes nothing.
The file's folder has to exist: File.open doesn't create folders.
| Error | When |
|---|---|
ERR_FILE_NOT_FOUND | It can't be opened: with "r", the file doesn't exist; with "w" or "a", the folder doesn't exist. The message gives the path and the mode: Could not open file '…' in mode 'r'. |
ERR_INVALID_VALUE | The mode isn't in the table, such as "rw" or "x". The message gives the mode. |
use File
// Write
var f = File.open("output.txt", "w")
f.writeLine("Hello from JARU")
f.close()
// Read
var g = File.open("output.txt", "r")
println(g.readLine()) // Hello from JARU
g.close()
exists(path)
Returns true if there's a file or a folder with that path, and false if not.
use File
if (File.exists("config.txt")) then
println("The file exists")
else
println("I can't find it")
end
delete(path)
Deletes the file path and returns true. If it doesn't exist or can't be deleted, it throws ERR_FILE_NOT_FOUND. To delete a folder, use rmDir.
use File
if (File.exists("temp.txt")) then
File.delete("temp.txt")
end
rename(path, newPath)
Renames the file path to newPath, or moves it to another folder, and returns true. Both paths have to be on the same unit: to take a file from the flash to the card, use copy and then delete.
| Error | When |
|---|---|
ERR_INVALID_VALUE | The paths are on different units. |
ERR_FILE_NOT_FOUND | It can't: the file doesn't exist, or the destination folder doesn't exist. |
use File
File.rename("draft.txt", "final.txt")
copy(path, destination)
Copies the file path to destination and returns true. If destination already exists, it replaces it. It can copy from one unit to another.
| Error | When |
|---|---|
ERR_FILE_NOT_FOUND | The source file can't be opened, or the destination one can't be created (for instance, because its folder doesn't exist). |
ERR_FILE_WRITE_FAIL | Writing fails halfway (for instance, because the unit fills up). |
use File
File.copy("flash:config.txt", "sd:backups/config.txt") // from the flash to the card
isDir(path)
Returns true if path is a folder, and false if it's a file or doesn't exist.
use File
if (!File.isDir("logs")) then
File.mkDir("logs")
end
mkDir(path)
Creates the folder path and returns true. It creates a single folder: the one above has to exist. If it can't be created, it throws ERR_FILE_WRITE_FAIL; on Windows, also if it already exists (with the message Directory '…' already exists.). Check first with exists.
use File
if (!File.exists("logs")) then
File.mkDir("logs")
end
rmDir(path)
Deletes the folder path and returns true. It has to be empty: delete its files first with delete. If it isn't empty or doesn't exist, it throws ERR_FILE_WRITE_FAIL.
use File
File.rmDir("logs/old")
size(path)
Returns the size of the file path, in bytes. If it doesn't exist, it throws ERR_FILE_NOT_FOUND.
use File
println("It takes ", File.size("data.txt"), " bytes")
setUnit(unit)
Changes the active unit, the one used by paths without a unit: "flash:" or "sd:". It returns true.
| Error | When |
|---|---|
ERR_INVALID_VALUE | unit isn't "flash:" or "sd:". |
ERR_FILE_NOT_FOUND | On a board, you ask for "sd:" and there's no card mounted. On Windows, "sd:" is always accepted. |
use File
File.setUnit("sd:") // from here on, paths without a unit go to the card
var f = File.open("log.txt", "a")
f.writeLine("Log entry")
f.close()
File.setUnit("flash:") // and from here on, to the flash again
getUnit()
Returns the active unit: "flash:" or "sd:".
use File
println(File.getUnit()) // flash:
Returned objects
File.open returns the open file. Its methods read and write in it from a position that moves forward with each read or write:
| Method | What it does | Returns |
|---|---|---|
write(text) | Writes a string | The characters written |
writeLine(text) | Writes a string and a line break | The characters written |
read() | Reads from the position to the end | A string |
readLine() | Reads the next line, without the line break | A string, or nil at the end |
seek(pos) / seek(pos, origin) | Moves the position | 0 if it succeeds |
position() | The position, in bytes from the start | An integer |
size() | The size of the file, in bytes | An integer |
isEOF() | Checks whether the end has been reached | true or false |
isOpen() | Checks whether the file is still open | true or false |
close() | Closes the file | nil |
All of them give ERR_INVALID_ARG_NUMBER if you don't pass the arguments they ask for. After close(), all except isOpen and close give an error: ERR_FILE_WRITE_FAIL when writing, ERR_FILE_READ_FAIL when reading and ERR_INVALID_OPERATION in the rest.
write(text)
Writes the string text to the file and returns how many characters it wrote. text has to be a string: to write a number, convert it first with toString().
If the file was opened with "r", it throws ERR_FILE_WRITE_FAIL (File was opened in read-only mode ('r').).
writeLine(text)
Like write, and it also ends the line. On Windows and on the ESP32 and ESP32-S3, the line ends in \r\n; on the P4 and the S31, in \n. readLine understands both.
use File
var f = File.open("list.txt", "w")
f.writeLine("first")
f.writeLine("second")
f.close()
read()
Reads from the position to the end of the file and returns it in a string. To read the whole file, call it right after opening it, or after seek(0).
readLine()
Reads the next line and returns it without the line break, be it \n or \r\n. At the end, it returns nil. The last line is read even if it doesn't end in a line break.
use File
var f = File.open("list.txt", "r")
var line = f.readLine()
while (line != nil)
println(line)
line = f.readLine()
end
f.close()
seek(pos, origin)
Moves the position to pos bytes from the start, which is the default. With origin it measures from somewhere else: 0, the start; 1, the current position; 2, the end (with a negative pos to go backwards). An origin that isn't any of those counts as 0.
It returns 0 if it succeeds and another number if not. pos and origin have to be integers (ERR_TYPE_MISMATCH).
On Windows, if the mode has no b, it can only go to position 0: with any other, it throws ERR_INVALID_OPERATION. Open the file with "rb" to go to any position.
use File
var f = File.open("list.txt", "rb")
f.seek(-8, 2) // 8 bytes before the end
println(f.position())
f.close()
position()
Returns the position, in bytes from the start of the file.
size()
Returns the size of the open file, in bytes, without moving the position. It's the same as File.size with its path.
isEOF()
Returns true if the end of the file has been reached.
On Windows it turns true after a read tries to go past the end: after reading the last line with readLine it's still false, and it turns true when readLine returns nil. On the boards it turns true as soon as there's nothing left to read. To go through a file, look at what readLine returns, not at isEOF().
isOpen()
Returns true if the file is open, and false after close().
close()
Closes the file and returns nil. What you wrote stays on the disk or in the flash. Closing it again does nothing.
Complete example
An event log: it adds lines with the time to a file, reads it whole and says how much it takes.
use File
// The log folder
if (!File.exists("logs")) then
File.mkDir("logs")
end
func note(message)
var f = File.open("logs/log.txt", "a")
f.writeLine("[" + time() + "] " + message)
f.close()
end
func show()
if (!File.exists("logs/log.txt")) then
println("There's no log yet")
return
end
var f = File.open("logs/log.txt", "r")
var line = f.readLine()
while (line != nil)
println(line)
line = f.readLine()
end
f.close()
end
note("System started")
note("Sensor active")
note("Temperature: 23.5 degrees")
show()
println("The log takes ", File.size("logs/log.txt"), " bytes")
The first time, on Windows, it prints something like this (the time is the current one; time() gives it as text):
[Fri Nov 6 19:45:41 2026] System started
[Fri Nov 6 19:45:41 2026] Sensor active
[Fri Nov 6 19:45:41 2026] Temperature: 23.5 degrees
The log takes 139 bytes
Each time you run it, the log grows: "a" appends at the end.
Pitfalls and known issues
- Text only.
writeandwriteLinewrite strings, and a string is cut at the first 0 byte: the module isn't for writing binary data.readdoes return every byte. seekon Windows, withb. Without thebin the mode,seekonly goes to position 0.- There's no way to list a folder. The module has no function that tells which files there are: keep the names yourself, for instance in an index file.
- Folders, one at a time. Neither
mkDirnoropencreate the folders above. - Close your files. Until
close(), what you wrote may not be in the flash or on the card yet.
All the known issues of JARU, together, are in Known issues.