Advanced Repeats
The guidance below assumes an existing knowledge of Contract Express Author and the Repeats functionality. It is also a working template and is designed to be uploaded to Contract Express to provide a full overview of the functionality described herein. For more information on this topic, please see the Definitive Guide to Repeats found on the Knowledge Base, which contains a wealth of information on Contract Express.
Each section introduces a scenario, explains the intended outcome, and provides reference markup. The examples are provided for demonstration and learning purposes only. They should be reviewed and adapted before use in any production template or migrated documentation environment.
A Simple Repeat
A repeat structure can be used when the same wording or data pattern must appear more than once. The following example shows repeated landlord details. Later examples build on this structure to demonstrate numbering, counting, punctuation, and repeat context handling.
- The Landlord is [Insert name] whose registered office is at [Insert address] and whose company number is [Insert company number].
- The Landlord is [Insert name] whose registered office is at [Insert address] and whose company number is [Insert company number].
Etc
The same wording repeats for each additional landlord that is collected.
Using Repeat structure:
<
>Repeat LandlordNumber
The Landlord is {LandlordName} whose registered office is at {LandlordAddress} and whose company number is {LandlordCompanyNumber}.The same repeat structure can also be presented in a table. When a repeated table is required, the repeat span should cover the full row so that each additional answer creates another row.
Name | Address | Company Number |
|---|---|---|
< Repeat LandlordNumber {LandlordName} | {LandlordAddress} | {LandlordCompanyNumber}> |
In the example above, the repeat is driven by a user-defined number of landlords. However, repeats can also be configured to run a fixed number of times without referencing a question. For example, applying to a span causes the content within the span to be repeated twice.
Repeat 2
Numbering repeated items
RepeatCounter
can be used to display the sequence number for each repeat. This is useful where generated text must refer to Landlord 1, Landlord 2, and so on.<
Repeat LandlordNumber
Landlord {RepeatCounter} is {LandlordName} whose registered office is at {LandlordAddress} and whose company number is {LandlordCompanyNumber}.>A custom title can be specified in the
Repeat Title
field to replace the default repeat headings displayed in the questionnaire. For example, instead of First of Two
, a more meaningful title can be shown:
RepeatCounter
Format
"
converts the number into an ordinal word, such as wth
"First
, Second
, Third
, and so on.Counting the Repeats
A repeat count is often needed when determining whether singular or plural wording should be used. In these cases, the variable that controls the repeat can be referenced directly. For example, the variable can be used to determine whether to display singular or plural text.
LandlordNumber
The Landlord <
LandlordNumber IsMoreThan 1
s jointly> own <LandlordNumber Is 1
s> the Property.A computable can also be used to reduce markup in the document. For example, a computable named
LandMore
can be defined as LandlordNumber IsMoreThan 1
.
The Landlord <
LandMore
s jointly> own <Not LandMore
s> the Property.There is no need to create separate computables for both
Is 1
and Is More Than 1
conditions. As shown in the example above, the singular form can be handled by reversing the span logic, reducing the need for additional computables.Alternatively, a custom function can also be used to apply the same logic to different number variables. For example, a function named
LandMoreF
can be defined as %1 IsMoreThan 1
.
The Landlord <
LandMoreF(LandlordNumber)
s jointly> own <Not LandMoreF(LandlordNumber)
s> the Property.Another option is to use the
Pluralize
function. This can also be used where singular and plural results must be controlled by a number variable.Presentation Options
Repeats can be presented in the questionnaire in different ways. The presentation is controlled through the presentation option on the number variable.
- Edit Boxdisplays an explicit question, such as Enter the number of landlords.
- Calculated from Repeatremoves the number question and usesInsert,Add, andRemovecontrols instead.
The presentation option affects the questionnaire experience only. The generated document output remains based on the repeat markup used in the template.
The
Repeat Title
field in Questionnaire Groups
can be used to refine the default repeat heading. For example, it can replace generic headings such as One of Two
with a more meaningful label for the authoring scenario.Nested Repeats, Unrepeated, and Nonrepeated
Nested repeats are used when one repeated list contains another repeated list. The
Unrepeated
function can be used to remove a variable from the nearest repeat context so the value is asked once and reused where needed.Single repeat context
Let’s say we have a list of board members who are all part of the same company. In this case, we want the board member name and job title questions to be asked for each board member, but ideally the company question should only be asked once. To do this, we apply the
Unrepeated
function to the company variable, which removes this particular variable from the repeat context and allows the question to only be asked once whilst reusing the answer.Name | Job Title | Company |
|---|---|---|
< Repeat BoardMemberNumber {BoardMemberName} | {BoardMemberJobTitle} | {Unrepeated(BoardMemberCompany)}> |
Nested repeat context
If employees are listed under each board member, another repeat can be added inside the board member repeat. When the company value must be used inside the nested repeat,
Unrepeated
is applied twice because each use removes only the nearest repeat context.Employee | Reports to | Company |
|---|---|---|
< Repeat BoardMemberNumber<Repeat EmployeeNumber {EmployeeName} | {Unrepeated(BoardMemberName)} | {Unrepeated(Unrepeated(BoardMemberCompany))}>> |
We can now see that
BoardMemberCompany
is Unrepeated twice, as each Unrepeated only removes the variable from the nearest repeat context. To see how many repeat contexts are applied, use the Outer Span button to help identify the applicable repeat contexts.We recommend avoiding the use of wherever possible. Although it can be useful in certain scenarios, it is more demanding on the system and can usually be replaced with
Nonrepeated
Unrepeated
. To remove content from multiple repetition contexts, apply Unrepeated
multiple times until the content is lifted out of the required repeat levels.If working with multiple repeat levels, consider creating custom functions to simplify the use of
Unrepeated
. This can make expressions easier to read, reuse, and maintain. For example, a custom function named U2
can be defined as: Unrepeated(Unrepeated(%1))
Using removes the need to repeatedly nest
U2
Unrepeated
functions whenever content needs to be lifted out of two repeat levels.<
Repeat BoardMemberNumber<Repeat EmployeeNumber
{U2(BoardMemberCompany)}>>Repeats in Suites
When working with a template suite, consider not only the relevancy of the current template, but also the relevancy rules defined across the suite. In most cases, these rules originate in the master template and are inherited by dependent templates.
The same principle applies to repeat contexts. As a result, a subtemplate may display warnings or notices related to a repeat context that is not immediately obvious when viewing the subtemplate in isolation. These notices often make more sense when the suite is considered as a whole.
For example, a master template may contain the questionnaire and collect repeated information, such as a list of buyers using the
BuyerName
variable. The repeat context established in the master template can then be inherited by dependent templates, even if the repeat structure is not directly visible within those templates.
Notice that the subtemplate is repeated in the master template using the number of buyers. This repeat around the
Attach
statement creates a repeat context that is inherited by the subtemplate. When automating the subtemplate, all content should be considered to be operating within this inherited repeat context.In this example, one NDA (subtemplate) is generated for each buyer using the
Repeat BuyerNumber
rule applied to the Attach
statement. When the questionnaire is completed, a separate NDA is produced for each buyer. Within the NDA subtemplate, buyer-specific information remains within the inherited repeat context. Any information passed from the master template that should appear only once must be removed from that context. This can be achieved by using the Unrepeated
function to lift the content out of the inherited repeat level.For example, a master template may collect repeated buyer information and then attach one sub template for each buyer. In that scenario, the sub template inherits a repeat context from the attach statement. Content in the sub template that is not specific to each buyer may need to be removed from that inherited repeat context by using
Unrepeated
.
This can sometimes cause the validation indicators to show warnings because the local repeat context for unrepeated content is not fully accounted for within the current template. Although this may appear incorrect when the template is viewed in isolation, the repeat context is valid when the template suite is considered as a whole.

It may be convenient to use
Nonrepeated
in these situations, as it avoids these warnings. However, Nonrepeated
uses more system resources than Unrepeated
.The choice between the two functions should therefore be based on factors such as the size and complexity of the template suite, as well as any potential impact on performance. Useful Functions
Let's look at some useful functions for working with repeats.
Punctuation
A repeat span can be combined with a punctuation span to apply punctuation to a repeated list. The
Mark
field should be placed inside the repeat so the correct punctuation appears for each item.<
Repeat FounderNumber Punctuation ", |, and "
{FounderName} ({FounderPercentage}%){Mark}> are the founders of the company.Mark
fields do not require Unrepeated
in this pattern.Collect
Collect
can be used to reuse values from a repeated list elsewhere in the document or questionnaire. Once we have our repeat set up, we might want to use the information from that repeat elsewhere.<
Repeat FounderNumber
{FounderName}, of {FounderCityBase}, who will control {FounderPercentage}% of the company.>The following expression collects all founder names into a formatted list.
{Collect(FounderName) Format ", | and "}
Collect
can also support dynamic questionnaire options. For example, a manager text selection can use Collect(FounderName)
so the selectable options are built from the founders entered earlier.Collect(FounderName)
Collect
can also be used to return a subset of repeated values by applying a condition.{Collect(FounderName, FounderCityBase Is "London") Format ", | and "}
If a collected value is used in another repeated context,
Unrepeated
may be required to remove the collected value from the local repeat context.Exists
Exists
is useful if you want to test whether a condition is true for at least one member of a repeated list.{Exists(FounderCityBase Is "Paris")}
When multiple conditions are combined within a single
Exists
function, the function checks whether all conditions are true for the same item in the repeated list. For example, the expression below tests whether at least one founder is both
based in Paris and owns at least 10% of the company.{Exists(FounderCityBase Is "Paris" and FounderPercentage IsAtLeast 10)}
By contrast, splitting the logic into two separate conditions tests each condition independently. In this example, the expression checks whether there is at least one founder based in Paris and at least one founder who owns more than 10% of the company. These conditions do
not
need to be satisfied by the same person.{Exists(FounderCityBase Is "Paris") and Exists(FounderPercentage IsAtLeast 10)}
Every
Another method of utilising the repeated list is to test whether a specific condition is met for every member of the list. For example, the below will return all of the founders based in New York.
Every
tests whether a condition is true for every member of a repeated list.{Every(FounderCityBase Is "New York")}
MemberNth
and MemberIndex
MemberNth
returns a value at a stated position in a list. MemberIndex
returns the position of a value in a list. These functions are often used together to find a selected value in one list and return the corresponding value from another list.<
Repeat MemberCount
{MemberName}, {MemberTitle}><
Repeat MemberCount<Unrepeated(PresidentSelect) Is MemberName
{MemberTitle}>> {PresidentSelect}This allows the title associated with the selected president to be returned, where the
PresidentSelect
variable is populated using a collection of members. The same result can also be achieved using the following approach:{MemberNth(MemberIndex(PresidentSelect, Collect(MemberName)), Collect(MemberTitle))} {PresidentSelect}
There are two functions at hand in this method. They are used together to get the President’s Title by calculating its position in the list generated by the repeat.
MemberIndex
lets us calculate the index position of a value in a list. In this situation we are using it to calculate the position of the Member that has been selected as president in the list of MemberNames. Index position of the President in the list of MemberNames: {MemberIndex(PresidentSelect, collect(MemberName))}
The next step is to obtain the Title for that Member. Since we know the position that the member is in now, we can obtain the corresponding Title value for that position in the list.
MemberNth
allows us to calculate the value of the member in a list:MemberNth(PositionInList, collect(MemberTitle))
becomes,
{MemberNth(MemberIndex(PresidentSelect, collect(MemberName)), collect(MemberTitle))} {PresidentSelect}
Because the Titles and the Names are in the same repeat context (as they are created from the same repeat), their MemberIndex values will match. This means that we can use MemberNth to calculate the value of one of them by known their position.
This works because
MemberName
and MemberTitle
are created in the same repeat context.GroupByEQ
GroupByEQ
returns the second part of a list of pairs where the first part matches a comparison value. It can be used as another method to return a related value from a repeated list.{GroupByEQ(Collect(List(MemberName, MemberTitle)), PresidentSelect)} {PresidentSelect}
Lets say we had a single pair of values,
A
and B
, we would first need to make them a list (string*) of two, List(A, B). This would then need to be a list of pairs (lists of lists - string**), even if there is only one pair, List(List(A, B)). If our comparison value is C, the logic of the GroupByEQ equation would be,
Return B if A = C
. The function would thus look like; GroupByEQ(List(List(A, B)), C)
.This can be expanded to include more pairs in the 'list of pairs': GroupByEQ(List(List(
A
, 1
B
), List(1
A
, 2
B
), List(2
A
, 3
B
)), 3
C
) .GroupByEQ will run through all of the pairs (A and B), compare each A to C, and return all the Bs where there is a match.
From our example above, the president's title can be retrieved by matching the selected president's name to a collection of paired
MemberName
and MemberTitle
values. Because MemberName
and MemberTitle
are repeated variables, using List
directly would introduce a repeat context issue. To avoid this, Collect
is used to create a collection of name and title pairs; Collect(List(MemberName, MemberTitle))
. Since only one president can be selected, there will only ever be a single matching pair, ensuring that the correct title is returned for the selected president.# operator
The
#
operator returns a specific value of a variable in a specific repeat context.{MemberTitle # Concatenate("[", ToString(MemberIndex(PresidentSelect, Collect(MemberName))), "]")} {PresidentSelect}
When
#
is used inside a repeat, it refers to the local repeat context. To refer to an absolute value, remove the value from the local repeat context first where required.## operator
The
##
operator returns a list of specific values of a variable in a specific repeat context.{MemberTitle ## List(List(ToString(MemberIndex(PresidentSelect, Collect(MemberName)))))} {PresidentSelect}
When
##
is used inside a repeat, it refers to the absolute value referenced.Repeats Based on Lists
Repeats do not have to be based only on whole number variables or fixed numbers. They can also be based on lists. This pattern is often useful for signatory blocks.
In the following example, a multi-select list of signatories controls the number of signatory blocks.
RepeatCounter
returns the relevant member of the list rather than a number.<
Repeat Signatories
Signed by:Print name: {RepeatCounter}>
Repeat items can still be numbered when using this approach by using the function, which returns the position of a value within a list. For example:
MemberIndex
<
Repeat Signatories
{MemberIndex(RepeatCounter, Unrepeated(Signatories))}. {RepeatCounter}>This outputs the first signatory as 1, the second signatory as 2, and so on.
Using Repeated Values in Different Repeat Contexts
The
Repeat
function affects the context in which dependent markup is evaluated. Tools such as Validation errors
, Usage Analysis
, and the various relevancy fields can help identify issues caused by repeat contexts. If messages similar to the following are displayed, further investigation of the repeat structure may be required.
A variable that appears in both repeated and nonrepeated contexts can produce duplicate questionnaire options or undefined field answers in the generated document.

Assume there are separate repeats for buyers and sellers.
<
Repeat BuyerNumber Punctuation "; |; and |"
{BuyerName} of {BuyerAddress}{Mark}> (together the "Buyer")<
Repeat SellerNumber Punctuation "; |; and |"
{SellerName} of {SellerAddress}{Mark}> (together the "Seller")For each buyer, a notice must be generated that includes the list of sellers. Using
SellerNumber
directly inside the BuyerNumber
repeat would create a new repeat context and would imply a separate set of sellers for each buyer.<
Repeat BuyerNumber
Dear {BuyerName}The shares are being sold by [Seller Name] of [SellerAddress] (together the “Seller”).>
A technically separate but functionally equivalent repeat context can be created by repeating over
Collect(SellerName)
. In that pattern, RepeatCounter
represents the current seller name.<
Repeat Collect(SellerName) Punctuation ", "
{RepeatCounter}{Mark}>RepeatCounter then replaces SellerName. To return the corresponding seller address, compare the seller name list with the seller address list and use
MemberNth
to return the value at the matching position.<
Repeat BuyerNumber
Dear {BuyerName},The shares are being sold by [Seller Name] of [SellerAddress] (together the “Seller”).>
The shares are being sold by {RepeatCounter} of
<
(together the "Seller").Repeat Unrepeated(Collect(SellerName)) Punctuation ", |, and |
{MemberNth(ToInteger(RepeatCounter), Unrepeated(Unrepeated(Collect(SellerAddress))))}{Mark}>The correct number of
Unrepeated
functions depends on the repeat context in which the expression is used. Review the context before applying this pattern.Support and additional guidance
For product-specific assistance, contact the Contract Express support team at contractexpress-support@thomsonreuters.com.
When adapting these examples to another platform or documentation system, review the markup, functions, and terminology against the target platform requirements before publishing.