---
title: "Strings"
url: /docs/en-us/luau/strings
last_updated: 2026-06-10T23:09:06Z
description: "A sequence of characters, such as letters, numbers, and symbols."
---

# Strings

The **string** data type is a sequence of characters, such as letters, numbers, and symbols. It's the data type for storing most text-based information.

## Declare strings

To declare a string variable, put quotes around the characters. It's more common to use double quotes (`"`), but single quotes (`'`) also work. If you want to include a single or double quote in your string, enclose your string in the other type of quote, or use an [escaped quote](#escape-strings).

```lua
local string1 = "Hello world!"
print(string1)  --> Hello world!

local string2 = 'Hello "world"!'
print(string2)  --> Hello "world"!
```

To include both single and double quotes in a string, or to create multi-line strings, declare them using double brackets:

```lua
local string1 = [[Hello
world!
Hello "world"!
Hello 'world'!]]

print(string1)
--> Hello
--> world!
--> Hello "world"!
--> Hello 'world'!
```

If necessary, you can nest multiple brackets inside a string using the same number of equal signs in both the beginning and ending bracket:

```lua
local string1 = [=[Hello
[[world!]]
]=]

print(string1)
--> Hello
--> [[world!]]
```

## Combine strings

To combine strings, **concatenate** them with two dots (`..`). Concatenating strings doesn't insert a space between them, so you'll need to include space(s) at the end/beginning of a preceding/subsequent string, or concatenate a space between the two strings.

```lua
local hello = "Hello"
local helloWithSpace = "Hello "
local world = "world!"

local string1 = hello .. world
local string2 = helloWithSpace .. world
local string3 = hello .. " " .. world

print(string1)  --> Helloworld!
print(string2)  --> Hello world!
print(string3)  --> Hello world!
```

Note that the `print()` command takes multiple arguments and combines them **with** spaces, so you can use `,` instead of `..` to yield spaces in `print()` outputs.

```lua
local hello = "Hello"
local world = "world"
local exclamationMark = "!"

print(hello .. world .. exclamationMark)  --> Helloworld!
print(hello, world .. exclamationMark)  --> Hello world!
print(hello, world, exclamationMark)  --> Hello world !
```

## Convert strings

To convert a string to a number, use the `Global.LuaGlobals.tonumber()` function. If the string doesn't have a number representation, `Global.LuaGlobals.tonumber()` returns `nil`.

```lua
local numericString = "123"
print(tonumber(numericString))  --> 123

local alphanumericString = "Hello123"
print(tonumber(alphanumericString))  --> nil
```

## Escape strings

To escape a double- or single-quote string declaration and embed almost any character, put a backslash (`\`) before the character. For example:

- To embed a single quote in a single-quote string, use `\'`.
- To embed a double quote in a double-quote string, use `\"`.

```lua
local string1 = 'Hello \'world\'!'
print(string1)  --> Hello 'world'!

local string2 = "Hello \"world\"!"
print(string2)  --> Hello "world"!
```

Certain characters following backslashes produce special characters rather than escaped characters:

- To embed a new line, use `\n`.
- To embed a horizontal tab, use `\t`.

```lua
local string1 = "Hello\nworld!"
print(string1)
--> Hello
--> world!

local string2 = "Hello\tworld!"
print(string2)  --> Hello	world!
```

## String interpolation

Luau supports **string interpolation**, a feature that lets you insert expressions into strings. Use backticks (`\``) to declare an interpolated string, then add expressions inside of curly brackets:

```lua
local world = "world"

local string1 = \`Hello {world}!\`
print(string1)  --> Hello world!
```

Although variables are the most common usage, you can use any expression, including math:

```lua
local world = "world"
local number = 1
local letters = {"w", "o", "r", "l", "d"}

local string1 = \`Hello {world}, {number} time!\`
local string2 = \`Hello {world}, {number + 1} times!\`
local string3 = \`Hello {table.concat(letters)} a third time!\`

print(string1)  --> Hello world, 1 time!
print(string2)  --> Hello world, 2 times!
print(string3)  --> Hello world a third time!
```

Standard escape rules apply for backticks, curly brackets, and backslashes:

```lua
local string1 = \`Hello \\`\{world\}\\`!\`
print(string1)  --> Hello \`{world}\`!
```

## Math conversion

If you perform math operations on a string, Luau automatically converts the string to a number. If the string doesn't have a number representation, it throws an error.

```lua
print("55" + 10)  --> 65
print("55" - 10)  --> 45
print("55" * 10)  --> 550
print("55" / 10)  --> 5.5
print("55" % 10)  --> 5
print("Hello" + 10)	 --> print("Hello" + 10):1: attempt to perform arithmetic (add) on string and number
```

## Comparisons

Strings can be compared using the `<`, `<=`, `>` and `>=` operators which compare using lexicographical order based on the ASCII codes of each character in a string. This will result in numbers in strings not being compared correctly, for example, `"100"` will be less than `"20"`, since the bytes `"0"` and `"1"` have lower ASCII codes than byte `"2"`.

```lua
print("Apple" < "apple") --> true
print("Banana" < "apple") --> true (B is before a in ASCII)
print("number100" < "number20") --> true
```

## String pattern reference

A **string pattern** is a combination of characters that you can use with `Library.string.match()`, `Library.string.gmatch()`, and other functions to find a piece, or substring, of a longer string.

### Direct matches

You can use direct matches in a Luau function like `Library.string.match()`, except for [magic characters](#magic-characters). For example, these commands look for the word **Roblox** within a string:

```lua
local match1 = string.match("Welcome to Roblox!", "Roblox")
local match2 = string.match("Welcome to my awesome game!", "Roblox")
print(match1)  --> Roblox
print(match2)  --> nil
```

### Character classes

Character classes are essential for more advanced string searches. You can use them to search for something that isn't necessarily character-specific but fits within a known category (class), including **letters**, **digits**, **spaces**, **punctuation**, and more.

The following table shows the official character classes for Luau string patterns:

Class | Represents | Example Match
| `.` | Any character | `32kasGJ1%fTlk?@94` |
| --- | --- | --- |
| `%a` | An uppercase or lowercase letter | `aBcDeFgHiJkLmNoPqRsTuVwXyZ` |
| `%l` | A lowercase letter | `abcdefghijklmnopqrstuvwxyz` |
| `%u` | An uppercase letter | `ABCDEFGHIJKLMNOPQRSTUVWXYZ` |
| `%d` | Any digit (number) | `0123456789` |
| `%p` | Any punctuation character | `!@#;,.` |
| `%w` | An alphanumeric character (either a letter **or** a number) | `aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789` |
| `%s` | A space or whitespace character | ` `, `\n`, and `\r` |
| `%c` | A special [control character](https://wikipedia.org/wiki/Control_character) | |
| `%x` | A hexadecimal character | `0123456789ABCDEF` |
| `%z` | The NULL character (`\0`) | |

For single-letter character classes such as `%a` and `%s`, the corresponding uppercase letter represents the "opposite" of the class. For instance, `%p` represents a punctuation character while `%P` represents all characters except punctuation.

### Magic characters

There are 12 "magic characters" which are reserved for special purposes in patterns:

| `$` | `%` | `^` | `*` | `(` | `)` |
| --- | --- | --- | --- | --- | --- |
| `.` | `[` | `]` | `+` | `-` | `?` |

You can escape and search for magic characters using the `%` symbol. For example, to search for **roblox.com**, escape the `.` (period) symbol by preceding it with a `%` as in `%.`.

```lua
-- "roblox.com" matches "roblox#com" because the period is interpreted as "any character"
local match1 = string.match("What is roblox#com?", "roblox.com")
print(match1)  --> roblox#com

-- Escape the period with % so it is interpreted as a literal period character
local match2 = string.match("I love roblox.com!", "roblox%.com")
print(match2)  --> roblox.com
```

### Anchors

You can search for a pattern at the beginning or end of a string by using the `^` and `$` symbols.

```lua
local start1 = string.match("first second third", "^first")  -- Matches because "first" is at the beginning
print(start1)  --> first

local start2 = string.match("third second first", "^first")  -- Doesn't match because "first" isn't at the beginning
print(start2)  --> nil

local end1 = string.match("first second third", "third$")  -- Matches because "third" is at the end
print(end1)  --> third

local end2 = string.match("third second first", "third$")  -- Doesn't match because "third" isn't at the end
print(end2)  --> nil
```

You can also use both `^` and `$` together to ensure a pattern matches only the full string and not just some portion of it.

```lua
-- Using both ^ and $ to match across a full string
local match1 = string.match("Roblox", "^Roblox$")  -- Matches because "Roblox" is the entire string (equality)
print(match1)  --> Roblox

local match2 = string.match("I play Roblox", "^Roblox$")  -- Doesn't match because "Roblox" isn't at the beginning AND end
print(match2)  --> nil

local match3 = string.match("I play Roblox", "Roblox")  -- Matches because "Roblox" is contained within "I play Roblox"
print(match3)  --> Roblox
```

### Class modifiers

By itself, a character class only matches **one** character in a string. For instance, the following pattern (`"%d"`) starts reading the string from left to right, finds the **first** digit (`2`), and stops.

```lua
local match = string.match("The Cloud Kingdom has 25 power gems", "%d")
print(match)  --> 2
```

You can use **modifiers** with any character class to control the result:

Quantifier | Meaning
| `+` | Match 1 or more of the preceding character class |
| --- | --- |
| `-` | Match as few of the preceding character class as possible |
| `*` | Match 0 or more of the preceding character class |
| `?` | Match 1 or less of the preceding character class |
| `%n` | For `n` between **1** and **9**, matches a substring equal to the `n`th captured string. |
| `%bxy` | The balanced capture matching `x`, `y`, and everything between (for example, `%b()` matches a pair of parentheses and everything between them) |

Adding a modifier to the same pattern (`"%d+"` instead of `"%d"`), outputs `25` instead of `2`:

```lua
local match1 = string.match("The Cloud Kingdom has 25 power gems", "%d")
print(match1)  --> 2

local match2 = string.match("The Cloud Kingdom has 25 power gems", "%d+")
print(match2)  --> 25
```

### Class sets

**Sets** should be used when a single character class can't do the whole job. For instance, you might want to match both lowercase letters (`%l`) **and** punctuation characters (`%p`) using a single pattern.

Sets are defined by brackets `[]` around them. In the following example, notice the difference between using a set (`"[%l%p]+"`) and **not** using a set (`"%l%p+"`).

```lua
local match1 = string.match("Hello!!! I am another string.", "[%l%p]+")  -- Set
print(match1)  --> ello!!!

local match2 = string.match("Hello!!! I am another string.", "%l%p+")  -- Non-set
print(match2)  --> o!!!
```

The first command (set) tells Luau to find both lowercase characters and punctuation. With the `+` quantifier added after the entire set, it finds **all** of those characters (`ello!!!`), stopping when it reaches the space.

In the second command (non-set), the `+` quantifier only applies to the `%p` class before it, so Luau grabs only the first lowercase character (`o`) before the series of punctuation (`!!!`).

Like character classes, sets can be "opposites" of themselves. This is done by adding a `^` character at the beginning of the set, directly after the opening `[`. For instance, `"[%p%s]+"` represents both punctuation and spaces, while `"[^%p%s]+"` represents all characters **except** punctuation and spaces.

Sets also support **ranges** which let you find an entire range of matches between a starting and ending character. This is an advanced feature which is outlined in more detail on the [Lua 5.1 Manual](https://www.lua.org/manual/5.1/manual.html#5.4.1).

### String captures

String **captures** are sub-patterns within a pattern. These are enclosed in parentheses `()` and are used to get (capture) matching substrings and save them to variables. For example, the following pattern contains two captures, `(%a+)` and `(%d+)`, which return two substrings upon a successful match.

```lua
local pattern = "(%a+)%s?=%s?(%d+)"

local key1, val1 = string.match("TwentyOne = 21", pattern)
print(key1, val1)  --> TwentyOne 21

local key2, val2 = string.match("TwoThousand= 2000", pattern)
print(key2, val2)  --> TwoThousand 2000

local key3, val3 = string.match("OneMillion=1000000", pattern)
print(key3, val3)  --> OneMillion 1000000
```

The `?` quantifier that follows both of the `%s` classes is a safe addition because it makes the space on either side of the `=` sign optional. That means the match succeeds if one (or both) spaces are missing around the equal sign.

In the `Library.string.gsub()` function, you can include capture groups in the replacement string using `%1`, `%2`, `%3`, etc., up to `%9`. Even though Luau supports up to 32 capture groups before throwing an error, you can only reference the first nine with this syntax:

```lua
local str = "love2play Roblox"
local pattern = "(%w+)(%d+)(%w+)%s+(%w+)"
local replacement = "I %1 %2 %3 %4!"

local result = string.gsub(str, pattern, replacement)
print(result) --> I love 2 play Roblox!
```

You can also nest string captures:

```lua
local places = "The Cloud Kingdom is heavenly, The Forest Kingdom is peaceful"
local pattern = "(The%s(%a+%sKingdom)[%w%s]+)"

for description, kingdom in string.gmatch(places, pattern) do
	print(description)
	print(kingdom)
end
--> The Cloud Kingdom is heavenly
--> Cloud Kingdom
--> The Forest Kingdom is peaceful
--> Forest Kingdom
```

This pattern search works as follows:

The `Library.string.gmatch()` iterator looks for a match on the entire "description" pattern defined by the outer pair of parentheses. This stops at the first comma and captures the following:

| # | Pattern | Capture |
| --- | --- | --- |
| **1** | `(The%s(%a+%sKingdom)[%w%s]+)` | The Cloud Kingdom is heavenly |

Using its successful first capture, the iterator then looks for a match on the "kingdom" pattern defined by the inner pair of parentheses. This nested pattern simply captures the following:

| # | Pattern | Capture |
| --- | --- | --- |
| **2** | `(%a+%sKingdom)` | Cloud Kingdom |

The iterator then backs out and continues searching the full string, capturing the following:

| # | Pattern | Capture |
| --- | --- | --- |
| **3** | `(The%s(%a+%sKingdom)[%w%s]+)` | The Forest Kingdom is peaceful |
| **4** | `(%a+%sKingdom)` | Forest Kingdom |

In addition to all of the above, there is a special case with an **empty capture** (`()`). If a capture is empty, then the position in the string will be captured:

```lua
local match1 = "Where does the capture happen? Who knows!"
local match2 = "This string is longer than the first one. Where does the capture happen? Who knows?!"

local pattern = "()Where does the capture happen%? Who knows!()"

local start1, finish1 = string.match(match1, pattern)
print(start1, finish1)  --> 1 42

local start2, finish2 = string.match(match2, pattern)
print(start2, finish2)  --> 43 84
```

These special captures may be nested like normal ones:

```lua
local places = "The Cloud Kingdom is heavenly, The Forest Kingdom is peaceful."
local pattern = "The (%a+()) Kingdom is %a+"

for kingdom, position in string.gmatch(places, pattern) do
	print(kingdom, position)
end
--> Cloud 10
--> Forest 42
```

The returned values are unusual in that they are **numbers** rather than strings:

```lua
local match = "This is an example"
local pattern = "This is an ()example"

local position = string.match(match, pattern)
print(typeof(position))  --> number
```