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 Server-Side JavaScript (SSJS), REST API calls from content, SQL Query Activities, or Journey Builder configuration.

Best use case

ampscript-development is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

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 Server-Side JavaScript (SSJS), REST API calls from content, SQL Query Activities, or Journey Builder configuration.

Teams using ampscript-development should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/ampscript-development/SKILL.md --create-dirs "https://raw.githubusercontent.com/PranavNagrecha/AwesomeSalesforceSkills/main/skills/apex/ampscript-development/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/ampscript-development/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How ampscript-development Compares

Feature / Agentampscript-developmentStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

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 Server-Side JavaScript (SSJS), REST API calls from content, SQL Query Activities, or Journey Builder configuration.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

Related Guides

SKILL.md Source

# 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:

- **Channel:** AMPscript syntax is identical across channels (email, SMS, push, Cloud Page), but subject lines and preheaders support only inline `%%= ... =%%` expressions — block syntax `%%[ ... ]%%` in subject lines causes send failures.
- **Data Extension structure:** Confirm the DE name (exact, case-sensitive in some contexts), the primary key field(s), and which field holds the subscriber's match value. Non-PK fields used in `Lookup` or `LookupRows` trigger full table scans on large DEs.
- **Subscriber context:** For email sends, the sendable DE or All Subscribers list provides the subscriber key. `AttributeValue("FieldName")` reads from the sendable DE row for the current subscriber; `Lookup()` reads from any other DE.
- **Common wrong assumption:** Practitioners assume AMPscript variables are available across `%%[ ... ]%%` blocks without re-declaration. Variables declared in one block are available in subsequent blocks in the same content area, but not across separate content areas in a template.
- **Limits:** `LookupRows()` returns a maximum of 2,000 rows. `LookupOrderedRows()` accepts a count cap as its second argument. Neither function paginates — design DEs so subscriber-scoped queries return well under 2,000 rows.

---

## 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:

| Function | Returns | When to Use |
|---|---|---|
| `Lookup(DE, returnField, matchField, matchValue)` | Single scalar value | Fetch 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 above | Case-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

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

---

## Recommended Workflow

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:

- [ ] All variables declared with `SET` before first use
- [ ] `LookupRows()` results guarded with `IF RowCount(@rows) > 0` before FOR loop
- [ ] FOR loop uses `Row(@rows, @i)` and `Field(@row, "FieldName")` — not direct array notation
- [ ] Subject line and preheader use only inline `%%= ... =%%` syntax, not block syntax
- [ ] Lookup field is the DE primary key or has a documented performance note if non-PK
- [ ] All string literals use straight quotes (`"`) not smart/curly quotes
- [ ] Fallback/ELSE content is present and renders correctly when no data matches
- [ ] Tested with a real subscriber via preview or test send — not generic preview only

---

## 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

| Artifact | Description |
|---|---|
| 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 note | Documentation of AMPscript vs SSJS choice rationale for the use case |
| Test subscriber record | Seed list entry or test data record used to validate personalization at send time |

---

## Related Skills

- `data-extension-design` — design DEs with appropriate PKs and indexing before writing AMPscript Lookup calls against them
- `email-studio-administration` — configure sendable DEs, All Subscribers list, and send classification before writing subscriber-context AMPscript
- `marketing-cloud-data-sync` — set up Synchronized Data Extensions (SDEs) used as lookup sources in AMPscript personalization

Related Skills

lwr-site-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when building or customizing sites on the Lightning Web Runtime (LWR) in Experience Cloud — including component authoring, custom theming with --dxp hooks, layout components, and publish lifecycle management. Trigger keywords: build LWR site Experience Cloud, Lightning Web Runtime custom theme, LWR component development community, Build Your Own LWR template, Microsite LWR, lightningCommunity__Theme_Layout, --dxp styling hooks. NOT for Aura-based communities (Build Your Own Aura template). NOT for standard Experience Builder drag-and-drop configuration without code.

data-cloud-activation-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when building developer-driven Data Cloud activation surfaces: webhook Data Action Targets with HMAC-SHA256 signing, Salesforce Platform Event data actions, Data Cloud-Triggered Flows on DMO insert, or Marketing Cloud journey triggers. Triggers on: webhook data action target, Data Cloud triggered Flow not firing, HMAC secret key for data action, platform event from Data Cloud, DMO insert trigger. NOT for configuring standard admin-level Activation Targets (SFTP, ad platform segment publishing, CRM segment activation) — those require admin configuration skills, not this developer extensibility skill.

unlocked-package-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when designing, creating, versioning, or installing unlocked packages: package directory configuration in sfdx-project.json, namespace management, package dependencies, version lifecycle (beta vs. released), ancestor versions, installation keys, and subscriber installation via sf CLI or Package Install UI. NOT for 2GP managed packages (ISV packaging with namespaces, push upgrades, or AppExchange listings), 1GP managed packages, change set deployments, or scratch org setup.

package-development-strategy

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when deciding between Salesforce package development approaches — unmanaged, unlocked, 1GP managed, or 2GP managed — including namespace selection, ISV distribution requirements, upgrade path design, and AppExchange packaging strategy. Trigger keywords: should I use managed or unlocked package, Salesforce package type selection, 2GP vs 1GP managed package, namespace decision Salesforce, ISV AppExchange packaging, unlocked package strategy. NOT for individual package creation steps, scratch org setup, or day-to-day package version build commands.

multi-package-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Designing, orchestrating, and maintaining multi-package architectures in Salesforce DX: dependency DAG design, layered package decomposition, install ordering, cross-package API contracts, mono-repo vs. multi-repo layout, and CI/CD pipeline sequencing for projects with two or more unlocked or managed packages. NOT for single-package creation or versioning (see unlocked-package-development), 2GP managed-package ISV workflows (see second-generation-managed-packages), or change-set deployments.

managed-package-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when building or maintaining Salesforce first-generation managed packages (1GP) for ISV distribution — covers namespace registration, packaging org structure, PostInstall/UninstallHandler Apex interface, push upgrades, Flow version management, and subscriber org considerations. NOT for second-generation managed packages (2GP), unlocked packages, or AppExchange listing setup.

cti-adapter-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when building or debugging a browser-based CTI softphone adapter using the Salesforce Open CTI JavaScript API — covers callcenter.xml definition, Lightning utility item registration, core API methods (enableClickToDial, onClickToDial, screenPop, setSoftphonePanelHeight, saveLog), call logging as Task, and the lightning-click-to-dial LWC component. NOT for Service Cloud Voice Amazon Connect setup, Omni-Channel routing configuration, or CTI adapter AppExchange package selection.

einstein-discovery-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when integrating Einstein Discovery predictions into Salesforce apps, automating bulk scoring jobs, deploying stories as prediction definitions, managing models via API, or querying prediction history. Trigger keywords: Einstein Discovery, smartdatadiscovery, predict endpoint, bulk scoring job, model refresh job, prediction definition, story deployment, regression prediction, multiclass prediction, CRM Analytics ML. NOT for CRM Analytics dashboard design, TCRM dataset management, Einstein Prediction Builder binary classification (which requires no CRM Analytics license), or Einstein Next Best Action recommendation strategies.

saql-query-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when writing, debugging, or optimizing SAQL queries in CRM Analytics — covering pipeline syntax, aggregation, windowing functions (rank, dense_rank, moving_average), cogroup joins, rollup subtotals, piggyback queries, and REST API execution. NOT for SOQL, standard Salesforce reports, SQL databases, or any non-CRM Analytics query language.

analytics-dataflow-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when building, debugging, or optimizing CRM Analytics dataflows — defining node types (sfdcDigest, Append, Augment, computeExpression, computeRelative, Flatten, dim2mea, sfdcRegister), scheduling runs, handling run failures, and tuning performance. NOT for standard data processing outside CRM Analytics, for recipe-based data prep, or for SAQL dashboard query tuning.

xss-and-injection-prevention

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when writing or reviewing Visualforce pages, Apex controllers, or LWC components that output user-supplied data, build dynamic queries, or construct HTTP responses. Triggers: 'XSS in Visualforce', 'SOQL injection vulnerability', 'how to encode output in Apex', 'JSENCODE Visualforce', 'open redirect prevention'. NOT for Apex CRUD/FLS enforcement (use soql-security or apex-crud-and-fls), NOT for Shield encryption (use shield-encryption-key-management), NOT for AppExchange security review process (use secure-coding-review-checklist).

visualforce-security-and-modernization

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when hardening or modernizing legacy Visualforce pages — covers the platform CSRF token model and when disabling it is a security regression, view state encryption guarantees and the 170 KB ceiling, FLS/CRUD enforcement gaps on `<apex:outputField>` and on getters that return sObjects, `<apex:includeScript>` interaction with the org Content Security Policy, hosting LWC inside a VF page via `lightning:container` / `lightning-out`, and the retire-vs-harden-vs-leave-alone decision for an inventory of legacy pages. Triggers: 'should I rewrite this Visualforce page in LWC', 'CSRF protection disabled on Visualforce page is that safe', 'community user sees a field they should not on a Visualforce page', 'view state encryption is that enough for sensitive data', 'how do I host an LWC inside a Visualforce page', 'apex:dynamicComponent and apex:actionFunction safe to keep'. NOT for greenfield Visualforce architecture (use apex/visualforce-fundamentals — controller types, view state pattern selection, PDF rendering); NOT for Visualforce email template authoring (use apex/visualforce-email-templates if/when that skill is authored); NOT for general Apex security review across triggers and async (use apex/soql-security and security/secure-coding-review-checklist).