Using Dynamic Data Attachments to flag potential duplicate submissions or previously registered entries

Description:
During data collection, users often need to identify potential duplicate entries. There are several ways to achieve this, and one of them is through the use of Dynamic Data Attachments (DDA).

In this topic, I’d like to demonstrate how this can be achieved using a recursive form lookup through Dynamic Data Attachments.

For this example, we’ll work with a single form and connect it to itself.

Setting up the form

Create a simple form with two questions:

  • id a text field with the numbers appearance, used to store identification numbers
  • name a basic text field for storing names

Connecting the form with itself

Go to Settings → Connect Projects and enable Data Sharing.

Then, under Import other project data, connect the form to itself and assign a name. In this example, we use: parent_form_registration

Note: This name is crucial. It will be used in the Dynamic Data Attachment lookup.

Setting up the lookup on the XLSForm

Add a type xml-external with the name used in the previous step in our example parent_form_registration this will allow the form to be attached to itself for creating the lookups.

Create calculation fields for the lookups

Add a row with type xml-external using the name defined earlier in this case: parent_form_registration
This allows the form to attach to itself and perform lookups.

Create calculation fields for the lookups

Use the following pattern in your calculations:
instance('XML-EXTERNAL-NAME')/root/data[{XPATH_TO_PARENT_FORM_DATA}=current()/../{XPATH_TO_CHILD_FORM}]/{XPATH_OF_DATA_TO_IMPORT}
From our example:

  • For id:
    instance('parent_form_registration')/root/data[id=current()/../id]/id
  • For name:
    instance('parent_form_registration')/root/data[id=current()/../id]/name

Note: If your form uses groups, those group names must be included in the XPath for the lookup to work correctly.
instance('XML-EXTERNAL-NAME')/root/data[{XPATH_TO_PARENT_FORM_DATA}=current()/../{XPATH_TO_USE_CHILD_FORM}]/{XPATH_OF_DATA_WE_NEED_TO_BRING_IN}

From our example it looks like this
For id: instance('parent_form_registration')/root/data[id=current()/../id]/id
For name: instance('parent_form_registration')/root/data[id=current()/../id]/name

Set up constraints and a note to display results

Add a constraint to the id question so that it triggers when a duplicate (previously registered user) is found.

At the bottom of the form, add a note that becomes relevant only when a match is detected, alerting the enumerator that a registered user already exists.

2 Likes

Hi.

I use this same self-referencing logic in all my registration forms, with one addition to handle Edit Mode.

When editing an existing record, find_id will match against the record’s own id, which is correct, data does exist, but the constraint string-length(${find_id})<=0 will mistake it for a duplicate and block the submission.

To differentiate “editing myself” from a “true duplicate,” I added a hidden field called uuid with this calculation:
once(uuid())

once() only fills an empty node, so a new record gets a fresh UUID, while an existing record being edited already has one and keeps it intact. The UUID persistence is key for this to work. Add uuid to your shared fields in Data Sharing settings, with id/name, or any other field fetched from the attached XML.

The updated find_id calculation to exclude “editing my own record”:
instance('parent_form_registration')/root/data[id=current()/../id and uuid!=current()/../uuid]/id

The rest like len field, and the warning note keep working exactly the same. Now find_id only returns a match when another record has your ID. The check works correctly whether you’re creating a new record or editing your own.

Hi @Isslam
Thanks for sharing. This example was indeed designed for a one‑way data flow. If you try to edit an already‑registered record, it will fail because the form will end up matching the same entry against itself.

Your approach of using once is definitely helpful for these use cases.