Automating Crop Farms in Minecraft with Lua and ComputerCraft

Minecraft Lua Automation ComputerCraft Modding

Minecraft Turtle Farm

Ever wanted a Minecraft farm that plants, harvests, and manages itself? 🌾

With the ComputerCraft mod and a bit of Lua scripting, it’s absolutely possible!
In this guide, we’ll explore how to turn your Turtle into a smart automated farmer that plants, harvests, and refuels — all by itself.


đź§  The Concept: Automated Farming with Turtles

ComputerCraft’s Turtle is a programmable robot that can dig, build, farm, and more.
Using Lua, we can control every action it performs — making it a perfect candidate for creating a self-managing crop farm.

This script allows the Turtle to:

  • Detect the farm’s boundaries automatically
  • Harvest and replant crops when ready
  • Refuel and restock seeds from a connected chest
  • Return to its origin point after completing a farming cycle

⚙️ Step 1: Setting Up the Farm

Here’s what you’ll need:

  • Farmland: hydrated dirt blocks for your crops
  • Turtle: place it on one edge of your farm
  • Chest + Wired Modem: put a chest next to a modem and connect it to the Turtle
  • Seeds and Fuel: store them inside the chest

💡 Tip: Right-click the modem until it turns red — this means it’s active and connected!


đź’» Step 2: The Lua Code

Save the following script as farm.lua inside your Turtle.
Then, simply run it in ComputerCraft by typing:


local x, y, z = 0, 0, 0
local direction = 0
local invertDirection = false

-- Ground blocks that are part of the farm
local groundBlocks = {
    ["minecraft:dirt"] = true,
    ["minecraft:grass_block"] = true,
    ["minecraft:farmland"] = true,
    ["minecraft:water"] = true,
    ["minecraft:flowing_water"] = true,
    -- add your own here:
    --["<yourmod>:<block>"] = true,
}

-- Blocks that are crops, with their maximum ages
local cropBlocks = {
    ["minecraft:wheat"] = 7,
    ["minecraft:carrots"] = 7,
    ["minecraft:potatoes"] = 7,
    ["minecraft:beetroots"] = 3,
    -- add your own here:
    --["<yourmod>:<block>"] = <maximum age>,
}

-- Mappings of crop blocks to seed items
local seeds = {
    ["minecraft:wheat"] = "minecraft:wheat_seeds",
    ["minecraft:carrots"] = "minecraft:carrot",
    ["minecraft:potatoes"] = "minecraft:potato",
    ["minecraft:beetroots"] = "minecraft:beetroot_seeds",
    -- add your own here:
    --["<yourmod>:<block>"] = "<yourmod>:<seed>",
}

-- Fuel types to pull from a chest if no fuel is in the inventory
local fuels = {
    ["minecraft:coal"] = true,
    ["minecraft:charcoal"] = true,
    ["minecraft:lava_bucket"] = true,
    -- add your own here:
    --["<yourmod>:<item>"] = true,
}

local seedItems = {}
for k, v in pairs(seeds) do seedItems[v] = k end

local function writePos()
    local file = fs.open("jackmacwindows.farm-state.txt", "w")
    file.writeLine(x)
    file.writeLine(y)
    file.writeLine(z)
    file.writeLine(direction)
    file.writeLine(invertDirection and "true" or "false")
    file.close()
end

local function refuel()
    if turtle.getFuelLevel() == "unlimited" or turtle.getFuelLevel() ==
    turtle.getFuelLimit() then return end
    for i = 1, 16 do
        if turtle.getItemCount(i) > 0 then
            turtle.select(i)
            turtle.refuel(turtle.getItemCount() - 1)
            if turtle.getFuelLevel() == turtle.getFuelLimit() then return true end
        end
    end
    if turtle.getFuelLevel() > 0 then return true
    else return false, "Out of fuel" end
end

local function forward()
    local ok, err = turtle.forward()
    if ok then
        if direction == 0 then x = x + 1
        elseif direction == 1 then z = z + 1
        elseif direction == 2 then x = x - 1
        else z = z - 1 end
        writePos()
        return true
    elseif err:match "[Ff]uel" then
        ok, err = refuel()
        if ok then return forward()
        else return ok, err end
    else return false, err end
end

local function back()
    local ok, err = turtle.back()
    if ok then
        if direction == 0 then x = x - 1
        elseif direction == 1 then z = z - 1
        elseif direction == 2 then x = x + 1
        else z = z + 1 end
        writePos()
        return true
    elseif err:match "[Ff]uel" then
        ok, err = refuel()
        if ok then return forward()
        else return ok, err end
    else return false, err end
end

local function up()
    local ok, err = turtle.up()
    if ok then
        y = y + 1
        writePos()
        return true
    elseif err:match "[Ff]uel" then
        ok, err = refuel()
        if ok then return forward()
        else return ok, err end
    else return false, err end
end

local function down()
    local ok, err = turtle.down()
    if ok then
        y = y - 1
        writePos()
        return true
    elseif err:match "[Ff]uel" then
        ok, err = refuel()
        if ok then return forward()
        else return ok, err end
    else return false, err end
end

local function left()
    local ok, err = turtle.turnLeft()
    if ok then
        direction = (direction - 1) % 4
        writePos()
        return true
    else return false, err end
end

local function right()
    local ok, err = turtle.turnRight()
    if ok then
        direction = (direction + 1) % 4
        writePos()
        return true
    else return false, err end
end

local function panic(msg)
    term.clear()
    term.setCursorPos(1, 1)
    term.setTextColor(colors.red)
    print("An unrecoverable error occured while farming:", msg,
    "\nPlease hold Ctrl+T to stop the program, then solve the issue described above, run
    'rm jackmacwindows.farm-state.txt', and return the turtle to the start position.
    Don't forget to label the turtle before breaking it.")
    if peripheral.find("modem") then
        peripheral.find("modem", rednet.open)
        rednet.broadcast(msg, "jackmacwindows.farming-error")
    end
    local speaker = peripheral.find("speaker")
    if speaker then
        while true do
            speaker.playNote("bit", 3, 12)
            sleep(1)
        end
    else while true do os.pullEvent() end end
end

local function check(ok, msg) if not ok then panic(msg) end end

local function tryForward()
    local ok, err, found, block
    repeat
        found, block = turtle.inspect()
        if found then
            if groundBlocks[block.name] or cropBlocks[block.name] then
                ok, err = up()
                if not ok then return ok, err end
            else return false, "Out of bounds" end
        end
    until not found
    ok, err = forward()
    if not ok then return ok, err end
    local lastY = y
    repeat
        found, block = turtle.inspectDown()
        if not found then
            ok, err = down()
            if not ok then return ok, err end
        end
    until found
    if groundBlocks[block.name] then
        ok, err = up()
        if not ok then return ok, err end
        turtle.digDown()
    elseif not cropBlocks[block.name] then
        while y < lastY do
            ok, err = up()
            if not ok then return ok, err end
        end
        ok, err = back()
        if not ok then return ok, err end
        return false, "Out of bounds"
    end
    return true
end

local function selectItem(item)
    local lut = {}
    if type(item) == "table" then
        if item[1] then for _, v in ipairs(item) do lut[v] = true end
        else lut = item end
    else lut[item] = true end
    local lastEmpty
    for i = 1, 16 do
        local info = turtle.getItemDetail(i)
        if info and lut[info.name] then
            turtle.select(i)
            return true, i
        elseif not info and not lastEmpty then lastEmpty = i end
    end
    return false, lastEmpty
end

local function handleCrop()
    local found, block = turtle.inspectDown()
    if not found then
        if selectItem(seedItems) then turtle.placeDown() end
    elseif block.state.age == cropBlocks[block.name] then
        local seed = seeds[block.name]
        turtle.select(1)
        turtle.digDown()
        turtle.suckDown()
        if turtle.getItemDetail().name ~= seed and not selectItem(seed)
        then return end
        turtle.placeDown()
    end
end

local function exchangeItems()
    local inventory, fuel, seed = {}, nil, nil
    for i = 1, 16 do
        turtle.select(i)
        local item = turtle.getItemDetail(i)
        if item then
            if not seed and seedItems[item.name] then
                seed = {slot = i, name = item.name, count = item.count, limit =
                turtle.getItemSpace(i)}
            elseif not turtle.refuel(0) then
                inventory[item.name] = inventory[item.name] or {}
                inventory[item.name][i] = item.count
            elseif not fuel then
                fuel = {slot = i, name = item.name, count = item.count, limit =
                turtle.getItemSpace(i)}
            end
        end
    end
	local modem = peripheral.find("modem", function(_, v) return not v.isWireless() end)
	local tries = 0
	while not modem and tries < 4 do
		tries = tries + 1
		check(left())
		modem = peripheral.find("modem", function(_, v) return not v.isWireless() end)
	end
	if not modem then panic("Could not find modem!") end
    local name = modem.getNameLocal()
    for _, chest in ipairs{peripheral.find("minecraft:chest")} do
        local items = chest.list()
        for i = 1, chest.size() do
            if items[i] then
                local item = items[i].name
                if inventory[item] then
                    for slot, count in pairs(inventory[item]) do
                        local d = chest.pullItems(name, slot, count, i)
                        if d == 0 then break end
                        if count - d <= 0 then inventory[item][slot] = nil
                        else inventory[item][slot] = count - d end
                    end
                    if not next(inventory[item])
                    then inventory[item] = nil end
                elseif fuel and fuel.count < fuel.limit and item == fuel.name
                then
                local d =
                 chest.pushItems(name, i, fuel.limit - fuel.count, fuel.slot)
                fuel.count = fuel.count + d
                elseif seed and seed.count < seed.limit and item == seed.name
                then
                local d =
                 chest.pushItems(name, i, seed.limit - seed.count, seed.slot)
                seed.count = seed.count + d
                end
            end
            if not next(inventory) then break end
        end
        if not next(inventory) then break end
    end
    if next(inventory) then
        for _, chest in ipairs{peripheral.find("minecraft:chest")} do
            local items = chest.list()
            for i = 1, chest.size() do
                if not items[i] then
                    local item, list = next(inventory)
                    for slot, count in pairs(list) do
                        local d = chest.pullItems(name, slot, count, i)
                        if d == 0 then break end
                        if count - d <= 0 then list[slot] = nil
                        else list[slot] = count - d end
                    end
                    if not next(list) then inventory[item] = nil end
                end
                if not next(inventory) then break end
            end
            if not next(inventory) then break end
        end
    end
    if not fuel or not seed then
        for _, chest in ipairs{peripheral.find("minecraft:chest")} do
            local items = chest.list()
            for i = 1, chest.size() do
                if items[i]
                 and ((fuel and items[i].name == fuel.name and fuel.count < fuel.limit) or
                (not fuel and fuels[items[i].name])) then
                    local d =
                     chest.pushItems(name, i, fuel and fuel.count - fuel.limit, 16)
                    if fuel then fuel.count = fuel.count + d
                    else fuel
                     = {name = items[i].name, count =
                      d, limit = turtle.getItemSpace(16)} end
                end
                if items[i]
                 and ((seed and items[i].name == seed.name
                  and seed.count < seed.limit) or
                (not seed and seedItems[items[i].name])) then
                    local d = chest.pushItems(name, i, seed
                     and seed.count - seed.limit, 1)
                    if seed then seed.count = seed.count + d
                    else seed = {name = items[i].name, count = d,
                     limit = turtle.getItemSpace(1)} end
                end
                if (fuel and fuel.count >= fuel.limit)
                 and (seed and seed.count >= seed.limit) then break end
            end
            if (fuel and fuel.count >= fuel.limit)
             and (seed and seed.count >= seed.limit) then break end
        end
    end
end

if fs.exists("jackmacwindows.farm-state.txt") then
    local file = fs.open("jackmacwindows.farm-state.txt", "r")
    x, y, z, direction =
     tonumber(file.readLine()), tonumber(file.readLine()),
      tonumber(file.readLine()),
    tonumber(file.readLine())
    invertDirection = file.readLine() == "true"
    file.close()
    -- check if we were on a boundary block first
    local found, block, ok, err, boundary
    local lastY = y
    repeat
        found, block = turtle.inspectDown()
        if not found then check(down()) end
    until found
    if groundBlocks[block.name] then
        check(up())
        turtle.digDown()
    elseif not cropBlocks[block.name] then
        if y == lastY then lastY = lastY + 1 end
        while y < lastY do check(up()) end
        while not back() do check(up()) end
        boundary = true
    end
    if direction == 1 or direction == 3 then
        -- we were in the middle of a rotation, finish that before continuing
        local mv = (direction == 0) == invertDirection and left or right
        if boundary then
            check(mv())
            check(mv())
            check(tryForward())
            invertDirection = not invertDirection
            mv = mv == left and right or left
            writePos()
        end
        check(mv())
        handleCrop()
        if x == 0 and z == 0 then
            while y > 0 do check(down()) end
            while y < 0 do check(up()) end
            exchangeItems()
        end
    end
elseif not peripheral.find("minecraft:chest")
or not peripheral.find("modem", function(_, m)
return not m.isWireless() end) then
    print[[
Please move the turtle to the starting position next to a modem with a chest.
The expected setup is the turtle next to
 a wired modem block, with a chest next to that modem block.
This program cannot run until placed correctly.
]]
    return
else exchangeItems() end

local ok, err
while true do
    ok, err = tryForward()
    if not ok then
        if err == "Out of bounds" then
            local mv = (direction == 0) == invertDirection and left or right
            check(mv())
            ok, err = tryForward()
            if not ok then
                if err == "Out of bounds" then
                    check(mv())
                    check(mv())
                    check(tryForward())
                    invertDirection = not invertDirection
                    mv = mv == left and right or left
                    writePos()
                else panic(err) end
            end
            check(mv())
        else panic(err) end
    end
    handleCrop()
    if x == 0 and z == 0 then
        while y > 0 do check(down()) end
        while y < 0 do check(up()) end
        exchangeItems()
    elseif peripheral.find("modem") then
        x, y, z = 0, 0, 0
        exchangeItems()
    end
    if turtle.getFuelLevel() < 100 then refuel() end
end
farm