diff --git a/src/libs/actions/IOU/Split.ts b/src/libs/actions/IOU/Split.ts
index ba2dad43626c..90ac609fe906 100644
--- a/src/libs/actions/IOU/Split.ts
+++ b/src/libs/actions/IOU/Split.ts
@@ -66,7 +66,7 @@ import type RecentlyUsedTags from '@src/types/onyx/RecentlyUsedTags';
 import type {OnyxData} from '@src/types/onyx/Request';
 import type {Receipt, ReceiptSource, SplitShares, TransactionChanges, WaypointCollection} from '@src/types/onyx/Transaction';
 import {isEmptyObject} from '@src/types/utils/EmptyObject';
-import {getAllPersonalDetails, getAllReports, getAllTransactionDrafts, getAllTransactions, getMoneyRequestPolicyTags, getPolicyTagsData, getUserAccountID} from './index';
+import {buildParticipantsPolicyTags, getAllPersonalDetails, getAllReports, getAllTransactionDrafts, getAllTransactions, getMoneyRequestPolicyTags, getUserAccountID} from './index';
 import type {StartSplitBilActionParams} from './index';
 import {
     buildMinimalTransactionForFormula,
@@ -156,6 +156,7 @@ type CreateSplitsAndOnyxDataParams = {
     policyRecentlyUsedCurrencies: string[];
     betas: OnyxEntry<OnyxTypes.Beta[]>;
     personalDetails: OnyxEntry<OnyxTypes.PersonalDetailsList>;
+    participantsPolicyTags: Record<string, OnyxTypes.PolicyTagLists>;
 };
 
 type SplitBillActionsParams = {
@@ -250,6 +251,8 @@ function splitBill({
         policyRecentlyUsedCurrencies,
         betas,
         personalDetails,
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        participantsPolicyTags: buildParticipantsPolicyTags(participants),
     });
 
     const parameters: SplitBillParams = {
@@ -344,6 +347,8 @@ function splitBillAndOpenReport({
         policyRecentlyUsedCurrencies,
         betas,
         personalDetails,
+        // eslint-disable-next-line @typescript-eslint/no-deprecated
+        participantsPolicyTags: buildParticipantsPolicyTags(participants),
     });
 
     const parameters: SplitBillParams = {
@@ -1285,6 +1290,7 @@ function createSplitsAndOnyxData({
     policyRecentlyUsedCurrencies,
     betas,
     personalDetails,
+    participantsPolicyTags,
 }: CreateSplitsAndOnyxDataParams): SplitsAndOnyxData {
     const currentUserEmailForIOUSplit = addSMSDomainIfPhoneNumber(currentUserLogin);
     const participantAccountIDs = participants.map((participant) => Number(participant.accountID));
@@ -1510,6 +1516,7 @@ function createSplitsAndOnyxData({
         // participant.login is undefined when the request is initiated from a group DM with an unknown user, so we need to add a default
         const email = isOwnPolicyExpenseChat || isPolicyExpenseChat ? '' : addSMSDomainIfPhoneNumber(participant.login ?? '').toLowerCase();
         const accountID = isOwnPolicyExpenseChat || isPolicyExpenseChat ? 0 : Number(participant.accountID);
+        const participantPolicyTags = participant.policyID ? participantsPolicyTags?.[participant.policyID] : {};
 
         if (isPendingDistanceSplitBill) {
             const individualSplit = {
@@ -1680,8 +1687,7 @@ function createSplitsAndOnyxData({
         // Add tag to optimistic policy recently used tags when a participant is a workspace
         const optimisticPolicyRecentlyUsedTags = isPolicyExpenseChat
             ? buildOptimisticPolicyRecentlyUsedTags({
-                  // TODO: Replace getPolicyTagsData (https://github.com/Expensify/App/issues/72721) and getPolicyRecentlyUsedTagsData (https://github.com/Expensify/App/issues/71491) with useOnyx hook
-                  policyTags: getPolicyTagsData(participant.policyID),
+                  policyTags: participantPolicyTags,
                   policyRecentlyUsedTags,
                   transactionTags: tag,
               })
@@ -1893,6 +1899,8 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest
             policyRecentlyUsedCurrencies,
             betas,
             personalDetails,
+            // eslint-disable-next-line @typescript-eslint/no-deprecated
+            participantsPolicyTags: buildParticipantsPolicyTags(participants),
         });
         onyxData = splitOnyxData;
 
@@ -2091,6 +2099,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest
 export {
     completeSplitBill,
     createDistanceRequest,
+    createSplitsAndOnyxData,
     splitBill,
     splitBillAndOpenReport,
     startSplitBill,
diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts
index a925e79fd4a2..2f7e704f5763 100644
--- a/src/libs/actions/IOU/index.ts
+++ b/src/libs/actions/IOU/index.ts
@@ -260,6 +260,23 @@ function getPolicyTags(): OnyxCollection<OnyxTypes.PolicyTagLists> {
     return allPolicyTags;
 }
 
+/**
+ * @deprecated This function uses Onyx.connect and should be replaced with useOnyx for reactive data access.
+ * TODO: remove `buildParticipantsPolicyTags` from this file (https://github.com/Expensify/App/issues/72721)
+ * All usages of this function should be replaced with params passed to the functions or useOnyx hook in React components.
+ */
+function buildParticipantsPolicyTags(participants: Participant[]): Record<string, OnyxTypes.PolicyTagLists> {
+    return participants.reduce<Record<string, OnyxTypes.PolicyTagLists>>((acc, participant) => {
+        if (participant.policyID) {
+            const tags = allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${participant.policyID}`];
+            if (tags) {
+                acc[participant.policyID] = tags;
+            }
+        }
+        return acc;
+    }, {});
+}
+
 /**
  * @deprecated This function uses Onyx.connect and should be replaced with useOnyx for reactive data access.
  * TODO: remove `getPolicyTagsData` from this file (https://github.com/Expensify/App/issues/72721)
@@ -876,6 +893,8 @@ export {
     getUserAccountID,
     getCurrentUserPersonalDetails,
     getRecentAttendees,
+    // eslint-disable-next-line @typescript-eslint/no-deprecated
+    buildParticipantsPolicyTags,
     // TODO: Replace getPolicyTagsData (https://github.com/Expensify/App/issues/72721) and getPolicyRecentlyUsedTagsData (https://github.com/Expensify/App/issues/71491) with useOnyx hook
     getPolicyTagsData,
     getPolicyTags,
diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts
index 269a743040f8..e53b2a63a8d5 100644
--- a/tests/actions/IOUTest.ts
+++ b/tests/actions/IOUTest.ts
@@ -24,7 +24,7 @@ import {putOnHold} from '@libs/actions/IOU/Hold';
 import {calculateDiffAmount} from '@libs/actions/IOU/MoneyRequestBuilder';
 import {handleNavigateAfterExpenseCreate} from '@libs/actions/IOU/NavigationHelpers';
 import {shouldOptimisticallyUpdateSearch} from '@libs/actions/IOU/SearchUpdate';
-import {completeSplitBill, splitBill, startSplitBill} from '@libs/actions/IOU/Split';
+import {completeSplitBill, createSplitsAndOnyxData, splitBill, startSplitBill} from '@libs/actions/IOU/Split';
 import {updateSplitTransactionsFromSplitExpensesFlow} from '@libs/actions/IOU/SplitTransactionUpdate';
 import {requestMoney, trackExpense} from '@libs/actions/IOU/TrackExpense';
 import {removeMoneyRequestOdometerImage, setMoneyRequestOdometerImage} from '@libs/actions/OdometerTransactionUtils';
@@ -58,6 +58,7 @@ import type {Participant} from '@src/types/onyx/Report';
 import type ReportAction from '@src/types/onyx/ReportAction';
 import type {ReportActions} from '@src/types/onyx/ReportAction';
 import type Transaction from '@src/types/onyx/Transaction';
+import type {SplitShares} from '@src/types/onyx/Transaction';
 import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet';
 import {isEmptyObject} from '@src/types/utils/EmptyObject';
 import SafeString from '@src/utils/SafeString';
@@ -6840,4 +6841,280 @@ describe('actions/IOU', () => {
             expect(updatedTransaction?.comment?.odometerEndImage).toBeUndefined();
         });
     });
+
+    describe('createSplitsAndOnyxData', () => {
+        const mockPersonalDetails: PersonalDetailsList = {
+            [RORY_ACCOUNT_ID]: {accountID: RORY_ACCOUNT_ID, login: RORY_EMAIL, displayName: 'Rory'},
+            [CARLOS_ACCOUNT_ID]: {accountID: CARLOS_ACCOUNT_ID, login: CARLOS_EMAIL, displayName: 'Carlos'},
+            [JULES_ACCOUNT_ID]: {accountID: JULES_ACCOUNT_ID, login: JULES_EMAIL, displayName: 'Jules'},
+            [VIT_ACCOUNT_ID]: {accountID: VIT_ACCOUNT_ID, login: VIT_EMAIL, displayName: 'Vit'},
+        };
+
+        const baseTransactionParams = {
+            amount: 400,
+            currency: CONST.CURRENCY.USD,
+            created: '2024-01-01',
+            merchant: 'Test Merchant',
+            comment: 'Test split',
+            tag: '',
+            category: '',
+            taxCode: '',
+            taxAmount: 0,
+            splitShares: {} as SplitShares,
+        };
+
+        const buildParams = (
+            overrides: {
+                participants?: IOUParticipant[];
+                existingSplitChatReportID?: string;
+                transactionParamOverrides?: Partial<typeof baseTransactionParams>;
+                participantsPolicyTags?: Record<string, PolicyTagLists>;
+            } = {},
+        ) => ({
+            participants: overrides.participants ?? [{accountID: CARLOS_ACCOUNT_ID, login: CARLOS_EMAIL}],
+            currentUserLogin: RORY_EMAIL,
+            currentUserAccountID: RORY_ACCOUNT_ID,
+            existingSplitChatReportID: overrides.existingSplitChatReportID,
+            transactionParams: {
+                ...baseTransactionParams,
+                ...overrides.transactionParamOverrides,
+            },
+            policyRecentlyUsedCategories: undefined,
+            policyRecentlyUsedTags: undefined,
+            isASAPSubmitBetaEnabled: false,
+            transactionViolations: {},
+            quickAction: undefined,
+            policyRecentlyUsedCurrencies: [],
+            betas: [CONST.BETAS.ALL],
+            personalDetails: mockPersonalDetails,
+            participantsPolicyTags: overrides.participantsPolicyTags ?? {},
+        });
+
+        it('returns valid splitData with chatReportID, transactionID, and reportActionID', () => {
+            // Given a basic 1:1 split between the current user and one participant
+
+            // When creating splits and Onyx data
+            const result = createSplitsAndOnyxData(buildParams());
+
+            // Then splitData should contain all required identifiers
+            expect(result.splitData.chatReportID).toBeTruthy();
+            expect(result.splitData.transactionID).toBeTruthy();
+            expect(result.splitData.reportActionID).toBeTruthy();
+        });
+
+        it('includes createdReportActionID in splitData for a new chat', () => {
+            // Given no existing split chat report
+
+            // When creating splits and Onyx data
+            const result = createSplitsAndOnyxData(buildParams());
+
+            // Then splitData should include a createdReportActionID for the new chat
+            expect(result.splitData.createdReportActionID).toBeTruthy();
+        });
+
+        it('omits createdReportActionID from splitData when using an existing chat', async () => {
+            // Given an existing chat report already in Onyx
+            const existingReportID = rand64();
+            await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${existingReportID}`, {
+                reportID: existingReportID,
+                type: CONST.REPORT.TYPE.CHAT,
+                participants: {[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT},
+            });
+            await waitForBatchedUpdates();
+
+            // When creating splits referencing that existing chat
+            const result = createSplitsAndOnyxData(buildParams({existingSplitChatReportID: existingReportID}));
+
+            // Then splitData should not include a createdReportActionID
+            expect(result.splitData.createdReportActionID).toBeUndefined();
+        });
+
+        it('splits amount equally among all participants when no splitShares are provided', () => {
+            // Given a $400 expense split between the current user and 3 other participants
+            const amount = 400;
+
+            // When creating splits without custom splitShares
+            const result = createSplitsAndOnyxData(
+                buildParams({
+                    participants: [
+                        {accountID: CARLOS_ACCOUNT_ID, login: CARLOS_EMAIL},
+                        {accountID: JULES_ACCOUNT_ID, login: JULES_EMAIL},
+                        {accountID: VIT_ACCOUNT_ID, login: VIT_EMAIL},
+                    ],
+                    transactionParamOverrides: {amount},
+                }),
+            );
+
+            // Then each of the 4 splits (current user + 3 others) should be $100
+            expect(result.splits).toHaveLength(4);
+            for (const split of result.splits) {
+                expect(split.amount).toBe(amount / 4);
+            }
+        });
+
+        it('respects custom splitShares amounts when provided', () => {
+            // Given a $200 expense with custom split: current user pays $150, Carlos pays $50
+            const splitShares: SplitShares = {
+                [RORY_ACCOUNT_ID]: {amount: 150},
+                [CARLOS_ACCOUNT_ID]: {amount: 50},
+            };
+
+            // When creating splits with those custom splitShares
+            const result = createSplitsAndOnyxData(
+                buildParams({
+                    transactionParamOverrides: {amount: 200, splitShares},
+                }),
+            );
+
+            // Then each participant's split should reflect the custom amounts
+            const currentUserSplit = result.splits.find((s) => s.accountID === RORY_ACCOUNT_ID);
+            const carlosSplit = result.splits.find((s) => s.accountID === CARLOS_ACCOUNT_ID);
+
+            expect(currentUserSplit?.amount).toBe(150);
+            expect(carlosSplit?.amount).toBe(50);
+        });
+
+        it('uses SET method for the split chat report in optimisticData when creating a new chat', () => {
+            // Given no existing split chat report
+
+            // When creating splits and Onyx data
+            const result = createSplitsAndOnyxData(buildParams());
+
+            // Then the chat report update should use SET to write the new report atomically
+            const splitChatReportUpdate = result.onyxData.optimisticData?.find(
+                (update) =>
+                    update.key.startsWith(ONYXKEYS.COLLECTION.REPORT) &&
+                    !update.key.includes(ONYXKEYS.COLLECTION.REPORT_ACTIONS) &&
+                    !update.key.includes(ONYXKEYS.COLLECTION.REPORT_METADATA),
+            );
+
+            expect(splitChatReportUpdate?.onyxMethod).toBe(Onyx.METHOD.SET);
+        });
+
+        it('uses MERGE method for the split chat report in optimisticData when reusing an existing chat', async () => {
+            // Given an existing chat report already in Onyx
+            const existingReportID = rand64();
+            await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${existingReportID}`, {
+                reportID: existingReportID,
+                type: CONST.REPORT.TYPE.CHAT,
+                participants: {[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT},
+            });
+            await waitForBatchedUpdates();
+
+            // When creating splits referencing that existing chat
+            const result = createSplitsAndOnyxData(buildParams({existingSplitChatReportID: existingReportID}));
+
+            // Then the chat report update should use MERGE to preserve existing fields
+            const splitChatReportUpdate = result.onyxData.optimisticData?.find((update) => update.key === `${ONYXKEYS.COLLECTION.REPORT}${existingReportID}`);
+
+            expect(splitChatReportUpdate?.onyxMethod).toBe(Onyx.METHOD.MERGE);
+        });
+
+        it('adds isOptimisticReport:true to REPORT_METADATA in optimisticData for a new chat', () => {
+            // Given no existing split chat report
+
+            // When creating splits and Onyx data
+            const result = createSplitsAndOnyxData(buildParams());
+
+            // Then optimisticData should flag the new report as optimistic
+            const reportMetaUpdate = result.onyxData.optimisticData?.find((update) => update.key.startsWith(ONYXKEYS.COLLECTION.REPORT_METADATA));
+
+            expect(reportMetaUpdate?.value).toMatchObject({isOptimisticReport: true});
+        });
+
+        it('does not include REPORT_METADATA isOptimisticReport in optimisticData for an existing chat', async () => {
+            // Given an existing chat report already in Onyx
+            const existingReportID = rand64();
+            await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${existingReportID}`, {
+                reportID: existingReportID,
+                type: CONST.REPORT.TYPE.CHAT,
+                participants: {[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT},
+            });
+            await waitForBatchedUpdates();
+
+            // When creating splits referencing that existing chat
+            const result = createSplitsAndOnyxData(buildParams({existingSplitChatReportID: existingReportID}));
+
+            // Then no REPORT_METADATA entry should be written for the existing report
+            const reportMetaUpdate = result.onyxData.optimisticData?.find((update) => update.key === `${ONYXKEYS.COLLECTION.REPORT_METADATA}${existingReportID}`);
+
+            expect(reportMetaUpdate).toBeUndefined();
+        });
+
+        it('clears pendingAction and pendingFields on the split transaction in successData', () => {
+            // Given a basic split
+
+            // When creating splits and Onyx data
+            const result = createSplitsAndOnyxData(buildParams());
+            const {transactionID} = result.splitData;
+
+            // Then successData should clear pending state on the split transaction
+            const txSuccessUpdate = result.onyxData.successData?.find((update) => update.key === `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`);
+
+            expect(txSuccessUpdate?.value).toMatchObject({pendingAction: null, pendingFields: null});
+        });
+
+        it('includes errors on the split transaction in failureData', () => {
+            // Given a basic split
+
+            // When creating splits and Onyx data
+            const result = createSplitsAndOnyxData(buildParams());
+            const {transactionID} = result.splitData;
+
+            // Then failureData should include an errors entry on the split transaction for user-visible feedback
+            const txFailureUpdate = result.onyxData.failureData?.find((update) => update.key === `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`);
+
+            expect(txFailureUpdate?.value).toHaveProperty('errors');
+        });
+
+        it('sets policy recently used tags in optimisticData for a policy expense chat participant with a tag', async () => {
+            // Given a workspace expense chat with a known tag list
+            const policyID = 'test_policy_999';
+            const tagListName = 'Department';
+            const tagName = 'Engineering';
+
+            const existingExpenseChatID = rand64();
+            await Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, {
+                [`${ONYXKEYS.COLLECTION.REPORT}${existingExpenseChatID}`]: {
+                    reportID: existingExpenseChatID,
+                    type: CONST.REPORT.TYPE.CHAT,
+                    chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT,
+                    policyID,
+                    isOwnPolicyExpenseChat: true,
+                    participants: {[RORY_ACCOUNT_ID]: RORY_PARTICIPANT},
+                },
+            } as OnyxMergeCollectionInput<typeof ONYXKEYS.COLLECTION.REPORT>);
+            await waitForBatchedUpdates();
+
+            const policyTagsList = {
+                [tagListName]: {
+                    name: tagListName,
+                    tags: {[tagName]: {name: tagName, enabled: true}},
+                },
+            };
+
+            // When splitting an expense with a tag inside that workspace chat
+            const result = createSplitsAndOnyxData(
+                buildParams({
+                    existingSplitChatReportID: existingExpenseChatID,
+                    participants: [
+                        {
+                            accountID: CARLOS_ACCOUNT_ID,
+                            login: CARLOS_EMAIL,
+                            isPolicyExpenseChat: true,
+                            isOwnPolicyExpenseChat: true,
+                            policyID,
+                        },
+                    ],
+                    transactionParamOverrides: {tag: tagName},
+                    participantsPolicyTags: {[policyID]: policyTagsList} as unknown as Record<string, PolicyTagLists>,
+                }),
+            );
+
+            // Then optimisticData should update POLICY_RECENTLY_USED_TAGS with the used tag
+            const recentlyUsedTagsUpdate = result.onyxData.optimisticData?.find((update) => update.key === `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`);
+
+            expect(recentlyUsedTagsUpdate?.value).toMatchObject({[tagListName]: [tagName]});
+        });
+    });
 });
