diff --git a/CHANGELOG.md b/CHANGELOG.md
index 386d655f1fda..32234dc05d25 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,10 @@ Ref: https://keepachangelog.com/en/1.0.0/
 * (x/staking) Fix a possible bypass of delagator slashing: [GHSA-86h5-xcpx-cfqc](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-86h5-xcpx-cfqc)
 * (store) [#435](https://github.com/crypto-org-chain/cosmos-sdk/pull/435) Fix a nil pointer panic when query historical state where a new store don't exist.
 
+### Features
+
+* (cli) [#21372](https://github.com/cosmos/cosmos-sdk/pull/21372) Added a `bulk-add-genesis-account` genesis command to add many genesis accounts at once.
+
 ## [v0.47.11](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.11) - 2024-04-22
 
 ### Bug Fixes
diff --git a/x/auth/helpers/genaccounts.go b/x/auth/helpers/genaccounts.go
index 00ee73c18e52..7e60b57098ed 100644
--- a/x/auth/helpers/genaccounts.go
+++ b/x/auth/helpers/genaccounts.go
@@ -135,3 +135,147 @@ func AddGenesisAccount(
 	genDoc.AppState = appStateJSON
 	return genutil.ExportGenesisFile(genDoc, genesisFileUrl)
 }
+
+type GenesisAccount struct {
+	// Base
+	Address string    `json:"address"`
+	Coins   sdk.Coins `json:"coins"`
+
+	// Vesting
+	VestingAmt   sdk.Coins `json:"vesting_amt,omitempty"`
+	VestingStart int64     `json:"vesting_start,omitempty"`
+	VestingEnd   int64     `json:"vesting_end,omitempty"`
+
+	// Module
+	ModuleName string `json:"module_name,omitempty"`
+}
+
+// AddGenesisAccounts adds genesis accounts to the genesis state.
+// Where `cdc` is the client codec, `accounts` are the genesis accounts to add,
+// `appendAcct` updates the account if already exists, and `genesisFileURL` is the path/url of the current genesis file.
+func AddGenesisAccounts(
+	cdc codec.Codec,
+	accounts []GenesisAccount,
+	appendAcct bool,
+	genesisFileURL string,
+) error {
+	appState, appGenesis, err := genutiltypes.GenesisStateFromGenFile(genesisFileURL)
+	if err != nil {
+		return fmt.Errorf("failed to unmarshal genesis state: %w", err)
+	}
+
+	authGenState := authtypes.GetGenesisStateFromAppState(cdc, appState)
+	bankGenState := banktypes.GetGenesisStateFromAppState(cdc, appState)
+
+	accs, err := authtypes.UnpackAccounts(authGenState.Accounts)
+	if err != nil {
+		return fmt.Errorf("failed to get accounts from any: %w", err)
+	}
+
+	newSupplyCoinsCache := sdk.NewCoins()
+	balanceCache := make(map[string]banktypes.Balance)
+	for _, acc := range accs {
+		for _, balance := range bankGenState.GetBalances() {
+			if balance.Address == acc.GetAddress().String() {
+				balanceCache[acc.GetAddress().String()] = balance
+			}
+		}
+	}
+
+	for _, acc := range accounts {
+		addr := acc.Address
+		coins := acc.Coins
+
+		accAddr, err := sdk.AccAddressFromBech32(addr)
+		if err != nil {
+			return fmt.Errorf("failed to parse account address %s: %w", addr, err)
+		}
+
+		// create concrete account type based on input parameters
+		var genAccount authtypes.GenesisAccount
+
+		balances := banktypes.Balance{Address: addr, Coins: coins.Sort()}
+		baseAccount := authtypes.NewBaseAccount(accAddr, nil, 0, 0)
+
+		vestingAmt := acc.VestingAmt
+		switch {
+		case !vestingAmt.IsZero():
+			vestingStart := acc.VestingStart
+			vestingEnd := acc.VestingEnd
+
+			baseVestingAccount := authvesting.NewBaseVestingAccount(baseAccount, vestingAmt.Sort(), vestingEnd)
+			if (balances.Coins.IsZero() && !baseVestingAccount.OriginalVesting.IsZero()) ||
+				baseVestingAccount.OriginalVesting.IsAnyGT(balances.Coins) {
+				return errors.New("vesting amount cannot be greater than total amount")
+			}
+
+			switch {
+			case vestingStart != 0 && vestingEnd != 0:
+				genAccount = authvesting.NewContinuousVestingAccountRaw(baseVestingAccount, vestingStart)
+
+			case vestingEnd != 0:
+				genAccount = authvesting.NewDelayedVestingAccountRaw(baseVestingAccount)
+
+			default:
+				return errors.New("invalid vesting parameters; must supply start and end time or end time")
+			}
+		case acc.ModuleName != "":
+			genAccount = authtypes.NewEmptyModuleAccount(acc.ModuleName, authtypes.Burner, authtypes.Minter)
+		default:
+			genAccount = baseAccount
+		}
+
+		if err := genAccount.Validate(); err != nil {
+			return fmt.Errorf("failed to validate new genesis account: %w", err)
+		}
+
+		if _, ok := balanceCache[addr]; ok {
+			if !appendAcct {
+				return fmt.Errorf(" Account %s already exists\nUse `append` flag to append account at existing address", accAddr)
+			}
+
+			for idx, acc := range bankGenState.Balances {
+				if acc.Address != addr {
+					continue
+				}
+
+				updatedCoins := acc.Coins.Add(coins...)
+				bankGenState.Balances[idx] = banktypes.Balance{Address: addr, Coins: updatedCoins.Sort()}
+				break
+			}
+		} else {
+			accs = append(accs, genAccount)
+			bankGenState.Balances = append(bankGenState.Balances, balances)
+		}
+
+		newSupplyCoinsCache = newSupplyCoinsCache.Add(coins...)
+	}
+
+	accs = authtypes.SanitizeGenesisAccounts(accs)
+
+	authGenState.Accounts, err = authtypes.PackAccounts(accs)
+	if err != nil {
+		return fmt.Errorf("failed to convert accounts into any's: %w", err)
+	}
+
+	appState[authtypes.ModuleName], err = cdc.MarshalJSON(&authGenState)
+	if err != nil {
+		return fmt.Errorf("failed to marshal auth genesis state: %w", err)
+	}
+
+	bankGenState.Balances = banktypes.SanitizeGenesisBalances(bankGenState.Balances)
+	bankGenState.Supply = bankGenState.Supply.Add(newSupplyCoinsCache...)
+
+	appState[banktypes.ModuleName], err = cdc.MarshalJSON(bankGenState)
+	if err != nil {
+		return fmt.Errorf("failed to marshal bank genesis state: %w", err)
+	}
+
+	appStateJSON, err := json.Marshal(appState)
+	if err != nil {
+		return fmt.Errorf("failed to marshal application genesis state: %w", err)
+	}
+
+	appGenesis.AppState = appStateJSON
+	return genutil.ExportGenesisFile(appGenesis, genesisFileURL)
+}
diff --git a/x/genutil/client/cli/core_genesis_cmd.go b/x/genutil/client/cli/core_genesis_cmd.go
index 384ddc9c782c..bd625e5a75ef 100644
--- a/x/genutil/client/cli/core_genesis_cmd.go
+++ b/x/genutil/client/cli/core_genesis_cmd.go
@@ -21,7 +21,6 @@ func GenesisCoreCommand(txConfig client.TxConfig, moduleBasics module.BasicManag
 		RunE:                       client.ValidateCmd,
 	}
 	gentxModule := moduleBasics[genutiltypes.ModuleName].(genutil.AppModuleBasic)
-
 	cmd.AddCommand(
 		GenTxCmd(moduleBasics, txConfig,
 			banktypes.GenesisBalancesIterator{}, defaultNodeHome),
@@ -30,6 +29,7 @@ func GenesisCoreCommand(txConfig client.TxConfig, moduleBasics module.BasicManag
 			gentxModule.GenTxValidator),
 		ValidateGenesisCmd(moduleBasics),
 		AddGenesisAccountCmd(defaultNodeHome),
+		AddBulkGenesisAccountCmd(defaultNodeHome),
 	)
 
 	return cmd
diff --git a/x/genutil/client/cli/genaccount.go b/x/genutil/client/cli/genaccount.go
index 043428daf5ab..0f6fdf66bd71 100644
--- a/x/genutil/client/cli/genaccount.go
+++ b/x/genutil/client/cli/genaccount.go
@@ -2,7 +2,9 @@ package cli
 
 import (
 	"bufio"
+	"encoding/json"
 	"fmt"
+	"os"
 
 	"github.com/cosmos/cosmos-sdk/client"
 	"github.com/cosmos/cosmos-sdk/client/flags"
@@ -86,3 +88,68 @@ contain valid denominations. Accounts may optionally be supplied with vesting pa
 
 	return cmd
 }
+
+// AddBulkGenesisAccountCmd returns bulk-add-genesis-account cobra Command.
+// This command is provided as a default, applications are expected to provide their own command if custom genesis accounts are needed.
+func AddBulkGenesisAccountCmd(defaultNodeHome string) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "bulk-add-genesis-account [/file/path.json]",
+		Short: "Bulk add genesis accounts to genesis.json",
+		Example: `bulk-add-genesis-account accounts.json
+where accounts.json is:
+[
+    {
+        "address": "cosmos139f7kncmglres2nf3h4hc4tade85ekfr8sulz5",
+        "coins": [
+            { "denom": "umuon", "amount": "100000000" },
+            { "denom": "stake", "amount": "200000000" }
+        ]
+    },
+    {
+        "address": "cosmos1e0jnq2sun3dzjh8p2xq95kk0expwmd7shwjpfg",
+        "coins": [
+            { "denom": "umuon", "amount": "500000000" }
+        ],
+        "vesting_amt": [
+            { "denom": "umuon", "amount": "400000000" }
+        ],
+        "vesting_start": 1724711478,
+        "vesting_end": 1914013878
+    }
+]
+`,
+		Long: `Add genesis accounts in bulk to genesis.json. The provided account must specify
+the account address and a list of initial coins. The list of initial tokens must
+contain valid denominations. Accounts may optionally be supplied with vesting parameters.
+`,
+		Args: cobra.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			clientCtx := client.GetClientContextFromCmd(cmd)
+			serverCtx := server.GetServerContextFromCmd(cmd)
+			config := serverCtx.Config
+
+			config.SetRoot(clientCtx.HomeDir)
+
+			f, err := os.Open(args[0])
+			if err != nil {
+				return fmt.Errorf("failed to open file: %w", err)
+			}
+			defer f.Close()
+
+			var accounts []auth.GenesisAccount
+			if err := json.NewDecoder(f).Decode(&accounts); err != nil {
+				return fmt.Errorf("failed to decode JSON: %w", err)
+			}
+
+			appendflag, _ := cmd.Flags().GetBool(flagAppendMode)
+
+			return auth.AddGenesisAccounts(clientCtx.Codec, accounts, appendflag, config.GenesisFile())
+		},
+	}
+
+	cmd.Flags().Bool(flagAppendMode, false, "append the coins to an account already in the genesis.json file")
+	cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
+	flags.AddQueryFlagsToCmd(cmd)
+
+	return cmd
+}