SfSkills Domains Docs GitHub

Home / apex / ampscript-development

ampscript-development

Use this skill when writing, debugging, or reviewing AMPscript in Marketing Cloud email bodies, subject lines, preheaders, SMS, push notifications, or Cloud Pages — including Lookup/LookupRows data retrieval, IF/ELSEIF conditional blocks, FOR loops over rowsets, and inline personalization. NOT for Journey Builder configuration. NOT for Server-Side JavaScript (SSJS), WSProxy, or REST API calls from content — use apex/ssjs-server-side-javascript. NOT for SQL Query Activities in Automation Studio — use data/marketing-cloud-sql-queries.

Open the skill folder on GitHub


AMPscript Development

This skill activates when a practitioner needs to write or debug AMPscript — the Marketing Cloud server-side scripting language evaluated at send time — to retrieve data from Data Extensions, apply conditional logic per subscriber, iterate over multi-row results, or embed dynamic personalization in email, SMS, push, or Cloud Page content. It does not cover SSJS, SQL Query Activities, or Journey Builder entry configuration.


Before Starting

Gather this context before working on anything in this domain:


Core Concepts

AMPscript Execution Model

AMPscript is evaluated server-side at send time — once per subscriber per send. It is not evaluated when the email is designed, previewed without a subscriber context, or when the template is saved. The implication: preview with a specific test subscriber selected, not the generic preview, or personalization strings and Lookup calls will return empty.

Code blocks use %%[ ... ]%% syntax. Multiple blocks in a single content area execute sequentially in document order. Inline output uses %%= expression =%% or the equivalent %%=v(@variable)=%% shorthand.

Variables and Declaration

Every variable must be declared with SET before use:

%%[
SET @firstName = AttributeValue("FirstName")
SET @loyaltyTier = Lookup("Loyalty_DE", "Tier", "SubscriberKey", _subscriberkey)
]%%

Referencing @firstName before the SET statement causes a runtime error and may suppress the entire email. AMPscript is case-insensitive for variable names but case-sensitive for DE names and field names in Lookup calls on case-sensitive (CS) function variants.

Lookup Functions

Four lookup functions cover the main retrieval patterns:

FunctionReturnsWhen to Use
Lookup(DE, returnField, matchField, matchValue)Single scalar valueFetch one field from one matching row
LookupRows(DE, matchField, matchValue)Rowset (all matches)Fetch all matching rows for iteration
LookupOrderedRows(DE, count, sortField, sortOrder, matchField, matchValue)Rowset (capped, sorted)Fetch top-N rows in order
LookupCS(...) / LookupRowsCS(...)Same as aboveCase-sensitive match — required when match values include mixed case and the DE is case-sensitive

Lookup() returns the value of returnField from the first matching row only. If multiple rows match, only the first is returned (undefined ordering unless the DE has a single PK match). Use LookupRows() when multiple rows may match.

FOR Loops Over Rowsets

Iterating over a LookupRows() result uses a numeric FOR loop with Row(@rowset, @index) to dereference each row:

%%[
SET @rows = LookupRows("Order_DE", "SubscriberKey", _subscriberkey)
SET @rowCount = RowCount(@rows)

FOR @i = 1 TO @rowCount DO
  SET @row = Row(@rows, @i)
  SET @orderNum = Field(@row, "OrderNumber")
  SET @total = Field(@row, "Total")
  /* output handled inline below */
NEXT @i
]%%

The inline output for each iteration must be placed between DO and NEXT @i or use separate inline expressions. RowCount() returns 0 when no rows match — always guard with an IF @rowCount > 0 check before the loop to avoid rendering empty list markup.


Common Patterns

Pattern: Conditional Content Block by Subscriber Attribute

When to use: Show different email body sections based on a field value in the sendable DE or a related DE (e.g., loyalty tier, product preference, region).

How it works:

%%[
SET @tier = AttributeValue("LoyaltyTier")
]%%

%%[ IF @tier == "Gold" THEN ]%%
  <p>As a Gold member, enjoy 20% off your next order.</p>
%%[ ELSEIF @tier == "Silver" THEN ]%%
  <p>As a Silver member, enjoy 10% off your next order.</p>
%%[ ELSE ]%%
  <p>Join our loyalty program to start earning rewards.</p>
%%[ ENDIF ]%%

Why not the alternative: Dynamic Content blocks in Email Studio are configured via the UI and evaluated before send — they cannot use AMPscript expressions as their conditions, only data filter rules. Use AMPscript IF/ELSEIF when conditions are data-driven and require arithmetic, string concatenation, or nested lookups.

Pattern: Multi-Row Order / Product List in Email Body

When to use: Render a subscriber-specific list (recent orders, product recommendations, event registrations) fetched from a non-sendable DE.

How it works:

%%[
SET @subKey = _subscriberkey
SET @orders = LookupOrderedRows("RecentOrders_DE", 5, "OrderDate", "DESC", "SubscriberKey", @subKey)
SET @orderCount = RowCount(@orders)
]%%

%%[ IF @orderCount > 0 THEN ]%%
<ul>
%%[ FOR @i = 1 TO @orderCount DO ]%%
  %%[ SET @row = Row(@orders, @i) ]%%
  <li>%%=Field(@row, "OrderNumber")=%% — %%=Field(@row, "Total")=%%</li>
%%[ NEXT @i ]%%
</ul>
%%[ ELSE ]%%
<p>No recent orders found.</p>
%%[ ENDIF ]%%

Why not the alternative: SSJS can perform the same retrieval via Platform.Load("Core", "1") and DataExtension.Init(), but SSJS is evaluated once per page/send context and has a higher runtime cost. For per-subscriber rendering AMPscript is preferred. Use SSJS only when making HTTP API calls or manipulating complex data structures unavailable in AMPscript.


Decision Guidance

SituationRecommended ApproachReason
Per-subscriber field personalization from sendable DEAttributeValue() or personalization string %%FieldName%%Direct access to sendable DE row; no extra query cost
Fetch a single value from another DELookup()Returns scalar value; lightest-weight retrieval
Fetch multiple rows from another DELookupRows() or LookupOrderedRows()Returns iterable rowset; use FOR loop for rendering
Condition depends on subscriber data at send timeAMPscript IF/ELSEIF/ELSEEvaluated per subscriber; Dynamic Content blocks cannot use runtime expressions
Need to call a REST API or perform HTTP fetchSSJSAMPscript has no HTTP call functions; use HTTPGet in AMPscript only for simple URL fetches with no auth headers
Case-sensitive field match requiredLookupCS() / LookupRowsCS()Default Lookup variants are case-insensitive; CS variants force exact case matching
Subject line or preheader personalizationInline %%= AttributeValue("Field") =%%Block syntax %%[ ]%% not supported in subject/preheader

Step-by-step instructions for an AI agent or practitioner working on this task:

  1. Identify the channel and content area — confirm whether the AMPscript will live in the email body, subject line, preheader, SMS, push, or Cloud Page. Subject lines and preheaders support inline expressions only.
  2. Map the data source — identify the Data Extension name(s) and field(s) needed. Note whether the lookup field is the DE primary key (fast) or a non-PK field (full table scan risk on large DEs).
  3. Choose the retrieval function — use AttributeValue() for sendable DE fields, Lookup() for a single value from another DE, LookupRows() or LookupOrderedRows() for multi-row results. Use CS variants only if case-sensitive matching is required.
  4. Write and declare all variables before use — place all SET statements at the top of the %%[ ... ]%% block. Never reference a variable before it is declared.
  5. Build the FOR loop with null guard — always check RowCount(@rows) > 0 before entering a FOR loop. Access each row with Row(@rows, @i) and each field with Field(@row, "FieldName").
  6. Preview with a real subscriber context — use the Preview tab with a subscriber selected or a test send to a seed list. Generic preview does not evaluate Lookup calls.
  7. Validate output and fallback paths — confirm the ELSE branch renders correctly when no data matches, and that required fields are never null in output positions.

Review Checklist

Run through these before marking work in this area complete:


Salesforce-Specific Gotchas

Non-obvious platform behaviors that cause real production problems:

  1. Primary key immutability post-creation — Once a Data Extension is created, its primary key field(s) cannot be changed. If the wrong field is set as the PK, the DE must be recreated and all historical data migrated. This frequently affects teams that set Email as the PK and later need to switch to ContactKey.
  2. Generic preview suppresses Lookup errors — Previewing an email without selecting a specific subscriber returns empty strings for all Lookup() and AttributeValue() calls rather than an error. Teams ship broken personalization to production because generic preview "worked." Always preview with a real subscriber or send to a test seed list.
  3. Block syntax in subject lines silently breaks the send — Using %%[ SET @x = ... ]%% in a subject line field does not produce an error in the UI; it renders the literal %%[...]%% text to subscribers. Marketing Cloud only evaluates inline %%= ... =%% syntax in subject and preheader fields.
  4. LookupRows cap at 2,000 rows with no error — If a subscriber has more than 2,000 matching rows, LookupRows() silently returns 2,000 rows with no warning. Loops that depend on a complete dataset will silently produce incomplete output. Use LookupOrderedRows() with an explicit count to make the cap intentional.
  5. Smart quotes cause parse failures — Copying AMPscript from Word, Google Docs, or a rich-text editor frequently introduces curly/smart quotes (" "). Marketing Cloud's parser treats these as invalid characters, and the error message is often generic ("script error") rather than pointing at the quote character.

Output Artifacts

ArtifactDescription
AMPscript code block%%[ ... ]%% block with variable declarations, lookup calls, and conditional/loop logic
Inline output expression%%= v(@var) =%% or %%=Field(@row, "Field")=%% placed in HTML content
Decision noteDocumentation of AMPscript vs SSJS choice rationale for the use case
Test subscriber recordSeed list entry or test data record used to validate personalization at send time

Package files