This guide discusses how to read and write files from/to the device’s local storage.
Reading and writing files — data in the form of plain text, JSON, XML, a local SQLite database, etc. — is essential to app development. There are countless cases when your app may need to read and/or write a file, but here are a few common instances:
Save/load user settings or other statistics.
Save the “app state” to a file and read it in a future session, so the user can return to exactly the same place where they left off, even if the app has exited.
Generate a local HTML file and load it in a native Web view.
Load data from a file that has been downloaded from a remote server and use that data in the app.
The system.pathForFile() function is the foundation of all file operations in Corona. Lua requires the entire path of a file when reading and writing, but mobile operating systems such as iOS obscure the file system via “sandboxing.” This makes it difficult to determine exactly where the file resides. Fortunately, Corona has simplified this with the system.pathForFile() function which returns a path that is compatible with Lua’s file I/O functions.
system.pathForFile( filename [, baseDirectory] )
The baseDirectory argument is an optional constant that corresponds to the base directory where the file is located. If not specified, the default is system.ResourceDirectory. See the System Directories section below for more information.
In the context of this guide, “writing” is synonymous with “saving” since you might need to generate new files as well as write to existing files.
Here’s a basic example of how to write data to a file:
-- Data (string) to write
local saveData = "My app state data"
-- Path for the file to write
local path = system.pathForFile( "myfile.txt", system.DocumentsDirectory )
-- Open the file handle
local file, errorString = io.open( path, "w" )
if not file then
    -- Error occurred; output the cause
    print( "File error: " .. errorString )
else
    -- Write data to file
    file:write( saveData )
    -- Close the file handle
    io.close( file )
end
file = nil
system.pathForFile() — this returns the path for the file to write, myfile.txt. Also, note that the base directory is set to system.DocumentsDirectory. This is important because, for security reasons, you can not write/save data to system.ResourceDirectory.
io.open() — this function opens the file for writing (or reading). It returns a new file handle, set to file. The second argument, "w", corresponds to the “mode” that the file will be opened in. This dictates what you’ll be doing with the file. In this case, "w" indicates write and tells Corona to create (write) a new file, or overwrite the file if it already exists. For a complete list of I/O modes, see the io.open() documentation.
file:write() — assuming the file is opened in write-compatible mode, the file:write() method will write the specified string to the file handle. In this example, the contents of the saveData variable are written to the specified file.
io.close() — whenever you perform file operations and you’re finished with the file handle, do not forget to call io.close(). This method closes the file handle and ends the I/O process.
To read a file, just get the file path, open the I/O in read mode, and set the contents to a variable.
-- Path for the file to read
local path = system.pathForFile( "myfile.txt", system.DocumentsDirectory )
-- Open the file handle
local file, errorString = io.open( path, "r" )
if not file then
    -- Error occurred; output the cause
    print( "File error: " .. errorString )
else
    -- Read data from file
    local contents = file:read( "*a" )
    -- Output the file contents
    print( "Contents of " .. path .. "\n" .. contents )
    -- Close the file handle
    io.close( file )
end
file = nil
system.pathForFile() — once again, you’ll need the path to the file that you want to read from. When reading files, you can specify any of the four system directories (see System Directories below). For example, if you include/bundle data files as part of your app, you can read these files from system.ResourceDirectory.
io.open() — this function opens the file and returns the file handle, set to file in this example. This time, the second argument must be set to "r" which corresponds to the read mode. For a complete list of I/O modes, see the io.open() documentation.
file:read() — assuming the file is opened in read-compatible mode, the file:read() method will read the contents of the file and set it to the variable savedData. The argument for the read function specifies the format of the procedure. If you want to read the entire contents of the file (newline characters preserved), use "*a" as in this example. Other formats are explained in the object:read() documentation.
io.close() — as emphasized above, whenever you perform file operations and you’re finished with the file handle, call io.close() to close the file handle and end the I/O process.
Another common task when reading data is to process each line of a file and use it for some purpose. For example, if you have a text file representing a list of products along with a price for each, you can loop through the file by lines and output or parse each line (product) individually.
The file:lines() function, in conjunction with a for loop, accomplishes this:
-- Path for the file to read
local path = system.pathForFile( "myfile.txt", system.DocumentsDirectory )
-- Open the file handle
local file, errorString = io.open( path, "r" )
if not file then
    -- Error occurred; output the cause
    print( "File error: " .. errorString )
else
    -- Output lines
    for line in file:lines() do
        print( line )
    end
    -- Close the file handle
    io.close( file )
end
file = nil
This process will return the next line on each iteration and continue until no more lines are available. Each line can be used in its entirety or parsed into smaller elements using string patterns. See the String Manipulation guide for details.
As noted above, system.pathForFile() requires a baseDirectory argument if you want to access a directory aside from the default system.ResourceDirectory.
Here are all four available system directory constants for reading/writing:
| Directory | Permissions | Description | 
|---|---|---|
system.ResourceDirectory | 
read | This directory refers to the core project directory which is the same location as the main.lua file. This is the default base directory for system.pathForFile(). | 
system.DocumentsDirectory | 
read/write | This directory is intended for files that the app cannot regenerate on its own, for example user-specific data, “app state” data, or anything that the app generates post-installation. Files in this directory will persist for the lifetime of the app — that is, until the app is explicitly removed from the device. On iOS, files in this location are backed up by syncing unless you specify otherwise (see notes below). | 
system.TemporaryDirectory | 
read/write | This directory is intended for single-session data. Files written to this location will generally persist as long as the app is running, but the operating system reserves the right to delete this data at any time. Thus, do not place important data in this directory. | 
system.CachesDirectory | 
read/write | Files in system.CachesDirectory tend to have a longer lifespan than those in system.TemporaryDirectory, but this is not reliable and you shouldn’t place important data in this directory. On iOS, files in this location are not backed up by syncing. | 
On Android devices, there is no literal system.ResourceDirectory because all resource files reside inside a compressed APK file. See Android File Restrictions below for more information.
In the Corona Simulator, equivalents of system.DocumentsDirectory and system.TemporaryDirectory are located in a sandboxed folder for each application. You can view these directories and the files within by selecting File → Show Project Sandbox in the Simulator.
On iOS and macOS, the native.setSync() API can be used to set the iCloud automatic backup flag for files in system.DocumentsDirectory. See native.setSync() for more information.
When accessing system.ResourceDirectory via system.pathForFile(), setting the filename parameter to nil will return the directory path without checking if the file exists.
If you need to check for the presence of a file, examine Testing if Files Exist in the next section.
Subfolders can be added to system.DocumentsDirectory and system.TemporaryDirectory using the LuaFileSystem (LFS).
Here’s how to create an images folder within system.DocumentsDirectory.
local lfs = require( "lfs" )
-- Get raw path to documents directory
local docs_path = system.pathForFile( "", system.DocumentsDirectory )
-- Change current working directory
local success = lfs.chdir( docs_path )  -- Returns true on success
local new_folder_path
local dname = "images"
if ( success ) then
    lfs.mkdir( dname )
    new_folder_path = lfs.currentdir() .. "/" .. dname
end
You can access files in a subfolder in two ways, depending on what you want to do with the file. If you want to display an image or play a sound from the subfolder, concatenate the subfolder name with the file name and then supply the base directory. For example, if you want to display the cat.png file in the images subfolder, do the following:
local catImage = display.newImage( "images/cat.png", system.DocumentsDirectory, 0, 0 )
Note that you don’t use system.pathForFile() in API calls that require a baseDirectory parameter, for example display.newImage(), display.newImageRect(), audio.loadSound(), etc.
If you want to access a file in a subfolder for reading or writing, do the following:
local path = system.pathForFile( "images/readme.txt", system.DocumentsDirectory ) local file, errorString = io.open( path )
If the file doesn’t exist, it returns nil — this leads to the next topic.
The following function can be used to test if a file exists in a folder or subfolder. Just remember to append the subfolder name to the file name before calling this function.
local function doesFileExist( fname, path )
    local results = false
    -- Path for the file
    local filePath = system.pathForFile( fname, path )
    if ( filePath ) then
        local file, errorString = io.open( filePath, "r" )
        if not file then
            -- Error occurred; output the cause
            print( "File error: " .. errorString )
        else
            -- File exists!
            print( "File found: " .. fname )
            results = true
            -- Close the file handle
            file:close()
        end
    end
    return results
end
-- Check for file in "system.DocumentsDirectory"
local results = doesFileExist( "images/cat.png", system.DocumentsDirectory )
-- Check for file in "system.ResourceDirectory"
local results = doesFileExist( "images/cat.png" )
The following function copies a file from one folder to another. This is useful if you need to copy a file bundled in system.ResourceDirectory to system.DocumentsDirectory. Note that you must create the destination subfolder before using this function.
function copyFile( srcName, srcPath, dstName, dstPath, overwrite )
    local results = false
    local fileExists = doesFileExist( srcName, srcPath )
    if ( fileExists == false ) then
        return nil  -- nil = Source file not found
    end
    -- Check to see if destination file already exists
    if not ( overwrite ) then
        if ( fileLib.doesFileExist( dstName, dstPath ) ) then
            return 1  -- 1 = File already exists (don't overwrite)
        end
    end
    -- Copy the source file to the destination file
    local rFilePath = system.pathForFile( srcName, srcPath )
    local wFilePath = system.pathForFile( dstName, dstPath )
    local rfh = io.open( rFilePath, "rb" )
    local wfh, errorString = io.open( wFilePath, "wb" )
    if not ( wfh ) then
        -- Error occurred; output the cause
        print( "File error: " .. errorString )
        return false
    else
        -- Read the file and write to the destination directory
        local data = rfh:read( "*a" )
        if not ( data ) then
            print( "Read error!" )
            return false
        else
            if not ( wfh:write( data ) ) then
                print( "Write error!" )
                return false
            end
        end
    end
    results = 2  -- 2 = File copied successfully!
    -- Close file handles
    rfh:close()
    wfh:close()
    return results
end
-- Copy "readme.txt" from "system.ResourceDirectory" to "system.DocumentsDirectory"
copyFile( "readme.txt", nil, "readme.txt", system.DocumentsDirectory, true )
File access in Corona is based on the underlying operating system which varies by platform. On iOS devices, you can access files in all of the directories described above. On Android, however, there is no literal system.ResourceDirectory because all resource files reside inside a compressed APK file.
Corona allows direct loading of images and audio files using the appropriate APIs, but it has limited access to resource files on Android using the file I/O APIs. Specifically, the following types can not be read from the resources directory: .html, .htm, .3gp, .lua, .m4v, .mp4, .png, .jpg, and .ttf.
Because of this limitation, if you have files of these types bundled in the core directory that you need to copy to another directory, you must change the file name so it can be accessed by the file I/O APIs. For example, if you want to move cat.png from the resource directory to the documents directory, it must be renamed cat.png.txt to be copied.
Here’s how to copy cat.png to the documents directory on Android, assuming it’s stored as cat.png.txt. This technique works for all platforms, so if you make it work for Android, it will work everywhere.
copyFile( "cat.png.txt", nil, "cat.png", system.DocumentsDirectory, true ) local catImage = display.newImage( "cat.png", system.DocumentsDirectory, 0, 100 )