All of the available Lua functions are listed below. Additionally, you may access most of the functions available in the Lua 5.2 standard library. If you have questions about the available functions or would like us to make new features available, please let us know in the FarmBot Forum.


Performs an HTTP request to the FarmBot API, returning nil if an error occurs (see logs for details).

This is a limited convenience function that provides an alternative to the http() helper. It has a few differences from http():

  • You do not need to run json.encode on inputs
  • You do not need to run json.decode on outputs
  • The only supported request format is JSON
  • You do not need to pass an auth_token() in the header
  • The base URL is not configurable. is the only endpoint supported.
  • Returns nil if there was an error
  • Errors are sent to the log stream (if any).
result = api({
    method = "post",
    -- Don't forget the leading "/":
    url = "/api/points",
    -- `body` is optional for GET requests.
    body = {
        x = 200,
        y = 200,
        z = 0,
        radius = 100,
        pointer_type = "GenericPointer"

if result then
    send_message("debug", "Point creation OK", "toast")
    send_message("error", "ERROR - See logs for details", "toast")


Returns the device’s authorization token (string). This value can be used to access API resources without the need to store account passwords in Lua code or ENV vars.

-- Fetch all points from API:

resp_json, err = http({
    method = "GET",
    url = "",
    headers = {
        Authorization = ("bearer " .. auth_token()),
        Accept = "application/json"

points = json.decode(resp_json)


Performs base64 encoding on an object such as an image. Useful for uploading images to 3rd party APIs. Can also be used in reverse: base64.decode().

data = take_photo_raw()
return base64.encode(data)


Performs camera calibration. Calling this function will reset camera calibration settings.

check_position(coord, tolerance)

check_position(coordinate, tolerance) returns true if the device is within the tolerance range of coordinate.

if check_position({x = 0, y = 0,  z = 0}, 1.23) then
  send_message("info", "FarmBot is at the home position")
home = coordinate(0, 0, 0)
if check_position(home, 0.5) then
  send_message("info", "FarmBot is at the home position")

coordinate(x, y, z)

Generate a coordinate for use in location-based functions such as move_absolute and check_position.

coordinate(1.0, 20, 30)
-- Returns:
-- {x = 1.0, y = 20,  z = 30}


ADVANCED USERS ONLY. Allows you to execute arbitrary CeleryScript nodes. Even advanced users should avoid using this function directly.

  kind = "rpc_request",
  args = {
    label = "example",
    priority = 500
  body = {
      kind = "move_absolute",
      args = {
        location = {kind = "coordinate", args = {x = 2, y = 2, z = 2}},
        offset = {kind = "coordinate", args = {x = 0, y = 0, z = 0}},
        speed = 100

current_month / current_hour / current_minute / current_second

Returns a number representing the current month, hour, minute, or second.


Take a photo of the current location. If any vegetation is detected in the photo, it will be added to the device’s list of weeds.


Emergency locks the Farmduino microcontroller, preventing motor and peripheral usage. Some features, such as send_message, are still available while emergency locked.

-- Lock the device:


Unlock a previously locked device.

-- Unlock the device:

env(key, value) / env(key)

Store and retrieve key/value pairs to disk. This information will be stored on the device SD card and eventually synced with your web app account.

key and value must be strings. No other values are allowed. Values may not exceed 1,000 characters in length.

To create or update a key/value pair:

env("key", "value")

-- Example:
env("MY_API_TOKEN", "abc123")

To retrieve a previously stored value:

-- Returns `nil` if no value is found.

-- Example:
api_token = env("MY_API_TOKEN")
if api_token then
  send_message("info", "Token value is " .. api_token)
  send_message("info", "Value not found")


Returns a string representation of FarmBot OS’s version.



Determines the length of an axis using stall detection, rotary encoders, or limit switch hardware.

-- Single axis:

-- Every axis in the order Z, Y, X:


Finds the 0 (home) position for an axis using stall detection, rotary encoders, or limit switch hardware.

-- Single axis:

-- Every axis in the order Z, Y, X:


Returns a string representation of the firmware version on the Farmduino/Arduino.



Returns a table with an x and y attribute represent the maximum length of the garden bed as determined by firmware config settings.

size = garden_size()
send_message("info", "Width: " .. size.y)
send_message("info", "Length: " .. size.x)

gcode(command, params)

ADVANCED USERS ONLY. This feature is available for extremely specific usecases. Users should avoid using this function where possible.

Calling the function will send raw GCode to the Farmduino. No validations will be applied. The function will block the calling process until a response is received from the firmware.

A Q param will implicitly be added by FBOS. Do not explicitly set the Q value. It will cause FBOS to crash.

-- Send "G00 X1.23 Y4.56 Z7.89" to the Farmduino
gcode("G00", { X = 1.23, Y = 4.56, Z = 7.89 })


Fetch device properties. This is the same device resource found on the API.

-- Every property:

-- Single property:

get_job_progress / set_job_progress

Creates new jobs in the UI jobs panel. This is useful for long running tasks such a photo grids.

set_job_progress("example", {
  type = "anything",
  status = "working",
  percent = 12.3,
progress = get_job_progress("example")
send_message("debug", "Job progress: " .. progress.percent, "toast")


Fetch FarmBot OS configuration properties. This is the same FarmBot OS configuration resource found on the API.

-- Fetch all properties:

-- Fetch single property:


Fetch firmware configuration properties. This is the same firmware configuration resource found on the API.



Return a table containing the current X, Y, Z value of the device.

position, error = get_position()

if error then
  send_message("error", error, "toast")
  message = "Y position is " .. position.y
  send_message("info", message)


Move to the 0 (home) position of a given axis.

-- Single axis:

-- Every axis:


Performs an HTTP request. Example:

response, error = http({
  -- OPTIONAL. Default value is "get".
  -- OPTIONAL. Only strings and numbers.
    Authorization="bearer eyJ....4cw",
  -- OPTIONAL. Must be a string. Use included JSON library for
  --           JSON APIs

if error then
  -- The `error` object is reserved for non-HTTP errors.
  -- Example: missing URL.
  -- `error` will be `nil` if no issues are found.
  send_message("info", "Unknown error: " .. inspect(error))
  -- The `response` object has three properties:
  --   `status`: Number              - Response code. Example: 200.
  --   `body`: String                - Response body. Always a string.
  --   `headers`: Map<String, String> - Response headers.


Alias for json.encode.


Converts a JSON encoded string to a Lua table:

result, error = json.decode('{"foo":"bar","example":123}')
-- { foo="bar", example=123 }


Converts a Lua variable into stringified JSON.

result, error = json.encode({ foo="bar", example=123 })
-- => '{"foo":"bar","example":123}'


Use the camera to determine soil depth at the current location. Results will be available as point resources in the API. Performing this action over a wide area in many locations will improve the accuracy of soil height readings taken via soil_height(x, y).

move_absolute(x, y, z, s?) / move_absolute(coord)

Move to an absolute coordinate position.

move_absolute(1.0, 2, 3.4)
-- Alternative syntax:
move_absolute(coordinate(1.0, 20, 30))
-- Enable "Safe Z":
  x = 1.0,
  y = 20,
  z = 30,
  safe_z = true

NOTE: move_absolute can accept an optional fourth argument that sets movement speed as a percentage of max speed.


Plot a sensor reading point on the sensors panel. Please note that calling new_sensor_reading() does not perform any readings, it only records a value. See also: read_pin()

position, error = get_position()

i = 0
while (i < 10) do
  i = i + 1
    value=(math.random() * 1024)


Every FarmBot has a different garden size and camera viewport. The photo_grid() helper provides developers with a metadata object about the unique camera setup for the current device. The helper is most useful for operations that perform full-garden photography, such as taking a scan of the garden.

Calling photo_grid() will return a table with the following properties:

Property Description
each An iterator function that is called once per cell (see example below).
total The number of cells contained in the photo grid for the device.
x_grid_points The length of the garden scan on the X axis, measured in cells.
y_grid_points The length of the garden scan on the Y axis, measured in cells.
x_grid_start_mm The X coordinate for the center of the first cell in the grid.
y_grid_start_mm The Y coordinate for the center of the first cell in the grid.
x_offset_mm The camera’s relative X offset from the FarmBot position.
y_offset_mm The camera’s relative Y offset from the FarmBot position.
x_spacing_mm The number of millimeters between cells on the X axis.
y_spacing_mm The number of millimeters between cells on the Y axis.
z The height at which the camera was calibrated.

Example: Perform a full-garden photo scan:

local grid = photo_grid()

    if read_status("informational_settings", "locked") then
        move_absolute({x = cell.x, y = cell.y, z = cell.z})
        local msg = "Taking photo " .. cell.count .. " of " ..
        send_message("info", msg)

read_pin(pin, mode?)

read_pin(pin_num, mode?) reads a pin when given a pin number and read mode ("analog" or "digital"). Defaults to "digital" if no mode is given:

pin23 = read_pin(23) -- Digital is the default mode
pin24 = read_pin(24, "digital")
pin25 = read_pin(25, "analog")


read_status(...path?) reads the entire device state tree into memory.

The device state tree contains numerous properties that are relevant to the device’s operation. It is the same state tree seen in FarmBotJS.

-- Provide a path to the property you are interested in:
read_status("location_data", "raw_encoders", "x")

-- Alternative syntax:
status = read_status().location_data.raw_encoders.x

send_message(type, message, channels)

send_message(type, message, channels?)

The first required parameter is a log type, which is one of the following string values: "assertion", "busy", "debug", "error", "fun", "info", "success", "warn"

The second required parameter is the message, which may be either a string or a number.

The third parameter is optional. It can be a single string or an array of strings. The strings must be one of the following: "ticker", "toast", "email", "espeak"

-- Send a message to the default channel ("ticker"):
send_message("error", "Hello")

-- Send a message to a single channel:
send_message("success", "You've got mail!", "email")

-- Send a message to multiple channels:
send_message("info", "All systems running.", {"toast", "espeak"})

set_pin_io_mode(pin, mode)

Sets the I/O mode of an Arduino pin. It is slightly similar to the pinMode() function in the Arduino IDE.

Valid pin modes: "input", "input_pullup", "output".

result, error = set_pin_io_mode(13, "output")
if error then
  send_message("error", inspect(error))
  send_message("info", inspect(result))

soil_height(x, y)

Given an X and Y coordinate, returns a best-effort estimate of the Z axis height of the soil. This function requires at least 3 soil height measurements. When there are less than 3 measurements available, it will return the SOIL HEIGHT setting from the device settings page.

x = 10
y = 29
my_soil_height = soil_height(x, y)
send_message("info", "Distance to soil at (10, 29): " .. inspect(my_soil_height))
-- => "Distance to soil at (10, 29): -409.84"


This is an advanced feature that is intended to be used in conjunction with watch_pin.

When called, soft_stop will cancel all current and pending movement requests. Unlike emergency_lock, it will not lock the device nor will it reset the state of peripherals. Commands (including movement commands) will continue normally after a soft stop occurs. This function can be used to pause FarmBot temporarily if a peripheral value changes mid-movement.


Known bug

take_photo returns errors asynchronously, which may lead developers to believe the operation has succeeded when it actually fails in the background. If you require a high level of control over errors or are taking photos beyond the limits that the Web App allows, see take_photo_raw().

Takes a photo using the device camera and uploads it to the web app. Returns nil on success. Returns an error object if capture fails.

error = take_photo()

if error then
  send_message("error", "Capture failed " .. inspect(error))
  send_message("info", "Capture OK")


take_photo_raw() takes a photo using the device camera and holds it in memory. This functionality is useful when uploading photos to 3rd party APIs. If your usecase requires taking hundreds or thousands of photos per-use, you can use take_photo_raw() to upload your images to a third-party image hosting provider that does not impose the same image hosting limits as the Web App.

take_photo_raw() does not upload images to the web app. You must manually upload the resulting images to a hosting provider.

data = take_photo_raw()
return base64.encode(data)


See documentation for


Returns a list of UART devices.

uart_list = uart.list()

for _number, device_path in ipairs(uart_list) do
  send_message("debug", inspect(device_path), {"toast"})
end, baud)

Open a UART device (typically, via USB) for reading and writing. Please note that the UART devices must be connected to the Raspberry Pi, not the Arduino.

-- device name, baud rate:
my_uart, error ="ttyAMA0", 115200)

if error then
    send_message("error", inspect(error), "toast")

if my_uart then
    -- Wait 60s for data...
    string, error2 =
    if error2 then
        send_message("error", inspect(error2), "toast")
        send_message("info", inspect(string), "toast")

    error3 = my_uart.write("Hello, world!")

    if error3 then
        -- Handle errors etc..


See documentation for


See documentation for


Update device properties. This is the same device resource found on the API.

update_device({key = "value"})
update_device({name = "Test Farmbot"})


Update FarmBot OS configuration properties. This is the same FarmBot OS configuration resource found on the API.

update_fbos_config({key = "value"})
update_fbos_config({disable_factory_reset = true})


Update firmware configuration properties. This is the same firmware configuration resource found on the API.

update_firmware_config({key = "value"})
update_firmware_config({encoder_enabled_z = 1.0})


If the sequence executing the Lua command contains a sequence variable, you can access its content by calling the variable(name) function:

-- Assumes you are inside of a function that has a variable:
x_pos = variable("parent").x
send_message("info", x_pos, {"toast"});


Pause execution for a certain number of milliseconds. Crashes if the value is three minutes or greater.

-- wait for 1 second:

watch_pin(pin, callback)

Fork the current Lua process into a second, parallel Lua script that is initialized every 500 milliseconds for the duration of the parent script’s lifetime.

Things to keep in mind:

  • The pin watcher feature exists to support internal functionality of the FarmBot, such as motor load monitoring for vacuum and rotary tools. It is an advanced feature that should only be used as a last resort when more simple solutions cannot be used.
  • The callback is a forked copy of the running Lua script. It is not executed in the same script.
  • The callback does not share memory with the calling script. It is completely isolated from its parent. Even though you can access variables with identical names as the parent script, they are not shared. They are duplicate copies.
  • Because the callback is re-initialized every 500 ms. It is not possible to store state in the callback function. Any variables that are modified will be reset upon the next run.
  • The callback is terminated within 500 ms of the parent’s termination.
watch_pin(13, function(data)
  local pin =
  local val = data.value
  local msg = "Pin " .. pin .. " has a value of " .. val
  send_message("debug", msg, "toast")

-- Wait 3 seconds so that the watcher
-- can run a few (~6) times.

write_pin(pin, mode, value)

Sets a pin to a particular mode and value:

write_pin(13, "analog", 128)

What’s next?