@UVEXISTS
@UVEXISTS is a REDCap action tag that checks a value is already saved in the project, such as a specimen ID registered on another form or an existing record ID.
REDCap can check that a value looks right, but not that it refers to something real.
A lab result can name a specimen ID that was never registered, and a follow-up form can
point to a participant record that does not exist. @UVEXISTS checks, as the
value is entered, that it is already saved in the project: a record ID, or
a value of another field such as the specimen ID on the collection form.
Syntax
@UVEXISTS=record
@UVEXISTS=[specimen_id]
@UVEXISTS={"in":"[specimen_id]","event":"enrolment_arm_1","scope":"dag",
"match":{"site_code":"[site]"},
"message":"Register the specimen on the collection form first.",
"blockSave":"hard"}
| Key | Meaning |
|---|---|
in | record (a record ID of this project), or one field
reference such as [specimen_id] (a value saved in that field, in any record) |
event | Look in one event only, by its unique event name |
scope | project (default), dag (records of the same
Data Access Group) or event (the event of the entry being checked) |
match | Look only at saved entries whose target field equals a field of
this record, e.g. {"site_code":"[site]"}; up to 5 pairs |
return | Copy fields of the entry found into fields of this form,
e.g. {"lab_site":"[result_site]"}; up to 10 pairs (see below) |
where | A condition the entry found must meet, e.g.
"[consent]='1' and [status]<>'3'"; up to 10 fields (see below) |
whereMessage | The message staff see when the value is saved, but
not in an entry that meets where |
surveys | true to check on surveys too (off by default) |
project | Look in another project on the same server, by project id or by an alias set in this project's module settings; that project must agree (see below) |
caseSensitive | true compares letter case exactly, in
the lookup and in when (off by default: sp-1 finds SP-1) |
message, blockSave, when | As for the other tags |
Field types
Text, dropdown, radio and SQL fields. The field searched in, and each pair of
match fields, must be stored the same way: two dates, two datetimes to the
minute, two datetimes to the second, two times of the same precision, or neither a date
nor a time, because dates are compared as stored. Two fields that hold numbers must use the
same decimal mark.
What is checked, and when
- The check runs when the value is entered or changed, not on every keystroke. It shows found, not found, or could not check. "Could not check" never blocks a save. A failed answer to Save's second question about a held "not found" (below) leaves that "not found" in force.
- Clicking Save re-checks the field first and waits for an answer still on its way, up to 10 seconds. A value typed and saved at once is therefore checked before the save.
- With several
whenbranches, the page sends the values its conditions read, so the server answers from the branch the page is enforcing. - Values are trimmed and A-Z letter case is ignored unless the rule sets
"caseSensitive":true. When the searched field holds numbers (an integer or number Text field, a calc or a slider), values compare by value, so7finds007. Dates are compared as REDCap stores them (Y-M-D), whatever format the field shows. @UVEXISTS=recordnever finds the record being edited. A record ID typed in another letter case is found after one read of every record ID, and so is007for record7when the record-ID field is validated as a number.- Save asks again about a "not found" the page has held for 30 seconds or more: the value may have been saved elsewhere since. That click waits for the answer, and the next click is decided by it. When the server cannot answer, the earlier "not found" stands.
- A blank value checks nothing, and so does a blank
matchfield on the same page. Amatchfield on another form that is still blank shows "could not check" to staff. - Signed-in users are answered only when they may open the forms the lookup reads (the
searched field's, the
matchfields' and thewherefields'), and at most 60 times a minute. A user in a Data Access Group is answered only on records of their own group. Staff in no group see the record the value was found in; staff in a group see it only when it is in their group. A record-ID lookup never names a record. - REDCap may confine a group user's reads to their own group. A "not found" from a rule that looks across groups is kept only when the module sees other groups' records in the same request; otherwise it is "could not check".
- The page config never carries where the rule looks (
in,event,scope,match,where). On a survey a misconfigured rule shows a general notice, and the "no access" reason staff see names no form or field. - On a survey the check runs only when the rule says
"surveys": true, and the answer is found or not found, never a record. The opt-in is refused when any field the lookup touches is an Identifier. Survey lookups may cause at most 60 reads of a whole searched field a minute per project.
Looking in another project
@UVEXISTS={"in":"[specimen_id]","project":"lab","match":{"site_code":"[site]"}}
- An administrator turns on @UVEXISTS in other projects in the Control Center. It is off by default.
- The searched project enables the module and lists the asking project under
Projects that may look up values here, with the fields it may search or
filter on (
recordfor its record ID;wherefields count as searched) and who gets an answer: only users with rights to those fields there (the default; a user in a Data Access Group there is answered from their group only), or any signed-in user of the asking project, with found or not found only and no Identifier searched. Survey respondents are answered only under the second choice, when the searched project also allows it and the rule says"surveys": true. - Until that agreement passes, every problem shows the same setup error, and nothing of the searched project is read. Field errors come after.
- The record found there is never shown. Every lookup, answered or refused, is written to the searched project's module log, with the value as a keyed hash under that project's key, or left out when that project logs no values.
- At most 30 lookups a minute per browser session into one project. One project answers at most 1,200 a minute for signed-in data entry, another 1,200 for post-save checks and scans, and 120 for survey respondents; the first two are Control Center settings. Over any of them the answer is "could not check", and the searched project's log gets one "throttled" line a minute.
- The audit asks as the user who saved. A user in a Data Access Group of the searched project gets "could not check" after saving: a check confined to that group would log a value saved in another group as missing, and the same save by someone else as clean.
- The Validation scan reads the searched project once per searched field and request. Each branch of the rule is judged on its own. A branch the scanning user would not be answered for, or that needs a project where that user is in a Data Access Group, is reported as not evaluated. A stored scan that looked up values in a project the reader would not be answered by is not shown to that reader.
Copying fields of the entry found
With return the lookup works like a spreadsheet VLOOKUP and copies fields of
the entry it finds into fields of this form. Each key is a field of the entry found and each
value a field here, the same way round as match. record copies the
record ID of the entry found. A rule takes up to 10 pairs.
@UVEXISTS={"in":"[specimen_id]",
"return":{"lab_site":"[result_site]","coll_date":"[result_coll_date]"}}
@UVEXISTS={"in":"[specimen_id]",
"return":{"record":"[source_record]","coll_date":"[result_coll_date]"}}
@UVEXISTS={"in":"record","event":"enrolment_arm_1","return":{"enrol_date":"[mother_enrol_date]"}}
@UVEXISTS={"in":"[specimen_id]","project":"lab","return":{"site_code":"[result_site]"}}
The first rule, on result_specimen_id, fills the site and collection date
of the registered specimen. The second also copies the record the specimen was registered
in. The third, on mother_record_id, reads the mother's enrolment date from the
enrolment event, and the fourth copies from the lab project when its agreement lets this
project copy site_code. For SP-1, registered in record 12 with
lab_site 2 and coll_date 2026-03-01, the first rule writes
2 into result_site and, for a result_coll_date shown
as D-M-Y, 01-03-2026.
Which values are copied
- The rule owns its fields. A found value fills them. "Not found", a blank
value, a Missing Data Code, a
whenthat is false and a value no branch applies to empty them. "Could not check" leaves them as they are. A branched rule owns the fields of every branch, so the branch that applies fills its own and empties the others'. Mark the fields@READONLY, since a value typed there is overwritten at the next check. - Where the values come from. The entry where
inand everymatchmatched, in the same record, event and repeat instance. A copied field must be on the form ofinor on a form that does not repeat. In a longitudinal project its form must also be in every event the entry can be found in (theeventnamed, or else every event that holds the form ofin, and with"scope":"event"only those that also hold this field's form), or the rule is refused. With"in":"record"every copied field must be on a form that does not repeat, and a longitudinal project names the event the values are read from withevent, which must not be a repeating event. - Several entries. When more than one saved entry holds the value, the
values are copied only when every entry holds the same ones. Otherwise nothing is
copied, an amber line under the green "Found" says how many saved entries hold the
value with different values to copy, and the post-save audit logs a rule problem.
Narrow the lookup with
match. Copyingrecordfrom two records is a disagreement too. - Data Access Groups. A user in a group gets values only from entries of their own group. A value found only in another group is still found, nothing is copied and the amber line says so. When REDCap confines the user's reads to their group, such a value reads as "could not check".
- A copy kept until the next save. The values are copied when this record is judged. A later change to the entry found shows on the page when this form is opened, and reaches the saved record when this record is saved again.
What a field here can take
The value is copied as REDCap stores it, so the field here must be able to hold every value the field there can.
| Field there | Field here |
|---|---|
| Any value | A Text field without validation, or a Notes field |
| Dropdown, radio, yes/no, true/false | A choice field that has each of its codes, or a Text field without validation (it gets the code) |
| A date, datetime or time | A Text field with the same kind of date or time; the order it shows the date in may differ |
| A number | A Text field with number validation and the same decimal mark.
When the field here fixes its decimal places (integer, number_1dp,
number_2dp and so on), the field there must fix the same number of them,
because number_2dp takes exactly two places and 7.5 would
fail it |
| Any other validation | A Text field with the same validation |
- The field here must be on this form. It cannot be this field, a field the lookup reads,
the record ID, a calc, slider, SQL, checkbox, file or descriptive field, a field filled
by
@CALCTEXT,@CALCDATEor@SETVALUE, or one that shows@RICHTEXTor a BioPortal ontology search. - A field flagged Identifier there may go only into a field flagged Identifier here, so a de-identified export does not carry it.
- One writer per field. A rule that copies two values into one field is
refused. Two rules that write one field are both refused, whether both are
@UVEXISTSrules or one is an@UVRANGErule withreturn. - No chains. A rule that copies values cannot read a field that a rule
copies or returns to, as its own field or in
in,matchorwhen. The save works out every value before it writes any, so a chain would lag one save behind. The rule that reads is refused and names the rule that writes.
Rights, surveys and when the values are written
- Rights. A copied value shows data from the entry found, so the page
fills the fields only for a user who may open every form the lookup reads (the form of
the searched field, of each
matchfield and of each copied field). Anyone else gets "could not check". The save checks the person who saved the same way; without those rights the fields keep their values and the audit logs a rule problem. - Surveys and saves without a user. A rule with
returncannot use"surveys":true. A survey respondent's save neither fills nor empties the fields, even when the form is also a survey. A save REDCap runs without a signed-in user, such as a scheduled job, copies nothing. In both cases a blank value, a Missing Data Code, awhenthat is false, a value no branch applies to or a branch withoutreturnstill empties the fields, and any other value leaves them as they are. - On the page. The page fills the fields as it opens and after each
answer. The first fill does not mark the form as changed, though calculations and
branching logic that read the fields still run; later fills raise an input and a change
event, as typing does. Save waits for a lookup on its way, even with
"blockSave":"off"and on a@READONLYfield. A "found" answer is asked again when the field is judged 30 seconds or more after it. - On save. The post-save audit asks again as the person who saved and
writes the values that differ, together with any
@UVRANGEreturned values, in one save per event and instance of this record. A number field holding the same number spelled another way (7.80for7.8) is not rewritten; any other field is written exactly, so007replaces7. - A choice the page does not offer. When
@HIDECHOICEor@UVCHOICEStook the option off the page, the page empties the dropdown or radio field and an amber line names the field. The save writes the code all the same, since the field's choice list holds it. - The cost. A lookup that copies values reads every saved value of the searched field and of the copied fields, as a "not found" does for any other rule, because a narrowed read can miss an entry spelled another way. The read is kept for the rest of the request and the signed-in user, so an import of many records by one user reads the searched project once.
- The project setting "@UVEXISTS returned values" (
exists-return-no-save) keeps the page fill and writes nothing when a record is saved. The Validation scan never writes.
Copying from another project
The searched project lists the fields others may copy apart from the fields they may
search, under "Fields of this project whose values it may copy" (record for its
record ID), and only for the choice "Only users who have rights". A rule that copies a field
not on that list gets the same setup error as any other refusal there. A refusal that would
describe how that project is built (a copied field on a repeating form or missing from an
event, an event that repeats) gives one message instead, "project N cannot give the values
"return" asks for". A choice field here that lacks a code of the field there is refused
without naming the code. The page and the save check the user's rights in both projects:
here the forms of this field and of each match field here, there the forms of
the fields searched and copied.
Only entries that meet conditions
With where the value must be saved in an entry that meets a condition on
that entry. The condition takes the language of when: one comparison, or several
joined with and, or and not, with parentheses and
keywords in any letter case.
@UVEXISTS={"in":"[specimen_id]","project":"lab",
"where":"[consent]='1' and ([status]<>'3' or [withdraw_date]>[coll_date])",
"whereMessage":"This specimen is registered, but not consented or withdrawn."}
@UVEXISTS={"in":"[kit_number]","where":"[kit_check(2)]='1' and not([kit_check(4)]='1')"}
@UVEXISTS={"in":"record","project":"screening","event":"screening_arm_1","where":"[eligible]='1'"}
| Key | Asks | Its fields are |
|---|---|---|
when | Does the rule apply to this record at all? | fields of this record |
match | Does the entry found hold the same value as a field of this record? | a field there, paired with a field here |
where | Does the entry found meet fixed conditions? | fields of the entry found |
- The answer. A value saved only in entries that fail
whereshows staff the rule'swhereMessage, or "This value is saved, but not in an entry that meets this rule's conditions." It is enforced as "not found", and empties the fields areturnfills. A survey respondent sees the plain "not found". A value saved nowhere is "not found", as before. - One entry.
wherereads the entry whereinand everymatchmatched: one event row, or one repeat instance with its event row. One entry that passes is enough. - How values compare. Text compares with the rule's letter case, and two
numbers by value. A comparison with a blank side of
<,>,<=or>=is never true, andnot(...)around it does not make it true. A blank status is not'3', so[status]<>'3'passes it. Missing Data Codes are plain values. Dates are written Y-M-D, times as'08:30', and numbers with a decimal point; a decimal-comma field is read by value. A checkbox box is tested as[field(code)]. - What it can test. Up to 10 fields of the entry found, each on the form
searched or on a form that does not repeat, and in a longitudinal project in every event
the entry can be found in. No event or instance references and no
[record]. With"in":"record", every field is on a form that does not repeat, and a longitudinal project names the event withevent. - Rights and agreements. The
wherefields are fields the lookup reads: a user needs their forms,"surveys":trueis refused when one is an Identifier, and another project must list them among the fields this project may search or filter on. A refusal that would describe that project gives one message, "project N cannot apply this "where" condition to the entry the lookup finds".
Audit
The post-save audit logs a violation as type: exists with reason
not-found, or filtered for a value saved only in entries that fail
where, and the Validation scan reports it. Saving the form that holds a
match field checks the lookup again. The scan reads the searched field once per
rule per scan request and checks every record against it. A lookup that could not be
completed is reported as a rule problem, never as a pass. A scan confined to one Data Access
Group reports a rule that looks across groups as not evaluated, because a value saved only in
another group would read as not found. A durable scan with @UVEXISTS rules claims
coverage through its change fence only when no record changed during the run.