Since Salesforce has no native 1:1 relationship field type, you implement one with two pieces working together: a relationship field (Lookup or Master-Detail on the child pointing to the parent), plus a uniqueness rule that prevents two child records from referencing the same parent. There are three common ways to enforce that uniqueness.
Step 1 — Pick the relationship type
- Lookup: child owns its own lifecycle and sharing. Use when the two records have independent ownership or when the relationship is optional.
- Master-Detail: child’s lifecycle and sharing follow the parent. Use when the child is conceptually part of the parent and 1:1 is a requirement.
Either way, this gives you the field that lets one child point to its parent. Now you need to stop a second child from pointing to the same parent.
Option A — Validation rule (admin-only, fastest)
On the child object, create a validation rule that fails if another record already references the same parent.
The cleanest way is to use a rollup-style approach via Process Builder/Flow or a helper field, or — more commonly — to use the Apex-free pattern: create a roll-up summary Child_Count__c on the parent (only works if the relationship is master-detail) and then on the child write a validation rule:
AND(
NOT(ISBLANK(Parent__c)),
Parent__r.Child_Count__c >= 1,
OR(
ISNEW(),
ISCHANGED(Parent__c)
)
)
This blocks creation of a second child for any parent that already has one. Works only with master-detail since lookup doesn’t allow roll-up summaries.
For lookup-based 1:1, you can use a helper formula on the parent populated by Flow or a trigger — but at that point Option B is cleaner.
Option B — Apex trigger (works for any relationship; scales for bulk)
A before insert, before update trigger on the child object:
trigger ChildOneToOne on Child__c (before insert, before update) {
Set<Id> parentIds = new Set<Id>();
for (Child__c c : Trigger.new) {
if (c.Parent__c != null) parentIds.add(c.Parent__c);
}
if (parentIds.isEmpty()) return;
// Find existing children for those parents
Map<Id, Id> parentToExistingChild = new Map<Id, Id>();
for (Child__c existing : [
SELECT Id, Parent__c
FROM Child__c
WHERE Parent__c IN :parentIds
]) {
parentToExistingChild.put(existing.Parent__c, existing.Id);
}
for (Child__c c : Trigger.new) {
Id existingId = parentToExistingChild.get(c.Parent__c);
if (existingId != null && existingId != c.Id) {
c.addError('Each Parent can have only one Child__c.');
}
}
}
Key bulkification points: one SOQL for all parents in the batch, ignore matches where the existing child is the current record (so updates don’t false-flag).
Option C — Duplicate Management rule (declarative, scales)
Salesforce’s Duplicate Rules engine can enforce a matching rule on a lookup field. Setup:
- Create a Matching Rule on the child object matching on the lookup field (
Parent__c). - Create a Duplicate Rule that uses that matching rule, action = Block on Create/Edit.
- Activate both.
The user gets an in-line duplicate warning/block if they try to create a second child for the same parent. This works for both lookup and master-detail and requires no code, but counts against your duplicate-rule limits per object.
Option D — Make the lookup field Unique via a workaround
Salesforce won’t let you mark a Lookup or Master-Detail field as Unique directly. But you can create a Text formula field that just returns the lookup’s Id as text, and mark it Unique and as an External Id. This is a clever hack that works for small data volumes but has limitations:
- Formula fields can’t be marked Unique in the field UI — but you can sometimes get the same effect via a text field populated by Flow
A cleaner version: add a Text(18) field Parent_Key__c marked Unique and External Id, then use a Flow (record-triggered) to copy the lookup Id into it on insert/update. The uniqueness constraint on the text field is enforced by the platform.
Which option to pick
| Option | Code | Bulk-safe | Works for Lookup | Works for M-D |
|---|---|---|---|---|
| A — Validation rule + roll-up | No | Yes | No (roll-up needs M-D) | Yes |
| B — Trigger | Yes (Apex) | Yes (if written right) | Yes | Yes |
| C — Duplicate Rule | No | Yes | Yes | Yes |
| D — Unique text-field workaround | No (Flow) | Yes | Yes | Yes |
For modern admin-led implementations, Duplicate Rule (Option C) is the recommended default. It’s declarative, fast to ship, and the error UI is good.
End-to-end recipe (declarative)
- Create
Child__cwith a Lookup fieldParent__ctoParent__c. Mark required. - In Setup → Matching Rules, create rule on
Child__cmatchingParent__cexactly. - In Setup → Duplicate Rules, create rule using that matching rule, action Block.
- Activate both. Test: try to create two
Child__crows for the sameParent__c— second one is blocked.
That’s a real 1:1 in Salesforce without writing any code.
Verified against: Salesforce Help — Duplicate Management and Apex Triggers. Last reviewed 2026-05-17.