string.find
string.find(s, pattern [, init [, plain]]) string.find searches a string for the first match of a Lua pattern (or a literal substring when plain is true) and returns the start and end indices of the match. If the pattern contains captures, those captures are returned after the indices. When nothing matches, string.find returns nil.
Signature
local start_idx, end_idx, ...captures = string.find(s, pattern [, init [, plain]])
s, the subject string to search.pattern, a Lua pattern (or plain text whenplainis true).init, optional 1-based index where the search starts (defaults to1). Negative values count from the end.plain, optional boolean. Whentrue, the pattern is treated as a literal string and pattern-matching is disabled.
Basic usage
local s = "hello world"
print(string.find(s, "world")) --> 7 11
print(string.find(s, "lua")) --> nil
The two integers are inclusive 1-based indices: the substring s:sub(7, 11) is the match.
Plain (non-pattern) search
When you want a literal lookup, pass plain = true. This avoids surprises with the magic characters ( ) . % + - * ? [ ] ^ $.
local path = "config.lua"
print(string.find(path, ".", 1, true)) --> 7 7 (the dot)
print(string.find(path, ".")) --> 1 1 (matches any char)
Starting offset
The init argument lets you continue searching after a previous hit:
local s = "ab-cd-ef"
local i, j = string.find(s, "-")
while i do
print(i, j)
i, j = string.find(s, "-", j + 1)
end
-- 3 3
-- 6 6
Negative init counts from the end:
string.find("/tmp/file.lua", "/", -5) --> 5 5
Captures in the return
When the pattern includes () captures, those captures follow the start/end indices:
local date = "2026-05-19"
local i, j, y, m, d = string.find(date, "(%d+)-(%d+)-(%d+)")
print(i, j, y, m, d) --> 1 10 2026 05 19
If you only need the captures and don’t care about positions, use string.match instead.
Method syntax
Because the string library is set as the metatable for strings, you can call string.find as a method:
local i, j = ("hello"):find("ll")
print(i, j) --> 3 4
Common patterns
-- Check whether a substring exists
if msg:find("ERROR", 1, true) then
io.write("alert\n")
end
-- Split-by-delimiter loop using string.find
local function split(s, sep)
local out, prev = {}, 1
while true do
local i, j = s:find(sep, prev, true)
if not i then
out[#out + 1] = s:sub(prev)
return out
end
out[#out + 1] = s:sub(prev, i - 1)
prev = j + 1
end
end
Gotchas
- Indices are 1-based and inclusive on both ends, unlike many languages.
- The dot (
.) in a pattern matches any character; passplain = truefor a literal dot. - A failed match returns
nil, not0, always test the first return value.
See also
- /reference/string-methods/ref-string-gsub/ , substitute pattern matches with a replacement.
- /tutorials/strings-and-patterns/ , walkthrough of Lua’s pattern syntax.
- /guides/lua-string-patterns/ , deeper coverage of patterns and captures.