Skip to main content

Basics — Marketing Cloud AMPscript fundamentals

Where AMPscript runs in Marketing Cloud, the three syntax forms (block, inline function, field interpolation), the function categories at a glance, and the decision tree for when to reach for AMPscript vs SSJS vs SQL.

Reference·Last updated 2026-05-13·Drafted by Lira · Edited by German Medina

AMPscript is the personalization language Marketing Cloud uses where you need per-recipient content at send time. It runs inside emails as they render for each subscriber, inside CloudPages composing dynamic HTML, inside Mobile Push and SMS, and anywhere else MC is templating content for an individual recipient. The function surface looks small (~150 functions) but the language has its own scoping rules, three different syntax forms, a NULL surface with three different functions for "nothing here", and a personalization preview that uses a different code path than the actual send — see gotchas.

This page is the orientation. The rest of the AMPscript catalog (function reference pages, Style Guide, debugging snippets) builds on the mental model below.

Official syntax

AMPscript has three syntax forms. Each does something different:

<!-- 1. Block form — multi-statement logic.
     Anything between %%[ and ]%% is AMPscript, not HTML. -->
%%[
  SET @firstName = AttributeValue("FirstName")
  IF Empty(@firstName) THEN
    SET @firstName = "Friend"
  ENDIF
]%%

<!-- 2. Inline function form — render a value into the HTML.
     The function or variable is evaluated and the result is output. -->
<p>Hello, %%=v(@firstName)=%%</p>

<!-- 3. Field-interpolation form — render a DE column or
     Subscriber Attribute directly. No @ prefix. -->
<p>Your order #%%OrderId%% ships tomorrow.</p>

A typical email body interleaves all three: a block at the top to compute variables and fetch lookups, inline function references in the prose to render the values, and field-interpolation for raw DE columns that don't need transformation.

The supported language surface inside a block:

| Feature | Available | |---|---| | SET @var = value (assignment) | ✓ | | IF / ELSEIF / ELSE / ENDIF | ✓ | | FOR / NEXT (numeric loop) | ✓ | | VAR @x, @y, @z (declaration without assignment) | ✓ | | Function calls — Lookup, Empty, Concat, DateAdd, Substring, etc. | ✓ | | OUTPUT / OUTPUTLINE (render explicitly from a block) | ✓ | | Comments — /* ... */ (single or multi-line) | ✓ | | WHILE loops | ✗ — only FOR | | BREAK / CONTINUE | ✗ — restructure with IF | | Custom functions | ✗ — no user-defined functions | | Regular expressions | ✗ — character-by-character with Substring and IndexOf | | try / catch | ✗ — defensive defaults with Empty() instead | | Native error handling | ✗ — most failures return NULL or 0 silently (see gotchas) |

Reference:

What survives in production

Where AMPscript runs — and how each context changes the patterns

AMPscript runs in several MC contexts. The language is the same; the runtime context changes which patterns make sense.

Email body (the primary context):

  • Renders once per recipient at send time.
  • Variables and lookups happen for every recipient — a 50k Send means 50k AMPscript runs, each independent.
  • Performance matters at scale: a heavy LookupRows in the email body is run 50,000 times. Pre-shape the data in a SQL Activity and read a thin DE in the email.
  • Personalization preview is necessary but not sufficient — see gotchas — #9.

CloudPages:

  • Renders once per page request.
  • Reads URL parameters via RequestParameter() — the AMPscript equivalent of Request.GetQueryStringParameter() in SSJS.
  • Can write to Data Extensions via InsertData / UpdateData / UpsertData — same write functions as in emails, same silent-failure behavior (see gotchas — #10).
  • AMPscript and SSJS can coexist on the same page; the rule is one block per language, with explicit handoffs.

Mobile Push and SMS:

  • Subset of the AMPscript surface — some functions that work in email don't work here (the surface changes per channel).
  • Body length constraints make AMPscript output errors visible immediately (truncated message, wrong character count).
  • Test on the actual channel, not in preview.

Triggered sends:

  • AMPscript runs per trigger, same as email per send.
  • The trigger payload is available via field interpolation just like a sendable DE row.

The three sources where data comes from

AMPscript pulls data from three places, and confusing them is the #1 cause of "the email rendered blank" failures:

| Source | How you reference it | When it's available | |---|---|---| | Sendable DE columns | %%ColumnName%% inline, [ColumnName] inside a block, or via Lookup() for a different DE | When the Send Activity ran against a DE that has that column | | Subscriber Attributes | AttributeValue("AttrName") or %%AttrName%% | When the All Subscribers profile has that attribute configured AND the recipient is a valid subscriber | | Local variables | %%=v(@varName)=%% inline, @varName inside a block | Only after SET @varName = ... ran successfully in a prior block |

When two sources have the same name (a DE column FirstName and a Subscriber Attribute FirstName), the inline %%FirstName%% resolves one of them based on the rendering context, and the resolution rule isn't always intuitive. The defense: name them differently. See gotchas — #4.

Output mechanisms

Three ways to render a value into the rendered HTML / text:

<!-- 1. Inline interpolation — the most common pattern. -->
<p>Hello, %%=v(@firstName)=%% — your order is %%=v(@orderTotal)=%%.</p>

<!-- 2. OUTPUT() inside a block — useful when you need to conditionally
     emit content based on logic that's hard to express in pure HTML. -->
%%[
  IF @segment == "Gold" THEN
    OUTPUT(Concat("<p>Exclusive: ", @goldPromo, "</p>"))
  ENDIF
]%%

<!-- 3. CONCAT() and assignment — build the string in a variable, then
     interpolate it. The pattern when output requires more than one
     branch. -->
%%[
  SET @greeting = Concat("Hello, ", @firstName)
  IF @hasOrders THEN
    SET @greeting = Concat(@greeting, " — welcome back")
  ELSE
    SET @greeting = Concat(@greeting, " — first time?")
  ENDIF
]%%
<p>%%=v(@greeting)=%%</p>

OUTPUT() is useful but easy to abuse. The Cleon convention: prefer building strings in variables and interpolating with %%=v(@x)=%%. OUTPUT() is reserved for the cases where mid-block emit is the cleanest expression of intent.

Function categories at a glance

AMPscript's function surface clusters into eight rough categories. Each has its own dedicated reference page in this catalog (as they ship):

| Category | Examples | Purpose | |---|---|---| | String functions | Concat, Substring, Lowercase, Trim, IndexOf, Replace | Build and slice text | | Date functions | Now, DateAdd, DateDiff, DatePart, FormatDate | Time arithmetic and formatting | | Math functions | Add, Subtract, Multiply, Divide, Mod, Round | Arithmetic (yes, math is functions, not operators) | | Validation | Empty, IsNull, IsEmailAddress, IsNumeric, IsPhoneNumber | Type and format checks | | Data Extension | Lookup, LookupRows, InsertData, UpdateData, UpsertData, Field, Row | Read and write DEs | | Subscriber / Profile | AttributeValue, _subscriberKey, _messagecontext, _jobid | Read the current recipient's context | | Cloud-write | UpdateSingleSalesforceObject, CreateSalesforceObject, RetrieveSalesforceObjects | Read/write Sales/Service Cloud objects | | Encoding / Hashing | URLEncode, Base64Encode, SHA256, MD5 | Encoding for tracking links, signature generation |

Reference pages will land for each category as they ship — see Related for the current list.

Quick decision

Reach for AMPscript when:

  • You need per-recipient personalization at send time in an email or push. AMPscript runs inline at render time; SSJS and SQL don't.
  • You need to read a sendable DE column for the current recipient — %%ColumnName%% is the simplest expression.
  • You need to conditionally render HTML based on the recipient's data — IF @x THEN OUTPUT(...) ENDIF.
  • You need a single-row lookup for context (segment, tier, last order) — Lookup() is the right tool.

Reach for SSJS instead when:

  • You're in a CloudPage doing multi-step orchestration (read DE, call API, write DE, then render).
  • You need HTTP callouts to external APIs (HTTP.Get / HTTP.Post exist in SSJS, not AMPscript).
  • You need bulk reads or writes that AMPscript's per-recipient model doesn't fit.
  • You need error handling beyond defensive defaults (SSJS has try / catch).

Reach for SQL Query Activity instead when:

  • The work is set-based read / transform / write across a Data Extension before the send.
  • You need to shape the audience (filtering, joining, deduplication) — that's the SQL Activity's job, run upstream of the Send Activity.
  • You're computing aggregates (counts, sums, averages) across the audience — AMPscript can't.

Don't reach for AMPscript when:

  • The personalization can be computed once upstream in a SQL Activity and stored in a column. A de_email_<send> DE with pre-computed columns is faster (one SQL run for 50k recipients vs 50k AMPscript runs) and easier to debug.
  • The logic is non-trivial. AMPscript with more than ~30 lines is a hand-off liability — refactor the data prep into SQL upstream so the email body becomes thin.

Related

More AMPscript reference pages incoming: String / Date / Math functions · Validation · Data Extension functions (Lookup family + write functions) · Subscriber / Profile functions · Cloud-write functions · Style Guide.

Plus how-to debugging snippets for the production failure shapes — render-time blanks, Lookup returning NULL, UpsertData duplicates from a CloudPage form, silent Cloud-write failures.