Menu
Coddy logo textTech

The __newindex Metamethod

Part of the Logic & Flow section of Coddy's Lua journey — lesson 19 of 54.

You've learned how __index controls what happens when you try to read a key that doesn't exist in a table. Now you'll discover its counterpart: __newindex, which controls what happens when you try to add or modify a key-value pair.

The __newindex metamethod is triggered whenever you attempt to assign a value to a key that doesn't currently exist in the table. This gives you the power to intercept and control modifications before they happen.

Here's a simple example that prevents any new keys from being added to a table:

local readOnly = {}
local meta = {
    __newindex = function(table, key, value)
        print("Cannot add key: " .. key)
    end
}

setmetatable(readOnly, meta)

readOnly.name = "Alice"  -- Output: Cannot add key: name
print(readOnly.name)     -- Output: nil

When you try to set readOnly.name = "Alice", Lua sees that name doesn't exist in the table. Instead of adding it, Lua calls the __newindex function with three arguments: the table itself, the key being set, and the value being assigned.

In this example, the function simply prints a message and doesn't actually store anything.

You can also use __newindex to validate data before allowing it into your table:

local player = {}
local meta = {
    __newindex = function(t, key, value)
        if key == "health" and type(value) ~= "number" then
            print("Health must be a number!")
        else
            rawset(t, key, value)
        end
    end
}

setmetatable(player, meta)

player.health = "high"  -- Output: Health must be a number!
player.health = 100     -- Allowed

Notice the use of rawset() in the second example. This function bypasses the metatable and directly sets the value in the table.

Without it, setting the value would trigger __newindex again, creating an infinite loop.

challenge icon

Challenge

Easy
Write a function createValidatedTable that takes key and value and returns a table that only accepts string values for new keys.

Create an empty table and attach a metatable with __newindex that validates the value type. If someone tries to add a non-string value, use rawset() to store the string "invalid" instead. For string values, store them normally using rawset().

Logic:

  • Create an empty table
  • Create a metatable with __newindex that receives the table, key, and value
  • Inside __newindex, check if the value is a string using type(value) == "string"
  • If it's a string, use rawset(t, key, value) to store it
  • If it's not a string, use rawset(t, key, "invalid") to store "invalid" instead
  • Attach the metatable to the table using setmetatable()
  • Use rawset() to add the initial key-value pair to the table (bypassing the metatable)
  • Return the table

Parameters:

  • key (string): The initial key to add to the table
  • value (string): The initial value to add to the table

Returns: A table with validation that only accepts string values, containing the initial key-value pair (table)

Cheat sheet

The __newindex metamethod is triggered when you attempt to assign a value to a key that doesn't exist in a table, allowing you to intercept and control modifications.

Basic example preventing new keys:

local readOnly = {}
local meta = {
    __newindex = function(table, key, value)
        print("Cannot add key: " .. key)
    end
}

setmetatable(readOnly, meta)

readOnly.name = "Alice"  -- Output: Cannot add key: name
print(readOnly.name)     -- Output: nil

The __newindex function receives three arguments: the table itself, the key being set, and the value being assigned.

Validating data with __newindex:

local player = {}
local meta = {
    __newindex = function(t, key, value)
        if key == "health" and type(value) ~= "number" then
            print("Health must be a number!")
        else
            rawset(t, key, value)
        end
    end
}

setmetatable(player, meta)

player.health = "high"  -- Output: Health must be a number!
player.health = 100     -- Allowed

Use rawset(t, key, value) to bypass the metatable and directly set values in the table. This prevents infinite loops when setting values inside __newindex.

Try it yourself

function createValidatedTable(key, value)
    -- Write code here
end
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Logic & Flow