Substring
Extracts a portion of a string starting at the given index for the specified length. Runtime-proven on a live Marketing Cloud Engagement CloudPage — including a start position below 1 that clamps silently while a negative length aborts the page.
Syntax
Substring(sourceString, startPosition[, substringLength]) → string
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
sourceString |
string | number | Yes | String to take a portion of |
startPosition |
string | number | Yes | 1-based position to start at, as a whole number |
substringLength |
string | number | No | Number of characters to take, as a whole number |
Example
%%[
VAR @part
SET @part = Substring("Hello World", 1, 5)
]%%
Part: %%=v(@part)=%%
Renders Part: Hello.
The usual pattern pairs it with IndexOf to cut a value at a separator — note the guard, because a missing separator would otherwise produce a negative length:
%%[
VAR @fullName, @space, @first
SET @fullName = "Dale Cameron"
SET @space = IndexOf(@fullName, " ")
IF @space > 1 THEN
SET @first = Substring(@fullName, 1, Subtract(@space, 1))
ELSE
SET @first = @fullName
ENDIF
]%%
That guard is not optional — a negative length aborts the page, see below.
Return value
string — the requested portion of the source.
An empty string is the only sentinel: it comes back when the start position is past the end of the source, when the requested length is 0, and when the source itself is empty. The returned text is otherwise an open domain, so there is no closed set of values to test for.
Behaviour
Positions are 1-based and the extraction runs forward. Substring("Hello World", 1, 5) gives Hello, starting at 7 gives Wor for a length of 3, and position 11 is the trailing d.
Omitting the length returns the remainder. Substring("Hello World", 7) gives World, and starting at the final position gives just d.
A start past the end returns an empty string, and an over-long length is capped. Starting at 20 in an eleven-character source returns nothing at all, while asking for 99 characters from position 7 returns the five that remain rather than failing.
Numbers are accepted for all three parameters and numeric strings for the two numeric ones. Taking two characters from position 3 of the numeric literal 9876543 gives 76, and quoting the start, the length, or both gives the same result as the bare numbers. A boolean source renders an empty string — it never becomes extractable text — and a boolean in either numeric position aborts the page, as do a decimal and a lettered string.
Counting is in UTF-16 code units, the same unit Length counts. In a source built as caf + Char(233) + " time", which measures 9, taking one character at position 4 yields a value whose own Length is 1, and starting at 6 lands exactly on the following word.
The two numeric arguments disagree about negatives
| Call | Renders |
|---|---|
Substring("Hello World", 1, 5) |
Hello |
Substring("Hello World", 0, 5) |
Hello |
Substring("Hello World", -3, 5) |
Hello |
Substring("Hello World", 3, 0) |
(empty) |
Substring("Hello World", 3, -2) |
page aborts |
A start position below 1 is silently clamped to the first character, so an off-by-one in the position is invisible. A negative length is not tolerated: it aborts the page with HTTP 422 and discards everything already rendered. Since lengths are usually computed by subtracting two positions, guard the arithmetic before the call rather than expecting the same forgiveness the start position gets.
Catalogued on Differs from official docs. The docs are silent on both bounds rather than wrong about them, so the entry is not flagged as contradicting them.
Show test script
%%[
VAR @b
SET @b = RequestParameter("b")
/* documented forms plus the accepted argument types */
IF @b == "safe" THEN
OutputLine(Concat("HEAD=[", Substring("Hello World", 1, 5), "]"))
OutputLine(Concat("MID=[", Substring("Hello World", 7, 3), "]"))
OutputLine(Concat("ONE=[", Substring("Hello World", 11, 1), "]"))
OutputLine(Concat("REST=[", Substring("Hello World", 7), "]"))
OutputLine(Concat("STARTATEND=[", Substring("Hello World", 11), "]"))
OutputLine(Concat("STARTPASTEND=[", Substring("Hello World", 20, 3), "]"))
OutputLine(Concat("STARTPASTEND2=[", Substring("Hello World", 12), "]"))
OutputLine(Concat("LEN0=[", Substring("Hello World", 3, 0), "]"))
OutputLine(Concat("EMPTYSRC=[", Substring("", 1, 3), "]"))
OutputLine(Concat("NUMSRC=[", Substring(9876543, 3, 2), "]"))
OutputLine(Concat("STARTSTR=[", Substring("Hello World", "7", 3), "]"))
OutputLine(Concat("LENSTR=[", Substring("Hello World", 7, "3"), "]"))
OutputLine(Concat("BOTHSTR=[", Substring("Hello World", "1", "5"), "]"))
OutputLine(Concat("SRCBOOL=[", Substring(true, 1, 2), "]"))
ENDIF
/* a start below 1 is clamped, an over-long length is capped */
IF @b == "clamped" THEN
OutputLine(Concat("START0=[", Substring("Hello World", 0, 5), "]"))
OutputLine(Concat("STARTNEG=[", Substring("Hello World", -3, 5), "]"))
OutputLine(Concat("LENOVER=[", Substring("Hello World", 7, 99), "]"))
ENDIF
/* positions count UTF-16 code units, matching Length */
IF @b == "unicode" THEN
VAR @u
SET @u = Concat("caf", Char(233), " time")
OutputLine(Concat("ULEN=[", Length(@u), "]"))
OutputLine(Concat("UACCENTLEN=[", Length(Substring(@u, 4, 1)), "]"))
OutputLine(Concat("UAFTER=[", Substring(@u, 6), "]"))
ENDIF
/* every branch below aborts the page: the start marker never renders */
IF @b == "lenneg" THEN
OutputLine(Concat("--- lenneg start ---"))
OutputLine(Concat("LENNEG=[", Substring("Hello World", 3, -2), "]"))
ENDIF
IF @b == "startdec" THEN
OutputLine(Concat("--- startdec start ---"))
OutputLine(Concat("STARTDEC=[", Substring("Hello World", 2.5, 3), "]"))
ENDIF
IF @b == "lendec" THEN
OutputLine(Concat("--- lendec start ---"))
OutputLine(Concat("LENDEC=[", Substring("Hello World", 1, 3.7), "]"))
ENDIF
IF @b == "badstr" THEN
OutputLine(Concat("--- badstr start ---"))
OutputLine(Concat("BADSTR=[", Substring("Hello World", "abc", 3), "]"))
ENDIF
IF @b == "startbool" THEN
OutputLine(Concat("--- startbool start ---"))
OutputLine(Concat("STARTBOOL=[", Substring("Hello World", true, 5), "]"))
ENDIF
IF @b == "lenbool" THEN
OutputLine(Concat("--- lenbool start ---"))
OutputLine(Concat("LENBOOL=[", Substring("Hello World", 1, true), "]"))
ENDIF
IF @b == "a0" THEN
OutputLine(Concat("--- a0 start ---"))
OutputLine(Concat("A0=[", Substring(), "]"))
ENDIF
IF @b == "a1" THEN
OutputLine(Concat("--- a1 start ---"))
OutputLine(Concat("A1=[", Substring("Hello World"), "]"))
ENDIF
IF @b == "a4" THEN
OutputLine(Concat("--- a4 start ---"))
OutputLine(Concat("A4=[", Substring("Hello World", 1, 5, 2), "]"))
ENDIF
]%%
OutputLine given a bare string literal renders an empty line. Wrap the argument in Concat() or your start and done markers vanish silently — which looks exactly like the function failing.
Availability
| Platform | Available |
|---|---|
| Marketing Cloud Engagement | Yes |
| Marketing Cloud Next | Yes, from API 67.0 |
See also
- Differs from official docs — the negative-argument asymmetry in full
IndexOf— locates the position to start atLength— the counting unit positions and lengths use- Official reference · ampscript.guide