Power Automate: stop duplicate records before they reach your team
A repeated submission should not become a second job. Build a SharePoint intake flow that recognises the same request, protects the destination and gives uncertain outcomes a clear recovery route.
4 October 2026 · For UK operations teams and Microsoft 365 makers
One request. One record.
Destination uniqueness enabled · Illustrative exampleIllustrative example: outcomes of the reviewed attempts
Illustrative example route: Request Intake → unique Job Register → operations review.
The expensive part is the second action
Duplicate rows become duplicate work: two engineers dispatched, two orders raised or two managers chasing the same request. Deleting the spare row later does not undo those consequences.
The user submits again
A slow confirmation prompts another attempt. If each submission receives a new reference, the flow cannot tell a repeat from a genuinely new job.
Two runs overlap
Both runs search for a reference. Both find nothing. Both try to create it. A preliminary search improves feedback but cannot enforce uniqueness.
The write outcome is unclear
A request may reach the destination even when its acknowledgement does not reach the flow. Recovery must inspect the stored result before deciding what to do next.
Define the boundary before building
This tutorial records maintenance requests from one intake list into one job register. It ends at record creation; payments, emails, approvals and dispatches need their own repeat-safe design.
What you need
- Permission to create and configure two SharePoint lists and a cloud flow.
- A connection that can read and write both lists.
- An upstream reference retained across retries of the same request.
- Agreement that site codes, references and job types are case-insensitive in this process.
Check your tenant’s licensing and connector policies before rollout. This example uses SharePoint actions and built-in flow operations.
Choose the right identity
Use source | site | request reference. Here, portal-v1|LEI|REQ-1042 is an illustrative example.
Do not generate a fresh GUID or timestamp inside every run. If a new upstream reference is issued for every click, fix that behaviour first. Similar free-text descriptions are not a reliable identity.
Corrections to an existing job need an authorised update path. They must not silently overwrite the original during duplicate recovery.
Build the protected intake flow
Use the names below when creating columns and actions. Enter expressions through the Expression editor without a leading @. All sample values are illustrative examples.
Create the lists and enforce uniqueness
Create Request Intake with required single-line text columns SiteCode, RequestReference and JobType. Add optional text columns Outcome and TargetItemID. Keep Title and populate it with the reference.
Create Job Register with the columns below. Create their names without spaces so the expressions can use the same internal names.
| Column | Type | Setting or purpose |
|---|---|---|
| Title | Single line of text | Store the request reference. |
| RequestKey | Single line of text | Required; Enforce unique values = Yes. |
| SiteCode, RequestReference, JobType | Single line of text | Required; store the normalised values. |
| SourceItemID | Number | The intake item that first created this job. |
Open the RequestKey column settings and enable unique values. SharePoint supports this on single-line text, uses case-insensitive uniqueness and requires an index. Resolve existing duplicates before enabling it on a populated list. Microsoft reference.
Capture a stable key
Create an automated cloud flow using SharePoint When an item is created on Request Intake. Use the created-only trigger so writing Outcome back does not start the flow again.
Add three Compose actions, named exactly as follows:
SiteCode
toUpper(trim(coalesce(triggerBody()?['SiteCode'], '')))
RequestReference
toUpper(trim(coalesce(triggerBody()?['RequestReference'], '')))
JobType
toUpper(trim(coalesce(triggerBody()?['JobType'], '')))
Add a Condition using this expression. Compare its result with the Boolean true. The pipe character is reserved as the key separator.
and(
not(empty(outputs('SiteCode'))),
not(empty(outputs('RequestReference'))),
not(empty(outputs('JobType'))),
not(contains(outputs('SiteCode'), '|')),
not(contains(outputs('RequestReference'), '|'))
)
On No, update the intake Outcome to Invalid input and terminate with Failed. On Yes, add Compose RequestKey:
concat(
'portal-v1|',
outputs('SiteCode'),
'|',
outputs('RequestReference')
)
Validate lessOrEquals(length(outputs('RequestKey')), 255) in another Condition. Reject an oversized key; do not truncate it, because different requests could then share the same key. Continue only on Yes.
Let the destination decide
Add a Scope named WriteRecord containing only SharePoint Create item, named CreateJob, targeting Job Register. Map Title and RequestReference to the RequestReference Compose output; map RequestKey, SiteCode and JobType to their respective Compose outputs; map SourceItemID to the trigger’s ID.
After the scope, add two parallel scopes with different Run after settings:
Recorded: run only when WriteRecord succeeds. Update the intake item to OutcomeRecordedand TargetItemID frombody('CreateJob')?['ID'].RecoverWrite: run only when WriteRecord fails or times out. Follow the recovery checks below.
In every intake Update item action, use the trigger ID and re-supply required intake fields from the trigger. Keep receipt updates outside WriteRecord: a failed receipt update must not be misclassified as a failed job creation. Run after reference.
Read back and compare the actual record
Inside RecoverWrite, add SharePoint Get items on Job Register, named FindJob. Set Top Count to 1. In Filter Query, enter the following expression; it doubles apostrophes inside the OData string literal.
concat(
'RequestKey eq ',
decodeUriComponent('%27'),
replace(
outputs('RequestKey'),
decodeUriComponent('%27'),
decodeUriComponent('%27%27')
),
decodeUriComponent('%27')
)
Example filter produced: RequestKey eq 'portal-v1|LEI|REQ-1042' (illustrative example). Check the column’s internal name if adapting an existing list. For large lists, use the indexed key and test lookup beyond the first 5,000 items; Microsoft documents a filtered-query limitation and pagination guidance. Get items reference.
After FindJob succeeds, check equals(length(body('FindJob')?['value']), 1). Only in its Yes branch, compare the normalised job type:
equals(
first(body('FindJob')?['value'])?['JobType'],
outputs('JobType')
)
- Match: update intake Outcome to
Existing record confirmed, with TargetItemID fromfirst(body('FindJob')?['value'])?['ID']. End successfully. This covers a repeat or a write whose acknowledgement was lost; do not claim to know which. - Different job type: record
Conflict - review required, keep the original job unchanged and terminate with Failed. - No match: record
Unresolved write - review requiredand terminate with Failed. A missing match is not proof that another write is safe.
Add a failure branch after FindJob for failed or timed-out reads. Record Recovery lookup failed if the intake list remains available, then terminate with Failed. If logging also fails, preserve the failed run for the support owner. Never treat all create errors as duplicates.
This example’s immutable payload contains only site, reference and job type. If you add amount, currency, asset or other business fields, validate and compare all relevant values before accepting a repeat.
Test the paths that ordinary demos miss
| Test | Expected evidence |
|---|---|
| New reference | One job created; intake receipt points to it. |
| Repeat the same reference and payload | Still one job; existing record confirmed. |
| Submit matching requests simultaneously | One destination record; the other run confirms it or remains an explicit unresolved exception. |
| Same key, different JobType | Conflict visible; original job unchanged. |
| Blank reference, pipe character or oversized key | Validation fails before CreateJob. |
| Apostrophe in the reference | Escaped lookup finds the exact key. |
| Remove create permission, keeping read permission | No matching record means failure, not “duplicate handled”. |
| Record exists but recovery cannot read it | Lookup failure remains visible; no blind second action. |
To exercise uncertain-write recovery, use a test copy that successfully creates the item and then deliberately fails its write scope. Confirm that recovery finds the committed row. Remove this test-only action afterwards; production WriteRecord must contain only CreateJob.
Give exceptions an owner and a safe next step
The operations owner decides whether two references mean the same job. The flow owner maintains mappings, access, monitoring and recovery. Nominate deputies and document how to find the destination record from an intake receipt.
Review failed runs and intake items with missing or exception Outcomes. For a recovered item, confirm the business record before replaying anything. Fix permissions or mapping faults first. Preserve the same RequestKey on a retry.
Teach users to retain their original reference and use the correction route. Restrict who can edit or delete destination keys; deleting a protected record removes its duplicate barrier. Set retention around the period in which requests may be replayed.
Know what this pattern does—and where it ends
The guarantee is scoped to one enforced key in one destination list. The rest of the business process still needs deliberate controls.
Concurrency is not the duplicate barrier
Setting trigger concurrency to one can sequence runs of that flow, but another flow or user can still write to the list. It also introduces waiting-run limits. Keep the destination constraint and choose concurrency for capacity and ordering needs.
A unique job is not a unique email
A later email, approval or payment can repeat independently. Use destination-supported idempotency for consequential external actions. Where the target cannot provide it, design reconciliation and manual recovery instead of promising exactly-once execution.
Keep the business key stable
All writers must construct the same key for the same request. Do not remove punctuation or change case rules without business agreement. A new key format needs a migration plan so old requests are still recognised.
Escalate when transactions matter
If one business operation must commit changes across several records together, this single-list pattern is insufficient. Assess a transactional data design and the target system’s own controls before scaling a consequential process.
Measure recovered work, not just green runs
Use the intake receipts and exception review to distinguish useful automation from hidden rework.
Record integrity
Track confirmed repeats, conflicts and unresolved writes separately. Review unexpected changes in the repeat rate; they may indicate a poor submission experience.
Operational response
Measure time from exception creation to an owner’s decision. Count intake items with no receipt so failed logging does not disappear from the picture.
Business impact
Baseline actual duplicate dispatches and correction time. Compare equivalent periods after release; a recovered write is not automatically a prevented duplicate job.
Is your intake ready for a repeat submission?
Tick only controls you have tested. This checklist helps prioritise work; it is not certification.
Frequently asked questions
Does Get items before Create item prevent duplicates?
No. Two runs can both find no record before either writes. Use the search for convenience, but enforce uniqueness at the destination.
Should I set trigger concurrency to one?
Only when the process needs sequential runs and you have assessed queue behaviour. It does not protect the destination from other flows or direct writes, so it cannot replace a unique key.
Can I treat every failed create as a duplicate?
No. Read back the exact key and compare the relevant stored values. If the lookup fails, no match exists or the payload differs, keep an explicit exception.
Does this guarantee an email or payment happens exactly once?
No. It protects record creation in the destination list. External actions need their own idempotency support or reconciliation and recovery process.
Make automation dependable when real life repeats itself
Smart Statistics helps UK businesses design Power Automate workflows, SharePoint solutions and operational reporting with clear ownership, practical recovery and measurable business value.