Skip to main content

Overview

Custom Lua triggers provide unlimited flexibility by allowing you to write your own trigger logic in Lua. They can monitor any game state, process complex conditions, and create sophisticated detection systems.
Custom triggers run frequently and can impact performance. Profile and optimize your code.

Basic Custom Trigger

function()
  -- Return true to activate the display
  local health = UnitHealth("player")
  local maxHealth = UnitHealthMax("player")
  return health < maxHealth * 0.3
end

Trigger Types

-- Trigger function
function(event, ...)
  if event == "PLAYER_REGEN_DISABLED" then
    return true  -- Show on combat start
  end
  return false
end

-- Untrigger function (optional)
function(event, ...)
  if event == "PLAYER_REGEN_ENABLED" then
    return true  -- Hide on combat end  
  end
  return false
end

State Management

Single State

function(allStates, event, ...)
  allStates[""] = allStates[""] or {}
  local state = allStates[""]
  
  -- Update state properties
  state.show = true
  state.changed = true
  state.value = UnitHealth("player")
  state.total = UnitHealthMax("player")
  state.progressType = "static"
  
  return true
end

Multiple States (Clones)

function(allStates, event, ...)
  -- Create state for each combo point
  local comboPoints = GetComboPoints("player", "target")
  
  for i = 1, 5 do
    local cloneId = "cp" .. i
    allStates[cloneId] = allStates[cloneId] or {}
    
    allStates[cloneId].show = i <= comboPoints
    allStates[cloneId].changed = true
    allStates[cloneId].index = i
  end
  
  return true
end

Unit States

function(allStates, event, unit)
  if not unit then return false end
  
  allStates[unit] = allStates[unit] or {}
  local state = allStates[unit]
  
  state.show = UnitExists(unit)
  state.changed = true
  state.unit = unit
  state.name = UnitName(unit)
  state.health = UnitHealth(unit)
  
  return true
end

Event Selection

Single Event

-- Events (comma-separated)
PLAYER_REGEN_DISABLED, PLAYER_REGEN_ENABLED

-- Trigger function
function(event)
  return event == "PLAYER_REGEN_DISABLED"
end

Multiple Events

-- Events
UNIT_HEALTH, UNIT_MAXHEALTH

-- Trigger function  
function(event, unit)
  if unit ~= "player" then return false end
  
  local health = UnitHealth("player")
  local maxHealth = UnitHealthMax("player")
  return (health / maxHealth) < 0.5
end

Frame Update (Polling)

-- Event
FRAME_UPDATE

-- Trigger function
function()
  -- Called every frame - use sparingly!
  return UnitCastingInfo("player") ~= nil
end
Use specific events instead of FRAME_UPDATE whenever possible for better performance.

Duration and Progress

Fixed Duration

function(allStates, event)
  allStates[""] = {
    show = true,
    changed = true,
    duration = 10,  -- 10 second duration
    expirationTime = GetTime() + 10,
    progressType = "timed",
    autoHide = true
  }
  return true
end

Custom Duration Function

-- Duration function (separate from trigger)
function()
  local start, duration = GetSpellCooldown(12345)
  if duration and duration > 0 then
    return duration, start + duration
  end
  return 0, 0
end

Static Progress

function(allStates, event)
  local state = allStates[""] or {}
  
  state.show = true
  state.changed = true
  state.value = UnitHealth("player")
  state.total = UnitHealthMax("player")
  state.progressType = "static"
  
  allStates[""] = state
  return true
end

Custom Functions

Name Function

function()
  return UnitName("target") or "No Target"
end

Icon Function

function()
  local texture = GetSpellTexture(12345)
  return texture or "Interface\\Icons\\INV_Misc_QuestionMark"
end

Texture Function

function()
  local _, class = UnitClass("player")
  return "Interface\\TargetingFrame\\UI-Classes-Circles",
         CLASS_ICON_TCOORDS[class]
end

Stacks Function

function()
  return GetComboPoints("player", "target")
end

Advanced Techniques

Persistent Variables

-- Use aura_env for persistent storage
aura_env.lastValue = aura_env.lastValue or 0

function(allStates, event)
  local currentValue = UnitHealth("player")
  local changed = currentValue ~= aura_env.lastValue
  aura_env.lastValue = currentValue
  
  if changed then
    allStates[""] = { show = true, changed = true }
    return true
  end
  return false
end

Throttling Updates

aura_env.lastUpdate = aura_env.lastUpdate or 0

function(allStates, event)
  local now = GetTime()
  
  -- Only update every 0.5 seconds
  if now - aura_env.lastUpdate < 0.5 then
    return false
  end
  
  aura_env.lastUpdate = now
  -- Process trigger logic...
  return true
end

Watching Other Triggers

function(allStates, event, triggerId, states)
  if event ~= "TRIGGER" then return false end
  
  -- React to trigger 1's state changes
  if triggerId == 1 then
    allStates[""] = {
      show = true,
      changed = true,
      otherShow = states[""] and states[""].show
    }
    return true
  end
  return false
end
Use “Watched Trigger Events” in the advanced trigger options to enable trigger watching.

Table State Properties

function(allStates, event)
  allStates[""] = {
    show = true,
    changed = true,
    customData = {
      targets = {"target1", "target2"},
      values = {100, 200, 300}
    }
  }
  return true
end

WeakAuras API

Unit Functions

-- Unit info
UnitName("player")
UnitClass("player")
UnitLevel("player")
UnitRace("player")

-- Unit state
UnitHealth("player")
UnitHealthMax("player")
UnitPower("player", 0)  -- 0 = mana
UnitPowerMax("player", 0)

-- Unit status
UnitExists("target")
UnitIsDeadOrGhost("player")
UnitAffectingCombat("player")
UnitIsPlayer("target")

Spell Functions

-- Spell info
GetSpellInfo(12345)
GetSpellTexture(12345)
IsSpellKnown(12345)

-- Cooldowns
GetSpellCooldown(12345)
GetSpellCharges(12345)

-- Casting
UnitCastingInfo("player")
UnitChannelInfo("player")

Aura Functions

-- Check auras
UnitAura("player", "Renew", nil, "HELPFUL")
UnitBuff("player", 1)  -- By index
UnitDebuff("target", "Shadow Word: Pain")

Combat Functions

-- Combo points / Holy Power / etc
GetComboPoints("player", "target")
UnitPower("player", SPELL_POWER_HOLY_POWER)

-- Runes (Death Knight)
GetRuneCooldown(1)
GetRuneType(1)

WeakAuras-Specific

-- Region access
WeakAuras.GetRegion(id)

-- State access  
WeakAuras.GetTriggerStateForTrigger(id, triggernum)

-- Scanning
WeakAuras.ScanEvents(event, ...)

Performance Optimization

-- Bad: Multiple API calls
function()
  if UnitHealth("player") < UnitHealthMax("player") * 0.3 then
    return UnitHealth("player") > 0
  end
end

-- Good: Cache values
function()
  local health = UnitHealth("player")
  local maxHealth = UnitHealthMax("player")
  if health < maxHealth * 0.3 then
    return health > 0
  end
end
function(event, unit)
  -- Filter early
  if unit ~= "player" then
    return false
  end
  
  -- Expensive checks only if needed
  local comboPoints = GetComboPoints("player", "target")
  return comboPoints >= 5
end
-- Bad: Creates table every call
function(allStates)
  allStates[""] = {  -- New table!
    show = true,
    changed = true
  }
end

-- Good: Reuse table
function(allStates)
  allStates[""] = allStates[""] or {}
  local state = allStates[""]
  state.show = true
  state.changed = true
end

Common Patterns

Resource Monitor

function(allStates, event, unit)
  if unit and unit ~= "player" then return false end
  
  local state = allStates[""] or {}
  state.show = true
  state.changed = true
  state.value = UnitPower("player", 0)
  state.total = UnitPowerMax("player", 0)
  state.progressType = "static"
  
  allStates[""] = state
  return true
end

Cooldown Tracker

function(allStates, event)
  local start, duration = GetSpellCooldown(12345)
  
  if duration and duration > 1.5 then  -- On cooldown
    allStates[""] = {
      show = true,
      changed = true,
      duration = duration,
      expirationTime = start + duration,
      progressType = "timed"
    }
    return true
  else  -- Ready
    allStates[""] = {
      show = false,
      changed = true
    }
    return true
  end
end

Multi-Target DoT Tracker

aura_env.targets = aura_env.targets or {}

function(allStates, event, ...)
  -- Scan nameplates for your DoTs
  for i = 1, 40 do
    local unit = "nameplate" .. i
    if UnitExists(unit) then
      local name, _, _, count, _, duration, expires = 
        UnitDebuff(unit, "Shadow Word: Pain", nil, "player")
      
      local guid = UnitGUID(unit)
      if name and guid then
        allStates[guid] = {
          show = true,
          changed = true,
          name = UnitName(unit),
          stacks = count,
          duration = duration,
          expirationTime = expires,
          progressType = "timed"
        }
      elseif allStates[guid] then
        allStates[guid].show = false
        allStates[guid].changed = true
      end
    end
  end
  
  return true
end

Debugging

function(allStates, event, ...)
  print("Event:", event)
  print("Args:", ...)
  
  local health = UnitHealth("player")
  print("Health:", health)
  
  return health < 1000
end

State Inspection

function(allStates, event)
  -- Print current states
  for cloneId, state in pairs(allStates) do
    print("Clone:", cloneId, "Show:", state.show)
  end
  
  return true
end

Error Handling

function(allStates, event)
  local success, result = pcall(function()
    -- Your trigger code
    return UnitHealth("player") < 1000
  end)
  
  if not success then
    print("Trigger error:", result)
    return false
  end
  
  return result
end
WeakAuras automatically wraps trigger functions in pcall. Errors will be logged but won’t break the addon.

Best Practices

Troubleshooting

  • Check events are registered correctly
  • Verify return value is boolean
  • Use print() to debug logic flow
  • Check for Lua errors in /wa debuglog
  • Profile with /wa pstart and /wa pstop
  • Avoid FRAME_UPDATE if possible
  • Cache repeated API calls
  • Use early returns to skip unnecessary work
  • Ensure changed = true is set
  • Verify trigger is returning true
  • Check if display has load conditions blocking it
  • Inspect state with custom code action

Trigger Overview

Trigger system fundamentals

Custom Code

Custom actions and functions

Conditions

Dynamic property changes

Build docs developers (and LLMs) love