alert() and alertcondition() are not two spellings of the same thing
Pine scripts need different alert tools depending on whether the message is static, dynamic, visual, or automated.
alert() and alertcondition() are close enough in name that people naturally treat them as interchangeable. They are not. The difference matters most when an indicator moves from “I want a TradingView alert” to “I want a reliable signal surface that can carry useful context.” That is where the older habit of dropping alertcondition() lines everywhere starts to creak.
I keep this as a discovery because a lot of alert confusion begins with a script that technically compiles. The user can create an alert. The condition can fire. But the message cannot carry the detail the user thinks it can carry, or the alert appears in the dialog in a way that does not match how the script is actually meant to be used.
Why this catches people
alertcondition() is useful when you want to expose named alert conditions from an indicator. It has to be declared in a suitable scope, and its message is fundamentally a template, not an arbitrary series string built at runtime. Placeholders can help, but they are not the same as composing a full payload from current script values.
alert(), on the other hand, is a runtime call. It can sit behind logic. It can build a series string. It can choose different messages for different branches. That makes it much more natural when the script has several signal states, when the payload is JSON, or when the alert should carry values that are specific to the current bar.
The Pine bit
The distinction I use is simple: alertcondition() names a condition for the alert UI; alert() emits an event from code. If the script is mostly a visual indicator and the user wants to select “Long setup” or “Short setup” from TradingView’s alert dialog, alertcondition() can be a good fit. If the script is an engine that decides what to send and when to send it, alert() usually fits the job better.
A lot of trouble comes from trying to make alertcondition() behave like a transport layer. It can advertise a condition, but it is not where I want to manage rich state. I would rather make state explicit and call alert() only when the script reaches a state worth sending.
//@version=6
indicator("Two alert surfaces", overlay = true)
longSetup = close > ta.highest(high[1], 20)
shortSetup = close < ta.lowest(low[1], 20)
alertcondition(longSetup, "Long setup", "Long setup detected")
alertcondition(shortSetup, "Short setup", "Short setup detected")
if barstate.isconfirmed and (longSetup or shortSetup)
side = longSetup ? "long" : "short"
msg = '{"side":"' + side + '","price":' + str.tostring(close, format.mintick) + '}'
alert(msg, alert.freq_once_per_bar_close)
Why it can survive a quick review
The bug gets missed because both mechanisms can produce something that looks like an alert. On a simple chart, that is enough to pass a quick test. The deeper question is who owns the alert message. If the alert dialog owns it, then alertcondition() plus placeholders may be acceptable. If the script owns it, then alert() is the cleaner expression of that responsibility.
Strategy alerts add another layer. Order events use the strategy order system and can pass alert_message through {{strategy.order.alert_message}}. That is not the same surface as an indicator’s alertcondition() line. Once there are indicators, strategies, order fills, dynamic payloads, and user-created alert messages in the same conversation, the names start to blur. The script should not.
How I handle it in builds
In a custom build, I usually decide on the alert surface at the same time as I decide the signal states. If the client wants manual notifications, named alert conditions may be perfect. If the client wants a webhook with structured data, I use alert() and make the script responsible for the payload. If a strategy is involved, I separate order-event messaging from discretionary signal messaging so the user does not assume they are the same.
I keep the chart labels and alert messages loosely coupled. The chart can explain the setup visually. The alert can be shorter, stricter, and more machine-readable. Trying to make one string serve both purposes is how scripts end up with messages that are awkward for humans and unreliable for webhooks.
Where this shows up
A useful review question is: could the user create the wrong alert from the UI? If the answer is yes, alertcondition() may need clearer titles or fewer exposed conditions. Named conditions are user-facing. If the list contains internal states, transitional states, or duplicate long and short variants that only make sense to the developer, the user can wire the wrong one and think the script failed.
With alert(), the failure mode is different. The user may not need to select individual conditions, but the script now owns more responsibility. It must decide when to call the alert, what frequency to use, and what message to send. That is why I do not think of alert() as “more advanced” in a vague way. It is simply the better tool when the script is the event engine.
This is also where documentation inside the script matters. A short note in the settings or publication text that says “Create one alert on any alert() function call” can prevent a lot of confusion. For a webhook build, I would rather have one well-owned alert path than six UI-selectable conditions that the user has to combine correctly.
In a finished script, I usually leave both mechanisms only when they serve different audiences. alertcondition() is helpful for the user who wants simple selectable conditions. alert() is helpful for the user who wants the script to own the event stream. If both are present, their titles should make that difference obvious. Otherwise the script looks more capable while giving the user two ways to create the wrong alert.
How I test it
I test this by checking the alert log, not only the chart. The chart tells me when a condition appeared. The log tells me what TradingView actually sent. If a webhook is involved, I compare the received payload with the intended state. That closes the gap between visual signal and operational event.
When the alert is meant for real routing, I test the user’s exact Create Alert configuration. Pine code can be correct while the UI alert is wrong. The final test is the path the user will actually use: condition selection, message field, frequency, webhook URL, and received payload.
Checks before I trust it
- Use
alertcondition()when the main need is named alert choices in the TradingView UI. - Use
alert()when the script needs dynamic messages, branch-specific payloads, or structured webhook data. - Do not expect
alertcondition()to behave like a runtime JSON builder. - Keep strategy order-event messages separate from indicator signal messages.
- Test the exact alert type the user will create, not just whether a condition becomes true.
The thing worth preserving is that alerts are not one feature. They are several surfaces with different responsibilities. Once I pick the surface deliberately, the script becomes easier to reason about and the user gets fewer surprises from the alert dialog.