diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumListViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumListViewModel.kt
index 6fbb5d74cea942fb5c591fe3589aadb4246419d1..5efeec62135b6c776247089183b0f3cc15217fdf 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumListViewModel.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumListViewModel.kt
@@ -72,7 +72,7 @@ class ForumListViewModel
         groupCount = forumManager.getGroupCount(txn, id),
     )
 
-    fun createForum(name: String) = runOnDbThread {
+    override fun createGroup(name: String) = runOnDbThread {
         forumManager.addForum(name)
     }
 
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumScreen.kt
index 0305332497297e30fefec9ee3d143b11119f9c74..db4b9ebe2af099d6d9e068eaa8bc59e7366b8144 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumScreen.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumScreen.kt
@@ -28,15 +28,5 @@ fun ForumScreen(
 ) = GroupScreen(
     strings = ForumStrings,
     viewModel = viewModel,
-    addGroupDialog = { visible ->
-        AddForumDialog(
-            visible = visible.value,
-            onCreate = { name ->
-                viewModel.createForum(name)
-                visible.value = false
-            },
-            onCancelButtonClicked = { visible.value = false }
-        )
-    },
     conversationScreen = { GroupConversationScreen(viewModel.threadViewModel) }
 )
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumStrings.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumStrings.kt
index ceee63746d7066c186866c2a34601fe7e253c45e..aa8f947f0e7a6473d8672e25b691782316774672 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumStrings.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/ForumStrings.kt
@@ -18,17 +18,18 @@
 
 package org.briarproject.briar.desktop.forums
 
+import org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH
 import org.briarproject.briar.desktop.group.GroupStrings
-import org.briarproject.briar.desktop.utils.InternationalizationUtils
 import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nF
 import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nP
-import org.briarproject.briar.desktop.utils.TimeUtils
 
 object ForumStrings : GroupStrings(
     listTitle = i18n("forum.search.title"),
     listDescription = i18n("access.forums.list"),
-    addButtonDescription = i18n("forum.add.title"),
+    addGroupTitle = i18n("forum.add.title"),
+    addGroupHint = i18n("forum.add.hint"),
+    addGroupButton = i18n("forum.add.button"),
     noGroupsYet = i18n("forum.empty_state.text"),
     noGroupSelectedTitle = i18n("forum.none_selected.title"),
     noGroupSelectedText = i18n("forum.none_selected.hint"),
@@ -42,4 +43,5 @@ object ForumStrings : GroupStrings(
     lastMessage = { timestamp ->
         i18nF("access.forums.last_post_timestamp", timestamp)
     },
+    groupNameMaxLength = MAX_FORUM_NAME_LENGTH,
 )
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/AddForumDialog.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/AddGroupDialog.kt
similarity index 83%
rename from briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/AddForumDialog.kt
rename to briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/AddGroupDialog.kt
index bd5e742b1ed8a0c1e42fc29b7569b9c37cbb8296..a33754503b52e1c2942b2978182aeca08657c8c2 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/forums/AddForumDialog.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/AddGroupDialog.kt
@@ -1,6 +1,6 @@
 /*
  * Briar Desktop
- * Copyright (C) 2021-2022 The Briar Project
+ * Copyright (C) 2021-2023 The Briar Project
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -16,7 +16,7 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-package org.briarproject.briar.desktop.forums
+package org.briarproject.briar.desktop.group
 
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Row
@@ -49,7 +49,8 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.Dialog
 import androidx.compose.ui.window.WindowPosition
 import androidx.compose.ui.window.rememberDialogState
-import org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH
+import org.briarproject.bramble.util.StringUtils.utf8IsTooLong
+import org.briarproject.briar.desktop.forums.ForumStrings
 import org.briarproject.briar.desktop.utils.AccessibilityUtils.description
 import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 import org.briarproject.briar.desktop.utils.PreviewUtils.preview
@@ -57,11 +58,12 @@ import org.briarproject.briar.desktop.utils.UiUtils.DensityDimension
 
 fun main() = preview {
     val visible = mutableStateOf(true)
-    AddForumDialog(visible.value, {}, { visible.value = false })
+    AddGroupDialog(ForumStrings, visible.value, {}, { visible.value = false })
 }
 
 @Composable
-fun AddForumDialog(
+fun AddGroupDialog(
+    strings: GroupStrings,
     visible: Boolean,
     onCreate: (String) -> Unit,
     onCancelButtonClicked: () -> Unit,
@@ -69,7 +71,7 @@ fun AddForumDialog(
     if (!visible) return
     val density = LocalDensity.current
     Dialog(
-        title = i18n("forum.add.title"),
+        title = strings.addGroupTitle,
         onCloseRequest = onCancelButtonClicked,
         state = rememberDialogState(
             position = WindowPosition(Alignment.Center),
@@ -82,7 +84,7 @@ fun AddForumDialog(
             val name = rememberSaveable { mutableStateOf("") }
             val onNameChanged = { changedName: String ->
                 // not checking for blank here, so user can still remove all characters
-                if (changedName.length <= MAX_FORUM_NAME_LENGTH) name.value = changedName
+                if (!utf8IsTooLong(changedName, strings.groupNameMaxLength)) name.value = changedName
             }
             Surface {
                 Scaffold(
@@ -93,19 +95,19 @@ fun AddForumDialog(
                     topBar = {
                         Box(Modifier.fillMaxWidth()) {
                             Text(
-                                text = i18n("forum.add.title"),
+                                text = strings.addGroupTitle,
                                 style = MaterialTheme.typography.h6,
                                 modifier = Modifier.padding(bottom = 12.dp)
                             )
                         }
                     },
                     content = {
-                        AddForumContent(name.value, onNameChanged, onCreate)
+                        AddGroupContent(name.value, onNameChanged, strings.addGroupHint, onCreate)
                     },
                     bottomBar = {
                         OkCancelBottomBar(
-                            okButtonLabel = i18n("forum.add.button"),
-                            okButtonEnabled = isValidForumName(name.value),
+                            okButtonLabel = strings.addGroupButton,
+                            okButtonEnabled = isValidGroupName(name.value, strings.groupNameMaxLength),
                             onOkButtonClicked = {
                                 onCreate(name.value)
                                 onNameChanged("")
@@ -119,17 +121,16 @@ fun AddForumDialog(
     }
 }
 
-private fun isValidForumName(name: String): Boolean {
-    return name.isNotBlank() && name.length <= MAX_FORUM_NAME_LENGTH
-}
+private fun isValidGroupName(name: String, maxLength: Int) =
+    name.isNotBlank() && !utf8IsTooLong(name, maxLength)
 
 @Composable
-fun AddForumContent(name: String, onNameChanged: (String) -> Unit, onCreate: (String) -> Unit) {
+fun AddGroupContent(name: String, onNameChanged: (String) -> Unit, description: String, onCreate: (String) -> Unit) {
     val focusRequester = remember { FocusRequester() }
     OutlinedTextField(
         value = name,
         onValueChange = onNameChanged,
-        label = { Text(i18n("forum.add.hint")) },
+        label = { Text(description) },
         keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
         singleLine = true,
         onEnter = {
@@ -139,7 +140,7 @@ fun AddForumContent(name: String, onNameChanged: (String) -> Unit, onCreate: (St
         modifier = Modifier
             .fillMaxWidth()
             .focusRequester(focusRequester)
-            .description(i18n("forum.add.hint")),
+            .description(description),
     )
     LaunchedEffect(Unit) {
         focusRequester.requestFocus()
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupList.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupList.kt
index 30ab73ee5a83692acab86095c6da74062c3663d9..bd766693819caae16eb7d6bba671e69d1b530a56 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupList.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupList.kt
@@ -120,7 +120,7 @@ fun GroupList(
             placeholder = strings.listTitle,
             icon = Icons.Filled.AddComment,
             searchValue = filterBy,
-            addButtonDescription = strings.addButtonDescription,
+            addButtonDescription = strings.addGroupTitle,
             onValueChange = onFilterSet,
             onAddButtonClicked = onAddButtonClicked,
         )
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupListViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupListViewModel.kt
index 225da54ce6044c4a68d8f400e5843f4579a1447a..4c01f085ab222e11040637fae0c2a7a797bdab90 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupListViewModel.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupListViewModel.kt
@@ -52,7 +52,7 @@ abstract class GroupListViewModel<T : GroupItem>(
 
     protected abstract val clientId: ClientId
 
-    protected abstract val _groupList: MutableList<T> //todo: check internal
+    protected abstract val _groupList: MutableList<T> // todo: check internal
     val list = derivedStateOf {
         val filter = _filterBy.value
         _groupList.filter { item ->
@@ -123,6 +123,8 @@ abstract class GroupListViewModel<T : GroupItem>(
         }
     }
 
+    abstract fun createGroup(name: String)
+
     protected abstract fun addOwnMessage(header: PostHeader)
 
     fun selectGroup(groupItem: GroupItem) {
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupScreen.kt
index 1eb9101a70a20399a3bb7cfb82b4735c3e2c2fb8..3c1e7bfd6b21c79598848b250e4efc7cbb53cab2 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupScreen.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupScreen.kt
@@ -25,9 +25,10 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.AddComment
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import org.briarproject.briar.desktop.conversation.Explainer
@@ -39,14 +40,21 @@ import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 fun <T : GroupItem> GroupScreen(
     strings: GroupStrings,
     viewModel: GroupListViewModel<T>,
-    addGroupDialog: @Composable (MutableState<Boolean>) -> Unit,
     conversationScreen: @Composable () -> Unit,
 ) {
-    val addDialogVisible = remember { mutableStateOf(false) }
-    addGroupDialog(addDialogVisible)
+    var addDialogVisible by remember { mutableStateOf(false) }
+    AddGroupDialog(
+        strings = strings,
+        visible = addDialogVisible,
+        onCreate = { name ->
+            viewModel.createGroup(name)
+            addDialogVisible = false
+        },
+        onCancelButtonClicked = { addDialogVisible = false }
+    )
 
     if (viewModel.noGroupsYet.value) {
-        NoGroupsYet(strings) { addDialogVisible.value = true }
+        NoGroupsYet(strings) { addDialogVisible = true }
     } else {
         Row(modifier = Modifier.fillMaxWidth()) {
             GroupList(
@@ -56,7 +64,7 @@ fun <T : GroupItem> GroupScreen(
                 filterBy = viewModel.filterBy.value,
                 onFilterSet = viewModel::setFilterBy,
                 onGroupItemSelected = viewModel::selectGroup,
-                onAddButtonClicked = { addDialogVisible.value = true },
+                onAddButtonClicked = { addDialogVisible = true },
             )
             VerticalDivider()
             Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
@@ -78,7 +86,7 @@ fun NoGroupsYet(strings: GroupStrings, onAdd: () -> Unit) = Explainer(
     ColoredIconButton(
         icon = Icons.Filled.AddComment,
         iconSize = 20.dp,
-        contentDescription = strings.addButtonDescription,
+        contentDescription = strings.addGroupTitle,
         onClick = onAdd,
     )
 }
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupStrings.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupStrings.kt
index 5ae2f103736e28790ab4bf0fa31b4c88ffdae874..505d325ffd4021925f1c7afceda2614f9c8bc116 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupStrings.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/group/GroupStrings.kt
@@ -21,11 +21,14 @@ package org.briarproject.briar.desktop.group
 abstract class GroupStrings(
     val listTitle: String,
     val listDescription: String,
-    val addButtonDescription: String,
+    val addGroupTitle: String,
+    val addGroupHint: String,
+    val addGroupButton: String,
     val noGroupsYet: String,
     val noGroupSelectedTitle: String,
     val noGroupSelectedText: String,
     val messageCount: (Int) -> String,
     val unreadCount: (Int) -> String,
     val lastMessage: (String) -> String,
+    val groupNameMaxLength: Int,
 )
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupListViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupListViewModel.kt
index c273f1c594ec05a3238fb8374dba70c0b081402f..c6310296e2a4e54c0069876cb7f6e56a39f4bcbf 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupListViewModel.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupListViewModel.kt
@@ -23,11 +23,15 @@ import org.briarproject.bramble.api.db.Transaction
 import org.briarproject.bramble.api.db.TransactionManager
 import org.briarproject.bramble.api.event.Event
 import org.briarproject.bramble.api.event.EventBus
+import org.briarproject.bramble.api.identity.IdentityManager
+import org.briarproject.bramble.api.identity.LocalAuthor
 import org.briarproject.bramble.api.lifecycle.LifecycleManager
 import org.briarproject.bramble.api.sync.ClientId
 import org.briarproject.bramble.api.sync.GroupId
+import org.briarproject.bramble.api.system.Clock
 import org.briarproject.briar.api.client.PostHeader
-import org.briarproject.briar.api.forum.ForumManager
+import org.briarproject.briar.api.privategroup.GroupMessageFactory
+import org.briarproject.briar.api.privategroup.PrivateGroupFactory
 import org.briarproject.briar.api.privategroup.PrivateGroupManager
 import org.briarproject.briar.api.privategroup.event.GroupMessageAddedEvent
 import org.briarproject.briar.desktop.forums.ThreadedConversationViewModel
@@ -39,7 +43,11 @@ import javax.inject.Inject
 
 class PrivateGroupListViewModel
 @Inject constructor(
+    private val clock: Clock,
+    private val identityManager: IdentityManager,
     private val privateGroupManager: PrivateGroupManager,
+    private val privateGroupFactory: PrivateGroupFactory,
+    private val privateGroupMessageFactory: GroupMessageFactory,
     threadViewModel: ThreadedConversationViewModel, // todo: subclass
     briarExecutors: BriarExecutors,
     lifecycleManager: LifecycleManager,
@@ -47,7 +55,7 @@ class PrivateGroupListViewModel
     eventBus: EventBus,
 ) : GroupListViewModel<PrivateGroupItem>(threadViewModel, briarExecutors, lifecycleManager, db, eventBus) {
 
-    override val clientId: ClientId = ForumManager.CLIENT_ID
+    override val clientId: ClientId = PrivateGroupManager.CLIENT_ID
 
     override val _groupList = mutableStateListOf<PrivateGroupItem>()
 
@@ -70,9 +78,14 @@ class PrivateGroupListViewModel
         groupCount = privateGroupManager.getGroupCount(txn, id),
     )
 
-    fun createPrivateGroup(name: String) = runOnDbThread {
-        TODO()
-        //privateGroupManager.addForum(name)
+    override fun createGroup(name: String) = runOnDbThread {
+        val author: LocalAuthor = identityManager.localAuthor
+        // in Android, the following two actions are done on the cryptoExecutor
+        val group = privateGroupFactory.createPrivateGroup(name, author)
+        val joinMsg = privateGroupMessageFactory.createJoinMessage(
+            group.id, clock.currentTimeMillis(), author
+        )
+        privateGroupManager.addPrivateGroup(group, joinMsg, true)
     }
 
     override fun loadGroups(txn: Transaction) =
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupScreen.kt
index 0f3d5a83875ccf1716ec70d900923686a1e46f8a..eb8091a1186eec312d90ab37b9770cfcbe4c36c9 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupScreen.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupScreen.kt
@@ -29,8 +29,5 @@ fun PrivateGroupScreen(
 ) = GroupScreen(
     strings = PrivateGroupStrings,
     viewModel = viewModel,
-    addGroupDialog = { visible ->
-        // TODO
-    },
     conversationScreen = { UiPlaceholder() }
 )
diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupStrings.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupStrings.kt
index e3994aee389ba072bd99b48ed6c7e2cb978040a3..d0ca2055c8eb3f88b1025105337a2ed6257f38b7 100644
--- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupStrings.kt
+++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/privategroup/PrivateGroupStrings.kt
@@ -18,6 +18,7 @@
 
 package org.briarproject.briar.desktop.privategroup
 
+import org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH
 import org.briarproject.briar.desktop.group.GroupStrings
 import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18n
 import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nF
@@ -27,7 +28,9 @@ import org.briarproject.briar.desktop.utils.InternationalizationUtils.i18nP
 object PrivateGroupStrings : GroupStrings(
     listTitle = i18n("forum.search.title"),
     listDescription = i18n("access.forums.list"),
-    addButtonDescription = i18n("forum.add.title"),
+    addGroupTitle = i18n("forum.add.title"),
+    addGroupHint = i18n("forum.add.hint"),
+    addGroupButton = i18n("forum.add.button"),
     noGroupsYet = i18n("forum.empty_state.text"),
     noGroupSelectedTitle = i18n("forum.none_selected.title"),
     noGroupSelectedText = i18n("forum.none_selected.hint"),
@@ -41,4 +44,5 @@ object PrivateGroupStrings : GroupStrings(
     lastMessage = { timestamp ->
         i18nF("access.forums.last_post_timestamp", timestamp)
     },
+    groupNameMaxLength = MAX_GROUP_NAME_LENGTH,
 )