AMPscript is the scripting language of Salesforce Marketing Cloud. It runs inside emails, SMS, CloudPages and landing pages, and it personalizes content by reading data extensions: tables keyed by subscriber. Learn the essentials, keep the cheat sheet handy, and practice against a sample subscriber below.
Learning Braze too? Try the Liquid 101. Why the two feel so different: Object Oriented Marketing.
1. Lesson 101
Ten concepts cover most real-world AMPscript. Every example on the right is live: edit it and the output updates as you type.
What AMPscript can see
At send time AMPscript sees one row per subscriber from the sendable data extension (the audience), plus any other data extension you look up. There is no profile object: if data is not in a table, AMPscript cannot reach it without an API call.
Delimiters
%%[ ]%% runs code without output. %%= =%% outputs one expression. %%FirstName%% is a shortcut that prints a field from the sendable row.
%%[ SET @greeting = "Hello" ]%% %%=v(@greeting)=%%, %%FirstName%%!
Variables
Variables start with @. Declare them with VAR (optional) and assign with SET. There are no operators like + or &: everything is a function, such as Concat() or Add().
%%[
VAR @tier
SET @tier = Uppercase(AttributeValue("PlanTier"))
]%%
%%=Concat(FirstName, " is on ", @tier)=%%Reading fields safely
A bare field name like Nickname throws an error if that column does not exist, which stops the send. AttributeValue("Nickname") returns empty instead. Use it whenever a field might be missing.
%%[ SET @nick = AttributeValue("Nickname") ]%%
Nickname: "%%=v(@nick)=%%"Fallbacks
Combine AttributeValue(), Empty() and IF so a missing value never produces "Hi ,".
%%[
SET @name = AttributeValue("FirstName")
IF Empty(@name) THEN SET @name = "there" ENDIF
]%%
Hi %%=v(@name)=%%,Strings
Concat, Uppercase, ProperCase, Length, Substring, Replace. Positions start at 1, not 0.
%%=Uppercase(Concat(FirstName, " ", LastName))=%%
%%=Substring("Marketing", 1, 6)=%%Conditionals
IF … THEN / ELSEIF / ELSE / ENDIF. Blocks can open and close around plain HTML, which is how most email templates switch content. String comparison with == is case-insensitive.
%%[ IF PlanTier == "vip" THEN ]%% Your VIP concierge is ready. %%[ ELSEIF PlanTier == "premium" THEN ]%% Your premium perks are waiting. %%[ ELSE ]%% Upgrade today. %%[ ENDIF ]%%
Lookups
Lookup(de, returnField, keyField, keyValue) returns one value from another data extension. Each extra field is another lookup, so keep them few.
%%[
SET @sku = AttributeValue("LastSKU")
SET @name = Lookup("Products_DE", "Name", "SKU", @sku)
]%%
Still thinking about %%=v(@name)=%%?Rowsets and loops
LookupRows and LookupOrderedRows return a rowset. Walk it with FOR @i = 1 TO RowCount(@rows), Row() and Field(). Rows start at 1.
%%[
SET @rows = LookupOrderedRows("Orders_DE", 3, "OrderDate desc",
"SubscriberKey", AttributeValue("SubscriberKey"))
FOR @i = 1 TO RowCount(@rows) DO
SET @row = Row(@rows, @i)
]%%
Order %%=Field(@row, "OrderId")=%%
%%[ NEXT @i ]%%Formatting numbers and dates
.NET-style patterns: FormatNumber(39, "C2") gives $39.00, Format(@date, "dddd, MMMM d") gives Saturday, November 14. DateAdd and DateDiff handle date math.
%%=FormatNumber(AttributeValue("Points"), "N0")=%%
%%=Format(AttributeValue("NextAppointment"), "MMM d, yyyy")=%%Skipping a subscriber
RaiseError("reason", true) skips this subscriber instead of sending a broken message. Without true it can stop the entire send.
%%[ IF Empty(AttributeValue("LastSKU")) THEN
RaiseError("No SKU on profile", true)
ENDIF ]%%Common mistakes
- Using a bare field name that may not exist. Prefer
AttributeValue(). - Writing
=in an IF. Comparisons use==;=is only for SET. - Forgetting
ENDIForNEXTwhen a block spans HTML. - Counting from 0. Substring, IndexOf, Row and loops all start at 1.
- One Lookup per field on large sends. Each call is a query; fetch a rowset once instead.
- Assuming journey data is live. Journey entry data is a snapshot taken at entry.
2. Playground
Your code runs against one sample subscriber stored the way SFMC stores it: a sendable row plus related data extensions. Change the subscriber to test your fallbacks. Exercises are graded automatically, and the right panel shows the same task in Liquid so you can feel the difference.
What AMPscript sees: data extensions
This playground runs a teaching-sized AMPscript interpreter written for this page, not Salesforce's engine. It supports the functions in the cheat sheet. Dates use your browser's time zone.
3. AMPscript cheat sheet
| Task | Syntax | Notes |
|---|---|---|
| Code block | %%[ … ]%% | Runs code, outputs nothing by itself |
| Inline output | %%=v(@var)=%% | One expression |
| Personalization string | %%FirstName%% | Field from the sendable data extension |
| Comment | /* note */ | Inside a block |
| Declare / set | VAR @a, @bSET @a = "x" | Variables start with @ |
| Read a field safely | AttributeValue("Field") | Empty instead of an error if missing |
| If / else | IF a == b THEN … ELSEIF … ELSE … ENDIF | Can span blocks with HTML in between |
| Logic | AND OR NOT == != < > | No arithmetic operators; use functions |
| Loop | FOR @i = 1 TO n DO … NEXT @i | Also DOWNTO |
| Empty check | Empty(@a) IsNull(@a) | |
| Inline if | IIF(cond, "yes", "no") | |
| Strings | Concat() Uppercase() Lowercase() ProperCase() Length() Substring(s, start, len) IndexOf() Replace() Trim() | Positions start at 1 |
| Math | Add() Subtract() Multiply() Divide() Mod() | |
| Numbers | FormatNumber(39, "C2") → $39.00FormatNumber(1250, "N0") → 1,250 | C currency, N number, P percent |
| Dates | Format(@d, "dddd, MMMM d")FormatDate(@d, "MM/dd/yyyy")Now() DateAdd(@d, 7, "D") DateDiff(@a, @b, "D") | Units: Y, M, D, H, MI |
| Lookup one value | Lookup("DE", "ReturnField", "KeyField", @key) | More key pairs allowed |
| Lookup rows | LookupRows("DE", "KeyField", @key)LookupOrderedRows("DE", 3, "Date desc", "KeyField", @key) | Returns a rowset |
| Walk a rowset | RowCount(@rows) Row(@rows, @i) Field(@row, "Name") | Rows start at 1 |
| Output from a block | Output(v(@a)) OutputLine() | |
| Skip this subscriber | RaiseError("reason", true) | true skips the subscriber, not the whole send |
| Not in the playground | HTTPGet() InsertDE() UpsertDE() ContentBlockByKey() TreatAsContent() | Real SFMC functions with side effects or external calls |
Official reference: AMPscript guide. Compare with the Liquid cheat sheet.