Salesforceで同じオブジェクトにレコードトリガーフローとApexトリガーがあると、「どちらの値が最後に残るのか」「同じレコードを更新したら、処理はもう一度動くのか」が分かりにくくなります。
公式ドキュメントには保存処理の実行順序が掲載されていますが、実際の構成では値の上書きや再実行まで含めて考える必要があります。今回は取引先の説明項目をFlowとApexの両方から更新し、最終的な値とデバッグログを確認します。
FlowとApexで同じ[説明]項目を上書きした結果、最終的にはafter-save Flowが設定した「04: after-save Flow」が残りました。
設計上の大前提として、今回のFlowとApexの混在は挙動確認のための構成です。実運用では、同じオブジェクトを起点にするレコードトリガーフローとApexトリガーを別々の入口として併用するのは原則避け、どちらかへ責務を寄せます。
今回確認すること
取引先名を変更したときに、次の4つの処理を動かします。
- before-saveレコードトリガーフローで
説明を更新する - before update Apexトリガーで
説明を上書きする - after update Apexトリガーで、その時点の値をログへ出力する
- after-saveレコードトリガーフローで同じ取引先の
説明を再更新する
確認したいのは、公式の実行順序そのものではありません。同じ項目を複数の自動化が更新したときの最終値と、after-save Flowによる再更新でApexが何回動くかを主題にします。
| 確認項目 | 実測結果 |
|---|---|
| before Apexが受け取った値 | 01: before-save Flow |
| 最初のafter Apexが受け取った値 | 02: before update Apex |
| Apexトリガーの実行回数 | before/afterともに2回 |
| 保存後の説明 | 04: after-save Flow |
ボンボン順番を覚えるだけじゃなく、2回目の更新で何がもう一度動くのかまで見てみよう。
検証環境と前提条件
今回は標準オブジェクトの取引先と、標準項目の取引先名、説明を使用します。カスタム項目は作成しません。
| 検証日 | 2026年7月22日 |
|---|---|
| 組織種別 | Developer Edition |
| Salesforceリリース | Summer ’26 |
| APIバージョン | 67.0 |
| オブジェクト | 取引先(Account) |
| 更新の起点 | 取引先名の変更 |
| 確認方法 | 取引先の保存結果、デバッグログ |
既存のFlow、Apexトリガー、入力規則などがある組織では結果へ影響する可能性があります。検証用組織で試すか、取引先にほかの自動化がないことを確認してから進めます。




1.実行回数を記録するApexクラスを作成する
再更新でトリガーが何回起動したか分かるように、beforeとafterの実行回数を保持するクラスを作成します。クラス名はAccountFlowApexTraceとしました。
public class AccountFlowApexTrace {
public static Integer beforeCount = 0;
public static Integer afterCount = 0;
}static変数は同じトランザクション内で値を保持するため、再更新によって同じトリガーが起動した場合も回数を数えられます。


2.Apexトリガーハンドラーを作成する
まず、before updateとafter updateで実行する処理をまとめたトリガーハンドラーを作成します。ログを絞り込みやすいように、出力するメッセージの先頭はORDER_TESTで統一しました。
public class AccountTriggerHandler {
public static void beforeUpdate(
List<Account> newRecords,
Map<Id, Account> oldMap
) {
// 同一トランザクション内でのbefore updateの実行回数を記録
AccountFlowApexTrace.beforeCount++;
for (Account currentRecord : newRecords) {
// 更新前の取引先を取得
Account oldRecord = oldMap.get(currentRecord.Id);
// 実行回数、取引先名の変更有無、現在の説明をログへ出力
System.debug(
LoggingLevel.ERROR,
'ORDER_TEST | before update #'
+ AccountFlowApexTrace.beforeCount
+ ' | Name changed='
+ (currentRecord.Name != oldRecord.Name)
+ ' | Description='
+ currentRecord.Description
);
// 取引先名が変更された場合のみ、説明をApex側の値で上書き
if (currentRecord.Name != oldRecord.Name) {
currentRecord.Description = '02: before update Apex';
}
}
}
public static void afterUpdate(List<Account> newRecords) {
// 同一トランザクション内でのafter updateの実行回数を記録
AccountFlowApexTrace.afterCount++;
for (Account currentRecord : newRecords) {
// 実行回数と、保存後の説明をログへ出力
System.debug(
LoggingLevel.ERROR,
'ORDER_TEST | after update #'
+ AccountFlowApexTrace.afterCount
+ ' | Description='
+ currentRecord.Description
);
}
}
}beforeUpdateメソッドでは、最初に説明の値をログへ出力します。取引先名が変更された更新だけ、02: before update Apexへ上書きします。
2回目の更新はafter-save Flowによる説明だけの変更なので、取引先名は変わりません。実際のログでもハンドラーは呼び出されましたが、beforeUpdateメソッドによる値の上書きは行われませんでした。


3.before updateとafter updateのApexトリガーを作成する
続いて、取引先の更新時に動くApexトリガーを作成します。トリガー側には実処理を書かず、実行タイミングに応じて先ほどのトリガーハンドラーを呼び出すだけにします。
trigger AccountTrigger on Account (
before update,
after update
) {
if (Trigger.isBefore) {
AccountTriggerHandler.beforeUpdate(
Trigger.new,
Trigger.oldMap
);
} else if (Trigger.isAfter) {
AccountTriggerHandler.afterUpdate(
Trigger.new
);
}
}この構成なら、トリガーは処理の入口、トリガーハンドラーは具体的な処理という役割が分かれます。処理が増えた場合も、トリガー本体を大きくせずハンドラー側で管理できます。


4.before-saveレコードトリガーフローを作成する
1つ目のFlowは、取引先を更新したときに動くbefore-saveフローです。開始条件は「取引先名が変更された」、最適化の対象は「高速項目更新」にします。
| オブジェクト | 取引先 |
|---|---|
| トリガー | レコードが更新された |
| エントリ条件 | 取引先名/変更済み/True |
| 最適化 | 高速項目更新 |


開始要素の次に「割り当て」を追加し、$Record.Descriptionへ01: before-save Flowを設定します。


5.after-saveレコードトリガーフローを作成する
2つ目は、取引先の保存後に動くafter-saveフローです。開始条件はbefore-save Flowと同じですが、最適化の対象は「アクションと関連レコード」にします。
| オブジェクト | 取引先 |
|---|---|
| トリガー | レコードが更新された |
| エントリ条件 | 取引先名/変更済み/True |
| 最適化 | アクションと関連レコード |
開始条件に「取引先名が変更された」を指定するのがポイントです。2回目は説明だけが変わるため、after-save Flowは再度起動せず、同じフローが繰り返されることを防げました。


「レコードを更新」要素でトリガー元の取引先を指定し、説明へ04: after-save Flowを設定します。これが、同じ取引先に対する2回目の更新です。


6.取引先を更新してデバッグログを取得する
2つのFlowを有効化し、Apexトリガーも有効な状態で取引先を開きます。デバッグログの対象ユーザーを設定したあと、取引先名を変更して保存します。
- 検証用の取引先を開く
- 変更前の
説明を確認する 取引先名だけを変更して保存する- 保存後の
説明を確認する - デバッグログを開き、
ORDER_TESTで検索する
取引先名の変更前:


取引先名の変更後:


検証結果:値の上書きとApexの再実行を確認する
before-save Flowとafter-save Flowはそれぞれ1回、before/after update Apexはそれぞれ2回動きました。最終的な説明は、after-save Flowが設定した04: after-save Flowでした。


実際のORDER_TESTのログは、次の順番になりました。
ORDER_TEST | before update #1 | Name changed=true | Description=01: before-save Flow
ORDER_TEST | after update #1 | Description=02: before update Apex
ORDER_TEST | before update #2 | Name changed=false | Description=04: after-save Flow
ORDER_TEST | after update #2 | Description=04: after-save Flow1回目のbefore update処理では、トリガーハンドラーが01: before-save Flowを受け取っていました。ここから、before-save Flowが先に動いたことが分かります。その後、ハンドラーが02: before update Apexへ上書きし、最初のafter update処理でも同じ値を確認できました。
after-save Flowが04: after-save Flowへ更新すると、同じ取引先に対して保存処理がもう一度始まりました。2回目に呼び出されたトリガーハンドラーのログはName changed=false、Description=04: after-save Flowとなり、Apexだけが再実行された流れを確認できました。




| 順番 | 処理 | 確認できた値・結果 |
|---|---|---|
| 1 | before-save Flow | 01: before-save Flowを設定 |
| 2 | 1回目のbefore update Apex | 01を受け取り、02へ上書き |
| 3 | 1回目のafter update Apex | 02: before update Apex |
| 4 | after-save Flow | 04: after-save Flowへ再更新 |
| 5 | 2回目のbefore update Apex | 04: after-save Flow。取引先名は変更されていないため上書きなし |
| 6 | 2回目のafter update Apex | 04: after-save Flow |
after-save Flowで同じレコードを更新するときの注意点
今回の検証では、after-save Flowから同じレコードを更新すると、保存処理がもう一度動くことを確認できました。起動条件が広いままだと、FlowやApexが意図せず繰り返し実行される可能性があります。
- 開始条件を、業務上必要な項目の変更時だけに絞る
- Flowでは
$Recordと$RecordPrior、ApexではTrigger.newとTrigger.oldMapを比較する - 同じレコードの項目更新だけなら、before-save Flowまたはbefore Apexで完結できないか確認する
- 同じオブジェクト上のFlowとApexを横断して、どの処理が再実行されるか確認する
Salesforceの公式アーキテクチャガイドでも、同じレコードの項目更新にはbefore-saveを使い、不要な2回目のDMLと再帰的な保存処理を避ける考え方が示されています。after-saveで同じレコードを更新する必要があるかは、最初に見直したいところです。
再実行によって複数の処理が積み上がると、SOQLやDMLなどのガバナ制限にも影響します。ログから処理の積み上がりを確認する方法は、次の関連記事で詳しく確認しています。


FlowとApexが同じオブジェクトにある場合は設計も確認する
今回のようにFlowとApexを混在させる構成は、あくまで動きを確認するためのものです。実運用では、同じオブジェクトにレコードトリガーフローとApexトリガーを別々の入口として作るのは原則避けたいと考えています。入口が増えるほど、値の上書き、再実行、ガバナ制限への影響を横断して追う必要があるためです。


今回のような混在構成は挙動を確認するためのものです。実運用では、保守性や実行順序の分かりやすさを考え、同じオブジェクトの自動化の入口をFlowまたはApexのどちらかに寄せるのが基本です。
Salesforceの公式アーキテクチャガイドでも、1つのオブジェクトでは自動化の入口を1つにし、同じオブジェクトでFlowとApexトリガーを入口として混在させないよう案内されています。Flowで無理なく保守できる処理はFlowへ、複雑度やデータ量、細かなトランザクション制御からApexが適する処理はApexへ寄せます。Flowを入口にして複雑な処理だけInvocable Apexへ任せる構成も選択肢です。
まとめ
この記事では、before-save Flow、before/after Apexトリガー、after-save Flowから同じ取引先の説明を更新し、値の上書きと再実行を実際に確認しました。
- before Apexは、before-save Flowが設定した
01を受け取り、02へ上書きした - 1回目のafter Apexでは、before Apexが上書きした
02を確認できた - after-save Flowが同じ取引先を更新すると、before/after Apexが2回目も起動した
- 最終的な
説明には、after-save Flowが設定した04が残った - 実運用では1オブジェクトにつき自動化の入口を1つにし、FlowトリガーとApexトリガーの混在は原則避ける
実行順序だけを見ると単純ですが、after-save Flowが同じレコードを更新すると、Apexは同じトランザクション内で再実行されます。今回のように更新対象と開始条件を絞り、2回目にどの処理が動くかまでデバッグログで確認しておくと、値の上書きや意図しない再帰を追いやすくなります。





コメント