luaguides

dofile: Open and Execute a Lua File

dofile ([filename])

dofile is one of Lua’s basic functions for loading and running code from the filesystem. It opens a file, compiles the contents as a Lua chunk, and executes that chunk. Whatever the chunk returns flows back to you, with the full return arity preserved.

The function is defined in §6.1 of the Lua 5.4 Reference Manual.

Signature

dofile ([filename])

Parameters

dofile takes a single optional argument.

#ParameterTypeDefaultDescription
1filenamestring?standard input (stdin)Path to the file to load and execute. Resolved against the process’s current working directory. Not searched against package.path or LUA_PATH.

The argument is a raw OS path. Forward slashes work on every platform; backslashes work on Windows inside double-quoted string literals. If you want module-style path resolution, use require instead.

Return Value

dofile returns all values the chunk returns, in order, with full arity:

  • A chunk ending in return a, b, c returns three values.
  • A chunk ending in a trailing expression like 1, 2 returns two values (a trailing expression is also a return value).
  • A chunk with no return at all returns nothing, and dofile then returns nothing.

A chunk with no trailing return is treated as a script. The values flow back through dofile as either a multi-value or as nothing at all, depending on the last expression in the file:

-- greet.lua
print("loaded")

Assigning the result to a single local gives nil, because there is nothing to assign. The script’s side effect (the print) still runs, but no values are produced:

local result = dofile("greet.lua")
print(result)  -- nil

If the file cannot be opened, the chunk has a syntax error, or the chunk raises a runtime error, dofile does not catch it. The error propagates to the caller, unchanged.

Loading a config file

The most common use of dofile is loading a Lua file as configuration. A .lua file is just a chunk, so it can return any value, usually a table.

config.lua returns a table with three connection settings. The trailing return is what makes the file act as a value rather than a script:

return {
  host = "127.0.0.1",
  port = 8080,
  debug = true,
}

Calling dofile on the file path assigns the returned table to a local variable named cfg. You can then read any field directly off cfg like any other Lua table:

local cfg = dofile("config.lua")
print(cfg.host, cfg.port, cfg.debug)

Running that loader produces three tab-separated values on a single line, matching the three fields the config table returned. The first two are a string and a number, the third a boolean, exactly as defined in config.lua:

127.0.0.1	8080	true

Most ~/.luarc-style dotfiles and game config screens follow this pattern. Because dofile re-reads the file on every call, you get hot-reloading for free if you call it again at runtime.

Capturing multiple return values

dofile preserves the chunk’s full return list, which makes it a clean way to expose a small set of values or functions from a script.

mathx.lua defines two local helper functions and then returns both of them. The trailing return produces two values, which is the main point of this example:

local function add(a, b) return a + b end
local function mul(a, b) return a * b end
return add, mul

The caller assigns the chunk’s multiple return values to two local variables, one per value. This positional destructuring is what makes dofile a clean way to expose a small API from a file:

local add, mul = dofile("mathx.lua")
print(add(2, 3), mul(4, 5))

The print statement shows the two computed results on a single line, separated by a tab. The first call to add(2, 3) returns 5, and mul(4, 5) returns 20, exactly as the chunk produced:

5	20

You can return as many values as you need. The caller can use select("#", ...) to count them, or just destructure positionally.

Error handling: wrap with pcall

dofile does not run in protected mode. A missing file, a syntax error, or a runtime error inside the chunk all crash the caller. Wrap the call in pcall if the file is optional.

broken.lua raises an error on its first line and does nothing else. The error call is the simplest possible runtime failure inside a chunk:

error("intentional failure")

Wrapping dofile in pcall converts the propagated error into a regular return value. The first return is a boolean indicating success, and the second carries the error message:

local ok, err = pcall(dofile, "broken.lua")
print(ok, err)

When run, this prints false followed by the error message, prefixed with the file path. The path prefix is .../broken.lua:1:, which tells you both where the file was and which line raised the error:

false	.../broken.lua:1: intentional failure

What makes the error message especially useful is the file path prefix, which is far more informative than a bare error string. For traceback control, swap pcall for xpcall with a custom message handler.

dofile vs loadfile, load, and require

Lua has four ways to load and run code, and they differ in subtle but important ways.

FunctionReads fromReturnsCachesPath searchProtected mode
dofilea file (or stdin)runs the chunknonono
loadfilea file (or stdin)the chunk function, or nil + errornonoyes
loada string or functionthe chunk function, or nil + errornon/ayes
requirea file via package.path / package.cpaththe chunk’s return values, cachedyes (package.loaded)yesyes (re-raises)

A few practical takeaways:

  • Reach for require for modules. Caching, package.path search, and the single-load guarantee save you from reinventing them.
  • Use load when the code is already in memory: a network response, a string built at runtime, a function that yields chunks.
  • Use dofile when you genuinely want a side-effecting, re-running, no-search chunk: config files, REPL input, a script you trust.
  • Use loadfile when you want dofile’s file semantics but need to inspect the chunk before running it, or want protected-mode error reporting as nil + error instead of an exception.

dofile is roughly loadfile plus an immediate call, minus the protected-mode reporting. Compare the two side by side:

-- dofile: runs the chunk, propagates errors as exceptions
local result = dofile("config.lua")

-- loadfile: returns a chunk function, or nil + error if compilation fails
local chunk, err = loadfile("config.lua")
if not chunk then
  print("could not load:", err)
else
  local result = chunk()  -- you run it yourself, when you want
end

The loadfile form is what you want when you need to inspect the chunk, defer execution, or report load errors as values instead of exceptions. dofile is the shorter form for the case where you just want to run the file.

The full module-loading story is in the Modules and require tutorial.

Common Pitfalls

A handful of mistakes show up over and over:

  1. Forgetting pcall. A missing config file is a runtime crash. Wrap with pcall if the file is optional, or pre-check with io.open before calling dofile:

    local cfg
    local f = io.open("config.lua", "r")
    if not f then
      cfg = defaults
    else
      f:close()
      cfg = dofile("config.lua")
    end
  2. Assuming package.path applies. It doesn’t. dofile("foo.lua") looks for foo.lua in the current working directory, full stop. There is no LUA_PATH lookup and no package.searchers involvement. The next snippet fails unless foo.lua is literally in the cwd, even if package.path is set to find it:

    -- works only if "./foo.lua" exists relative to cwd
    local lib = dofile("foo.lua")
    
    -- use require if you want package.path search
    local lib = require("foo")
  3. Caching expectations. Re-calling dofile("x.lua") re-runs the file. Globals it sets or mutates happen every time. With the same setup.lua on disk, the first call prints setup ran once with require and twice with dofile:

    -- setup.lua: print("setup ran")
    
    dofile("setup.lua")  -- prints "setup ran"
    dofile("setup.lua")  -- prints "setup ran" again
    
    require("setup")      -- prints "setup ran"
    require("setup")      -- no-op, cached
  4. stdin surprise. dofile() with no argument reads from stdin. That’s useful for piping. The next line reads Lua code from a pipe and runs it through dofile():

    echo 'print(1+1)' | lua -e 'dofile()'

    In a non-interactive host (a GUI app, a server, anything without a tty), stdin usually blocks or returns nothing on EOF, so the call hangs or silently does nothing.

  5. Wrong working directory. dofile is resolved against the process’s current working directory, not the script’s location. If your tool changes cwd mid-run, relative paths break. The chunk can recover by reading its own path from arg[0] and building sibling paths from that:

    -- inside a chunk, derive its own directory and load a sibling file
    local script = arg[0] or ""
    local here = script:match("(.*/)") or "./"
    local helpers = dofile(here .. "helpers.lua")

Version Notes

The signature dofile ([filename]) and its semantics (file read, chunk compiled, chunk run, errors propagated, no caching) have been stable from Lua 5.1 through Lua 5.4. The 5.2 changes to load and loadfile did not touch dofile. The 5.4 manual still describes the same behavior, and the implementation in lbaselib.c is essentially unchanged.

If you maintain code that has to run on 5.0 or earlier, the no-argument dofile() reading from stdin is documented the same way in every edition.

See Also