diff --git a/go.mod b/go.mod
index c0dce8523cfe1edf9849bc44ba1b5e773035f8e8..9f1107e2480e34124d209f2f4713142516a76245 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ require (
 	github.com/hack-pad/go-indexeddb v0.2.0
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/jwalterweatherman v1.1.0
-	gitlab.com/elixxir/client/v4 v4.6.2-0.20230407211058-ea509f607564
+	gitlab.com/elixxir/client/v4 v4.6.2-0.20230412184157-7049ce3a4ce3
 	gitlab.com/elixxir/crypto v0.0.7-0.20230405173828-f811be53be9a
 	gitlab.com/elixxir/primitives v0.0.3-0.20230214180039-9a25e2d3969c
 	gitlab.com/xx_network/crypto v0.0.5-0.20230214003943-8a09396e95dd
diff --git a/go.sum b/go.sum
index 647b852ed9c9d9976b94d483407c6ee677eed568..2e0304c11d77a6ee053cb676cf79d30b9520ea27 100644
--- a/go.sum
+++ b/go.sum
@@ -391,8 +391,8 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
 github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
 gitlab.com/elixxir/bloomfilter v0.0.0-20230315224936-a4459418f300 h1:oF3Pkf5NBb48KB89Q4sQXKQCIsWp1IVsqKWHWFsfBRc=
 gitlab.com/elixxir/bloomfilter v0.0.0-20230315224936-a4459418f300/go.mod h1:1X8gRIAPDisS3W6Vtr/ymiUmZMJUIwDV1o5DEOo/pzw=
-gitlab.com/elixxir/client/v4 v4.6.2-0.20230407211058-ea509f607564 h1:8dBzmq9bnDqvraQyYiP6LbgqPCfMEnKgl/kyFWId8kc=
-gitlab.com/elixxir/client/v4 v4.6.2-0.20230407211058-ea509f607564/go.mod h1:5ndu2D6dIswTmcY9cPs0/3bBS0F7P2DGCoHz/zU2evk=
+gitlab.com/elixxir/client/v4 v4.6.2-0.20230412184157-7049ce3a4ce3 h1:7jiYtp+vZDIXwhw5v5uYdrLjnQAxgCSwnU8E5M/9fmM=
+gitlab.com/elixxir/client/v4 v4.6.2-0.20230412184157-7049ce3a4ce3/go.mod h1:5ndu2D6dIswTmcY9cPs0/3bBS0F7P2DGCoHz/zU2evk=
 gitlab.com/elixxir/comms v0.0.4-0.20230310205528-f06faa0d2f0b h1:8AVK93UEs/aufoqtFgyMVt9gf0oJ8F4pA60ZvEVvG+s=
 gitlab.com/elixxir/comms v0.0.4-0.20230310205528-f06faa0d2f0b/go.mod h1:z+qW0D9VpY5QKTd7wRlb5SK4kBNqLYsa4DXBcUXue9Q=
 gitlab.com/elixxir/crypto v0.0.7-0.20230405173828-f811be53be9a h1:0LtIGmpl8cn11yBj8vyd7TiTNQPGXAdyASpm6+10uwY=
diff --git a/wasm/channels.go b/wasm/channels.go
index 0e057b539a29457f389293874f9f0ee0a041def0..307fe5cee304ff580ba1726b8036ce81268cc991 100644
--- a/wasm/channels.go
+++ b/wasm/channels.go
@@ -256,7 +256,11 @@ func GetPublicChannelIdentityFromPrivate(_ js.Value, args []js.Value) any {
 //     using [Cmix.GetID].
 //   - args[1] - Bytes of a private identity ([channel.PrivateIdentity]) that is
 //     generated by [GenerateChannelIdentity] (Uint8Array).
-//   - args[2] - A function that initialises and returns a Javascript object
+//   - args[2] - JSON of an array of integers of [channels.ExtensionBuilder]
+//     IDs. The ID can be retrieved from an object with an extension builder
+//     (e.g., [ChannelsFileTransfer.GetExtensionBuilderID]). Leave empty if not
+//     using extension builders. Example: `[2,11,5]` (Uint8Array).
+//   - args[3] - A function that initialises and returns a Javascript object
 //     that matches the [bindings.EventModel] interface. The function must match
 //     the Build function in [bindings.EventModelBuilder].
 //
@@ -264,10 +268,13 @@ func GetPublicChannelIdentityFromPrivate(_ js.Value, args []js.Value) any {
 //   - Javascript representation of the [ChannelsManager] object.
 //   - Throws a TypeError if creating the manager fails.
 func NewChannelsManager(_ js.Value, args []js.Value) any {
+	cmixId := args[0].Int()
 	privateIdentity := utils.CopyBytesToGo(args[1])
-	em := newEventModelBuilder(args[2])
+	extensionBuilderIDsJSON := utils.CopyBytesToGo(args[2])
+	em := newEventModelBuilder(args[3])
 
-	cm, err := bindings.NewChannelsManager(args[0].Int(), privateIdentity, em)
+	cm, err := bindings.NewChannelsManager(
+		cmixId, privateIdentity, extensionBuilderIDsJSON, em)
 	if err != nil {
 		utils.Throw(utils.TypeError, err)
 		return nil
@@ -324,7 +331,11 @@ func LoadChannelsManager(_ js.Value, args []js.Value) any {
 //   - args[1] - Path to Javascript file that starts the worker (string).
 //   - args[2] - Bytes of a private identity ([channel.PrivateIdentity]) that is
 //     generated by [GenerateChannelIdentity] (Uint8Array).
-//   - args[3] - The received message callback, which is called everytime a
+//   - args[3] - JSON of an array of integers of [channels.ExtensionBuilder]
+//     IDs. The ID can be retrieved from an object with an extension builder
+//     (e.g., [ChannelsFileTransfer.GetExtensionBuilderID]). Leave empty if not
+//     using extension builders. Example: `[2,11,5]` (Uint8Array).
+//   - args[4] - The received message callback, which is called everytime a
 //     message is added or changed in the database. It is a function that takes
 //     in the same parameters as [channels.MessageReceivedCallback]. On the
 //     Javascript side, the UUID is returned as an int and the channelID as a
@@ -332,15 +343,15 @@ func LoadChannelsManager(_ js.Value, args []js.Value) any {
 //     the UUID. The channel ID is provided so that the recipient can filter if
 //     they want to the processes the update now or not. An "update" bool is
 //     present which tells you if the row is new or if it is an edited old row.
-//   - args[4] - The deleted message callback, which is called everytime a
+//   - args[5] - The deleted message callback, which is called everytime a
 //     message is deleted from the database. It is a function that takes in the
 //     same parameters as [indexedDb.DeletedMessageCallback]. On the Javascript
 //     side, the message ID is returned as a Uint8Array.
-//   - args[5] - The muted user callback, which is called everytime a user is
+//   - args[6] - The muted user callback, which is called everytime a user is
 //     muted or unmuted. It is a function that takes in the same parameters as
 //     [indexedDb.MutedUserCallback]. On the Javascript side, the channel ID and
 //     user public key are returned as Uint8Array.
-//   - args[6] - ID of [ChannelDbCipher] object in tracker (int). Create this
+//   - args[7] - ID of [ChannelDbCipher] object in tracker (int). Create this
 //     object with [NewChannelsDatabaseCipher] and get its id with
 //     [ChannelDbCipher.GetID].
 //
@@ -352,10 +363,11 @@ func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 	cmixID := args[0].Int()
 	wasmJsPath := args[1].String()
 	privateIdentity := utils.CopyBytesToGo(args[2])
-	messageReceivedCB := args[3]
-	deletedMessageCB := args[4]
-	mutedUserCB := args[5]
-	cipherID := args[6].Int()
+	extensionBuilderIDsJSON := utils.CopyBytesToGo(args[3])
+	messageReceivedCB := args[4]
+	deletedMessageCB := args[5]
+	mutedUserCB := args[6]
+	cipherID := args[7].Int()
 
 	cipher, err := bindings.GetChannelDbCipherTrackerFromID(cipherID)
 	if err != nil {
@@ -363,7 +375,8 @@ func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 	}
 
 	return newChannelsManagerWithIndexedDb(cmixID, wasmJsPath, privateIdentity,
-		messageReceivedCB, deletedMessageCB, mutedUserCB, cipher)
+		extensionBuilderIDsJSON, messageReceivedCB, deletedMessageCB,
+		mutedUserCB, cipher)
 }
 
 // NewChannelsManagerWithIndexedDbUnsafe creates a new [ChannelsManager] from a
@@ -384,7 +397,11 @@ func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 //   - args[1] - Path to Javascript file that starts the worker (string).
 //   - args[2] - Bytes of a private identity ([channel.PrivateIdentity]) that is
 //     generated by [GenerateChannelIdentity] (Uint8Array).
-//   - args[3] - The received message callback, which is called everytime a
+//   - args[3] - JSON of an array of integers of [channels.ExtensionBuilder]
+//     IDs. The ID can be retrieved from an object with an extension builder
+//     (e.g., [ChannelsFileTransfer.GetExtensionBuilderID]). Leave empty if not
+//     using extension builders. Example: `[2,11,5]` (Uint8Array).
+//   - args[4] - The received message callback, which is called everytime a
 //     message is added or changed in the database. It is a function that takes
 //     in the same parameters as [indexedDb.MessageReceivedCallback]. On the
 //     Javascript side, the UUID is returned as an int and the channelID as a
@@ -392,11 +409,11 @@ func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
 //     the UUID. The channel ID is provided so that the recipient can filter if
 //     they want to the processes the update now or not. An "update" bool is
 //     present which tells you if the row is new or if it is an edited old row.
-//   - args[4] - The deleted message callback, which is called everytime a
+//   - args[5] - The deleted message callback, which is called everytime a
 //     message is deleted from the database. It is a function that takes in the
 //     same parameters as [indexedDb.DeletedMessageCallback]. On the Javascript
 //     side, the message ID is returned as a Uint8Array.
-//   - args[5] - The muted user callback, which is called everytime a user is
+//   - args[6] - The muted user callback, which is called everytime a user is
 //     muted or unmuted. It is a function that takes in the same parameters as
 //     [indexedDb.MutedUserCallback]. On the Javascript side, the channel ID and
 //     user public key are returned as Uint8Array.
@@ -410,17 +427,19 @@ func NewChannelsManagerWithIndexedDbUnsafe(_ js.Value, args []js.Value) any {
 	cmixID := args[0].Int()
 	wasmJsPath := args[1].String()
 	privateIdentity := utils.CopyBytesToGo(args[2])
-	messageReceivedCB := args[3]
-	deletedMessageCB := args[4]
-	mutedUserCB := args[5]
+	extensionBuilderIDsJSON := utils.CopyBytesToGo(args[3])
+	messageReceivedCB := args[4]
+	deletedMessageCB := args[5]
+	mutedUserCB := args[6]
 
 	return newChannelsManagerWithIndexedDb(cmixID, wasmJsPath, privateIdentity,
-		messageReceivedCB, deletedMessageCB, mutedUserCB, nil)
+		extensionBuilderIDsJSON, messageReceivedCB, deletedMessageCB,
+		mutedUserCB, nil)
 }
 
 func newChannelsManagerWithIndexedDb(cmixID int, wasmJsPath string,
-	privateIdentity []byte, messageReceivedCB, deletedMessageCB, mutedUserCB js.Value,
-	cipher *bindings.ChannelDbCipher) any {
+	privateIdentity, extensionBuilderIDsJSON []byte, messageReceivedCB,
+	deletedMessageCB, mutedUserCB js.Value, cipher *bindings.ChannelDbCipher) any {
 
 	messageReceived := func(uuid uint64, channelID *id.ID, update bool) {
 		messageReceivedCB.Invoke(uuid, utils.CopyBytesToJS(channelID.Marshal()), update)
@@ -440,7 +459,7 @@ func newChannelsManagerWithIndexedDb(cmixID int, wasmJsPath string,
 
 	promiseFn := func(resolve, reject func(args ...any) js.Value) {
 		cm, err := bindings.NewChannelsManagerGoEventModel(
-			cmixID, privateIdentity, model, nil)
+			cmixID, privateIdentity, extensionBuilderIDsJSON, model)
 		if err != nil {
 			reject(utils.JsTrace(err))
 		} else {
diff --git a/wasm/channelsFileTransfer.go b/wasm/channelsFileTransfer.go
index c14c3006c0a81f7d4053cd833803e20ab626e64e..5155e4ad4668bef0f3ad52f52efec748e7dbb0ff 100644
--- a/wasm/channelsFileTransfer.go
+++ b/wasm/channelsFileTransfer.go
@@ -26,10 +26,11 @@ type ChannelsFileTransfer struct {
 func newChannelsFileTransferJS(api *bindings.ChannelsFileTransfer) map[string]any {
 	cft := ChannelsFileTransfer{api}
 	channelsFileTransferMap := map[string]any{
-		"MaxFileNameLen": js.FuncOf(cft.MaxFileNameLen),
-		"MaxFileTypeLen": js.FuncOf(cft.MaxFileTypeLen),
-		"MaxFileSize":    js.FuncOf(cft.MaxFileSize),
-		"MaxPreviewSize": js.FuncOf(cft.MaxPreviewSize),
+		"GetExtensionBuilderID": js.FuncOf(cft.GetExtensionBuilderID),
+		"MaxFileNameLen":        js.FuncOf(cft.MaxFileNameLen),
+		"MaxFileTypeLen":        js.FuncOf(cft.MaxFileTypeLen),
+		"MaxFileSize":           js.FuncOf(cft.MaxFileSize),
+		"MaxPreviewSize":        js.FuncOf(cft.MaxPreviewSize),
 
 		// Uploading/Sending
 		"Upload":                       js.FuncOf(cft.Upload),
@@ -75,6 +76,16 @@ func InitChannelsFileTransfer(_ js.Value, args []js.Value) any {
 	return utils.CreatePromise(promiseFn)
 }
 
+// GetExtensionBuilderID returns the ID of the extension builder in the tracker.
+// Pass this ID into the channel manager creator to use file transfer manager in
+// conjunction with channels.
+//
+// Returns:
+//   - Extension builder ID (int).
+func (cft *ChannelsFileTransfer) GetExtensionBuilderID(js.Value, []js.Value) any {
+	return cft.api.GetExtensionBuilderID()
+}
+
 // MaxFileNameLen returns the max number of bytes allowed for a file name.
 //
 // Returns: