diff --git a/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/DeterministicTestDataCreatorImpl.kt b/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/DeterministicTestDataCreatorImpl.kt
index a681e897b3d616c6570ddf87ca6471880ada1036..4c3ab340fe68d608959058af0dd8589462b31dd8 100644
--- a/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/DeterministicTestDataCreatorImpl.kt
+++ b/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/DeterministicTestDataCreatorImpl.kt
@@ -43,12 +43,14 @@ import org.briarproject.bramble.api.properties.TransportPropertyManager
 import org.briarproject.bramble.api.sync.Group
 import org.briarproject.bramble.api.sync.GroupFactory
 import org.briarproject.bramble.api.sync.GroupId
-import org.briarproject.bramble.api.sync.Message
+import org.briarproject.bramble.api.sync.MessageId
 import org.briarproject.bramble.api.system.Clock
 import org.briarproject.briar.api.autodelete.AutoDeleteConstants
 import org.briarproject.briar.api.avatar.AvatarManager
 import org.briarproject.briar.api.avatar.AvatarMessageEncoder
 import org.briarproject.briar.api.conversation.ConversationManager
+import org.briarproject.briar.api.forum.ForumFactory
+import org.briarproject.briar.api.forum.ForumManager
 import org.briarproject.briar.api.messaging.MessagingManager
 import org.briarproject.briar.api.messaging.PrivateMessageFactory
 import org.briarproject.briar.api.privategroup.GroupMessageFactory
@@ -58,6 +60,12 @@ import org.briarproject.briar.api.privategroup.PrivateGroupManager
 import org.briarproject.briar.api.test.TestAvatarCreator
 import org.briarproject.briar.desktop.GroupCountHelper
 import org.briarproject.briar.desktop.attachment.media.ImageCompressor
+import org.briarproject.briar.desktop.testdata.conversation.Direction
+import org.briarproject.briar.desktop.testdata.conversation.Message
+import org.briarproject.briar.desktop.testdata.conversation.conversations
+import org.briarproject.briar.desktop.testdata.forum.Post
+import org.briarproject.briar.desktop.testdata.forum.PostAuthor
+import org.briarproject.briar.desktop.testdata.forum.forums
 import org.briarproject.briar.desktop.utils.KLoggerUtils.i
 import org.briarproject.briar.desktop.utils.KLoggerUtils.w
 import java.io.IOException
@@ -82,6 +90,8 @@ class DeterministicTestDataCreatorImpl @Inject internal constructor(
     private val contactManager: ContactManager,
     private val privateGroupManager: PrivateGroupManager,
     private val privateGroupFactory: PrivateGroupFactory,
+    private val forumManager: ForumManager,
+    private val forumFactory: ForumFactory,
     private val transportPropertyManager: TransportPropertyManager,
     private val conversationManager: ConversationManager,
     private val messagingManager: MessagingManager,
@@ -141,6 +151,8 @@ class DeterministicTestDataCreatorImpl @Inject internal constructor(
         for (privateGroup in privateGroups) {
             createRandomPrivateGroupMessages(privateGroup, contacts, numPrivateGroupPosts)
         }
+
+        createForums()
     }
 
     @Throws(DbException::class)
@@ -297,7 +309,7 @@ class DeterministicTestDataCreatorImpl @Inject internal constructor(
             LOG.w(e) {}
             return
         } ?: return
-        val m: Message = try {
+        val m = try {
             avatarMessageEncoder.encodeUpdateMessage(groupId, 0, "image/jpeg", `is`).first
         } catch (e: IOException) {
             throw DbException(e)
@@ -341,7 +353,7 @@ class DeterministicTestDataCreatorImpl @Inject internal constructor(
     private fun createPrivateMessage(
         contactId: ContactId,
         groupId: GroupId,
-        message: org.briarproject.briar.desktop.testdata.Message
+        message: Message
     ) {
         val timestamp = message.date.toEpochSecond(ZoneOffset.UTC) * 1000
         val text = message.text
@@ -427,4 +439,33 @@ class DeterministicTestDataCreatorImpl @Inject internal constructor(
     ) {
         // TODO
     }
+
+    private fun createForums() {
+        for (f in forums.forums) {
+            // create forum
+            val forum = forumManager.addForum(f.name)
+
+            val members = f.members.associateWith {
+                if (it is PostAuthor.RemoteAuthor) authorFactory.createLocalAuthor(it.name)
+                else identityManager.localAuthor
+            }
+            // todo: create real contact to also share forum!
+
+            // add posts
+            fun addPost(post: Post, parentId: MessageId?) {
+                val m = forumManager.createLocalPost(
+                    forum.id,
+                    post.text,
+                    post.date.toEpochSecond(ZoneOffset.UTC) * 1000,
+                    parentId,
+                    members[post.author]!!
+                )
+                // todo: add non-local posts using incoming message via some contact this forum is shared with
+                forumManager.addLocalPost(m)
+                post.replies.forEach { addPost(it, m.message.id) }
+            }
+            f.posts.forEach { addPost(it, null) }
+        }
+        LOG.i { "Created ${forums.forums.size} forums." }
+    }
 }
diff --git a/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/TestDeterministicData.kt b/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/TestDeterministicData.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8249d89353513576ba9fa8abf2b97090e275d419
--- /dev/null
+++ b/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/TestDeterministicData.kt
@@ -0,0 +1,43 @@
+/*
+ * Briar Desktop
+ * Copyright (C) 2021-2022 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
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package org.briarproject.briar.desktop.testdata
+
+import org.briarproject.briar.desktop.testdata.conversation.conversations
+import org.briarproject.briar.desktop.testdata.forum.Post
+import org.briarproject.briar.desktop.testdata.forum.PostAuthor
+import org.briarproject.briar.desktop.testdata.forum.forums
+
+fun main() {
+    for (conversation in conversations.persons) {
+        println("conversation with: ${conversation.name}") // NON-NLS
+        for (message in conversation.messages) {
+            println("  ${message.direction} ${message.text} ${message.read} ${message.date}")
+        }
+    }
+
+    for (forum in forums.forums) {
+        println("Forum: ${forum.name}") // NON-NLS
+        fun printPost(post: Post, level: Int) {
+            val name = if (post.author is PostAuthor.RemoteAuthor) post.author.name else "me"
+            println("|".repeat(level) + "+ ${post.date} - $name: ${post.text}")
+            post.replies.forEach { printPost(it, level + 1) }
+        }
+        forum.posts.forEach { printPost(it, 0) }
+    }
+}
diff --git a/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/Conversations.kt b/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/conversation/Conversations.kt
similarity index 94%
rename from briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/Conversations.kt
rename to briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/conversation/Conversations.kt
index c157180f38094eb3c910b68378e65f01a52f169c..2734f8b52b207d6014b1ea46166e0db332978668 100644
--- a/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/Conversations.kt
+++ b/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/conversation/Conversations.kt
@@ -16,7 +16,7 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-package org.briarproject.briar.desktop.testdata
+package org.briarproject.briar.desktop.testdata.conversation
 
 import java.time.LocalDateTime
 
diff --git a/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/ConversationsData.kt b/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/conversation/ConversationsData.kt
similarity index 98%
rename from briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/ConversationsData.kt
rename to briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/conversation/ConversationsData.kt
index 76ac296831ecf9377d9f145a561f2debfc6567b4..b3d4da40ee14a77e5963eb23aa5f7462c85a1a90 100644
--- a/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/ConversationsData.kt
+++ b/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/conversation/ConversationsData.kt
@@ -15,8 +15,9 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
+@file:Suppress("nls")
 
-package org.briarproject.briar.desktop.testdata
+package org.briarproject.briar.desktop.testdata.conversation
 
 import java.time.LocalDateTime.of as dt
 
diff --git a/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/ConversationsDsl.kt b/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/conversation/ConversationsDsl.kt
similarity index 91%
rename from briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/ConversationsDsl.kt
rename to briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/conversation/ConversationsDsl.kt
index cf44f9f9da8a11e25a45cc44c9dcf7c1d50046ff..e27bb923cfc21eca18e1252aacd1832b4eeb3d19 100644
--- a/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/ConversationsDsl.kt
+++ b/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/conversation/ConversationsDsl.kt
@@ -16,10 +16,10 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-package org.briarproject.briar.desktop.testdata
+package org.briarproject.briar.desktop.testdata.conversation
 
-import org.briarproject.briar.desktop.testdata.Direction.INCOMING
-import org.briarproject.briar.desktop.testdata.Direction.OUTGOING
+import org.briarproject.briar.desktop.testdata.conversation.Direction.INCOMING
+import org.briarproject.briar.desktop.testdata.conversation.Direction.OUTGOING
 import java.time.LocalDateTime
 import java.time.format.DateTimeFormatter
 
diff --git a/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/TestListConversationData.kt b/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/forum/Forums.kt
similarity index 57%
rename from briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/TestListConversationData.kt
rename to briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/forum/Forums.kt
index 89cf0ab89af3b0468d9461dd89175db9d12e467f..05909ed261086eef2d026f78eb6018e4dbe8e892 100644
--- a/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/TestListConversationData.kt
+++ b/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/forum/Forums.kt
@@ -16,13 +16,32 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-package org.briarproject.briar.desktop.testdata
+package org.briarproject.briar.desktop.testdata.forum
 
-fun main(args: Array<String>) {
-    for (conversation in conversations.persons) {
-        println("conversation with: ${conversation.name}")
-        for (message in conversation.messages) {
-            println("  ${message.direction} ${message.text} ${message.read} ${message.date}")
-        }
-    }
+import java.time.LocalDateTime
+
+data class Forums(
+    val forums: List<Forum>
+)
+
+data class Forum(
+    val name: String,
+    var members: List<PostAuthor>,
+    var posts: List<Post>,
+)
+
+data class Post(
+    val author: PostAuthor,
+    val text: String,
+    val date: LocalDateTime,
+    val replies: List<Post>,
+)
+
+sealed interface PostAuthor {
+    object Me : PostAuthor
+
+    data class RemoteAuthor(
+        val name: String,
+        val sharedWith: Boolean, // todo: not supported yet
+    ) : PostAuthor
 }
diff --git a/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/forum/ForumsData.kt b/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/forum/ForumsData.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8db914762cfe76d1d976c050a1dcd29313f10166
--- /dev/null
+++ b/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/forum/ForumsData.kt
@@ -0,0 +1,126 @@
+/*
+ * Briar Desktop
+ * Copyright (C) 2021-2022 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
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+@file:Suppress("nls")
+
+package org.briarproject.briar.desktop.testdata.forum
+
+import java.time.LocalDateTime
+
+val forums = forums {
+    forum {
+        name = "Briar Friends"
+
+        val me = myself()
+        val anna = member("Anna", sharedForum = true)
+        val paul = member("Paul")
+        val claudia = member("Claudia", sharedForum = true)
+
+        post {
+            author = anna
+            text = "Hi everybody!"
+
+            post {
+                author = paul
+                text = "Hey Anna! It's me, Paul \uD83D\uDC4B️"
+
+                post {
+                    author = anna
+                    text = "Hi Paul! Nice to have you around!"
+                }
+
+                post {
+                    author = paul
+                    text = "Yes indeed. Thanks for sharing the forum with me! \uD83D\uDC99\uD83E\uDD17️️"
+                }
+            }
+
+            post {
+                author = me
+                text = "Hi Anna! Where can I see who else is in here?"
+
+                post {
+                    author = anna
+                    text = "Right now it's only Paul, you and me. " +
+                        "But it's actually not possible to have a list of all members of a forum, " +
+                        "since every person in the forum could share it with any of their contacts."
+
+                    post {
+                        author = me
+                        text = "Oh, that's a bit unexpected!️️"
+                    }
+
+                    post {
+                        author = claudia
+                        text = "That's actually how Paul slipped me in here. Hello everyone!️️"
+
+                        post {
+                            author = paul
+                            text = "️️\uD83D\uDE08️"
+                        }
+                    }
+                }
+            }
+        }
+
+        post {
+            author = anna
+            text = "What do you think about Briar?"
+        }
+    }
+
+    forum {
+        name = "Let's try forums"
+
+        val me = myself()
+        val brian = member("Brian", sharedForum = true)
+        val claudia = member("Claudia", sharedForum = true)
+        val claudia2 = member("Claudia")
+
+        post {
+            author = brian
+            text = "I've just started and shared this forum using Briar Desktop, " +
+                "did everything work as expected?"
+            date = LocalDateTime.of(2022, 11, 28, 16, 12, 38)
+
+            post {
+                author = me
+                text = "Wow nice, yes, everything seems to work!"
+
+                post {
+                    author = brian
+                    text = "Perfect, that's amazing!"
+                }
+            }
+
+            post {
+                author = claudia
+                text = "Hey people! That's so exciting that forums work on Briar Desktop now, too!"
+
+                post {
+                    author = claudia2
+                    text = "And it is fully compatible with Briar Android as well! I'm writing this on my phone just now."
+
+                    post {
+                        author = me
+                        text = "Oh hi, can you introduce me to your Briar account on the phone, please?"
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/forum/ForumsDsl.kt b/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/forum/ForumsDsl.kt
new file mode 100644
index 0000000000000000000000000000000000000000..44d9a52a7bfaaf519ec9313d95eb6e868d19c867
--- /dev/null
+++ b/briar-desktop/src/test/kotlin/org/briarproject/briar/desktop/testdata/forum/ForumsDsl.kt
@@ -0,0 +1,140 @@
+/*
+ * Briar Desktop
+ * Copyright (C) 2021-2022 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
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package org.briarproject.briar.desktop.testdata.forum
+
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+import kotlin.random.Random
+
+@DslMarker
+annotation class PostsDsl
+
+/**
+ * Define a list of forums.
+ */
+fun forums(block: ForumsBuilder.() -> Unit) = ForumsBuilder().apply(block).build()
+
+@PostsDsl
+class ForumsBuilder {
+
+    private val forums = mutableListOf<Forum>()
+
+    /**
+     * Add a new forum to the forum list.
+     */
+    fun forum(block: ForumBuilder.() -> Unit) {
+        forums.add(ForumBuilder().apply(block).build())
+    }
+
+    fun build() = Forums(forums)
+}
+
+@PostsDsl
+class ForumBuilder : PostHierarchyBuilder() {
+
+    /**
+     * The name of the forum.
+     * Needs to be explicitly set.
+     */
+    lateinit var name: String
+
+    private var members = mutableListOf<PostAuthor>(PostAuthor.Me)
+    override var lastReplySent: LocalDateTime = LocalDateTime.now()
+
+    /**
+     * Create and return a new member for this forum.
+     * You can use the return value as the `author` of a [post].
+     */
+    fun member(name: String, sharedForum: Boolean = false) =
+        PostAuthor.RemoteAuthor(name, sharedForum).also { members.add(it) }
+
+    /**
+     * Reference to the local author.
+     * You can use this as the `author` of a [post].
+     */
+    fun myself() = PostAuthor.Me
+
+    fun build(): Forum {
+        check(this::name.isInitialized) { "A forum needs a name to be valid." } // NON-NLS
+        return Forum(name, members, posts)
+    }
+}
+
+@PostsDsl
+class PostBuilder(parentPostSent: LocalDateTime) : PostHierarchyBuilder() {
+
+    /**
+     * The author of the post.
+     * You have to create authors using `member` in a forum.
+     * If not set, defaults to the local author.
+     */
+    var author: PostAuthor = PostAuthor.Me
+
+    /**
+     * The text of the post.
+     * Needs to be explicitly set.
+     */
+    lateinit var text: String
+
+    private var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
+    private var _date = parentPostSent.addRandomDelay()
+
+    /**
+     * The date of the post.
+     * Can be set to a [String] matching the format `yyyy-MM-dd HH:mm:ss`
+     * or to a [LocalDateTime] object.
+     *
+     * If not set, defaults to [LocalDateTime.now] for the first post in a forum,
+     * and to a random time between zero and five minutes
+     * after the last post in the corresponding thread for all subsequent ones.
+     */
+    var date: Any = ""
+        set(value) {
+            if (value is String) {
+                _date = LocalDateTime.parse(value, formatter)
+            } else if (value is LocalDateTime) {
+                _date = value
+            }
+            lastReplySent = _date
+        }
+
+    override var lastReplySent: LocalDateTime = _date
+
+    fun build(): Post {
+        check(this::text.isInitialized) { "A forum post needs to contain a text to be valid." } // NON-NLS
+        return Post(author, text, _date, posts)
+    }
+}
+
+@PostsDsl
+abstract class PostHierarchyBuilder {
+    protected val posts = mutableListOf<Post>()
+    protected abstract var lastReplySent: LocalDateTime
+
+    /**
+     * Add a new post to the forum or a new reply to the enclosing post.
+     */
+    fun post(block: PostBuilder.() -> Unit) {
+        val post = PostBuilder(lastReplySent).apply(block).build()
+        posts.add(post)
+        lastReplySent = post.date
+    }
+}
+
+private fun LocalDateTime.addRandomDelay() = plusSeconds(Random.nextLong(5 * 60))