diff --git a/js/src/graphql/group.ts b/js/src/graphql/group.ts
index 6b0baf8a0..5f5b27584 100644
--- a/js/src/graphql/group.ts
+++ b/js/src/graphql/group.ts
@@ -326,6 +326,7 @@ export const GROUP_TIMELINE = gql`
   query GroupTimeline(
     $preferredUsername: String!
     $type: ActivityType
+    $author: ActivityAuthor
     $page: Int
     $limit: Int
   ) {
@@ -334,7 +335,7 @@ export const GROUP_TIMELINE = gql`
       preferredUsername
       domain
       name
-      activity(type: $type, page: $page, limit: $limit) {
+      activity(type: $type, author: $author, page: $page, limit: $limit) {
         total
         elements {
           id
diff --git a/js/src/i18n/en_US.json b/js/src/i18n/en_US.json
index 8a58bda0e..fda891bd5 100644
--- a/js/src/i18n/en_US.json
+++ b/js/src/i18n/en_US.json
@@ -960,5 +960,8 @@
   "@{username}'s follow request was accepted": "@{username}'s follow request was accepted",
   "Delete this discussion": "Delete this discussion",
   "Are you sure you want to delete this entire discussion?": "Are you sure you want to delete this entire discussion?",
-  "Delete discussion": "Delete discussion"
+  "Delete discussion": "Delete discussion",
+  "All activities": "All activities",
+  "From yourself": "From yourself",
+  "By others": "By others"
 }
diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json
index d52a037c8..4c5777190 100644
--- a/js/src/i18n/fr_FR.json
+++ b/js/src/i18n/fr_FR.json
@@ -1054,5 +1054,8 @@
   "@{username}'s follow request was accepted": "@{username}'s follow request was accepted",
   "Delete this discussion": "Supprimer cette discussion",
   "Are you sure you want to delete this entire discussion?": "Êtes-vous certain⋅e de vouloir supprimer l'entièreté de cette discussion ?",
-  "Delete discussion": "Supprimer la discussion"
+  "Delete discussion": "Supprimer la discussion",
+  "All activities": "Toutes les activités",
+  "From yourself": "De vous",
+  "By others": "Des autres"
 }
diff --git a/js/src/views/Group/Timeline.vue b/js/src/views/Group/Timeline.vue
index bab7d7b80..ff89edd43 100644
--- a/js/src/views/Group/Timeline.vue
+++ b/js/src/views/Group/Timeline.vue
@@ -23,6 +23,74 @@
       </ul>
     </nav>
     <section class="timeline">
+      <b-field>
+        <b-radio-button v-model="activityType" :native-value="undefined">
+          <b-icon icon="timeline-text"></b-icon>
+          {{ $t("All activities") }}</b-radio-button
+        >
+        <b-radio-button
+          v-model="activityType"
+          :native-value="ActivityType.MEMBER"
+        >
+          <b-icon icon="account-multiple-plus"></b-icon>
+          {{ $t("Members") }}</b-radio-button
+        >
+        <b-radio-button
+          v-model="activityType"
+          :native-value="ActivityType.GROUP"
+        >
+          <b-icon icon="cog"></b-icon>
+          {{ $t("Settings") }}</b-radio-button
+        >
+        <b-radio-button
+          v-model="activityType"
+          :native-value="ActivityType.EVENT"
+        >
+          <b-icon icon="calendar"></b-icon>
+          {{ $t("Events") }}</b-radio-button
+        >
+        <b-radio-button
+          v-model="activityType"
+          :native-value="ActivityType.POST"
+        >
+          <b-icon icon="bullhorn"></b-icon>
+          {{ $t("Posts") }}</b-radio-button
+        >
+        <b-radio-button
+          v-model="activityType"
+          :native-value="ActivityType.DISCUSSION"
+        >
+          <b-icon icon="chat"></b-icon>
+          {{ $t("Discussions") }}</b-radio-button
+        >
+        <b-radio-button
+          v-model="activityType"
+          :native-value="ActivityType.RESOURCE"
+        >
+          <b-icon icon="link"></b-icon>
+          {{ $t("Resources") }}</b-radio-button
+        >
+      </b-field>
+      <b-field>
+        <b-radio-button v-model="activityAuthor" :native-value="undefined">
+          <b-icon icon="timeline-text"></b-icon>
+          {{ $t("All activities") }}</b-radio-button
+        >
+        <b-radio-button
+          v-model="activityAuthor"
+          :native-value="ActivityAuthorFilter.SELF"
+        >
+          <b-icon icon="account"></b-icon>
+          {{ $t("From yourself") }}</b-radio-button
+        >
+        <b-radio-button
+          v-model="activityAuthor"
+          :native-value="ActivityAuthorFilter.BY"
+        >
+          <b-icon icon="account-multiple"></b-icon>
+          {{ $t("By others") }}</b-radio-button
+        >
+      </b-field>
       <transition-group name="timeline-list" tag="div">
         <div
           class="day"
@@ -100,12 +168,20 @@ import { IActivity } from "../../types/activity.model";
 import Observer from "../../components/Utils/Observer.vue";
 import SkeletonActivityItem from "../../components/Activity/SkeletonActivityItem.vue";
 import RouteName from "../../router/name";
+import { Location } from "vue-router";
 
 const PAGINATION_LIMIT = 25;
 const SKELETON_DAY_ITEMS = 2;
 const SKELETON_ITEMS_PER_DAY = 5;
 type IActivitySkeleton = IActivity | { skeleton: string };
 
+enum ActivityAuthorFilter {
+  SELF = "SELF",
+  BY = "BY",
+}
+
+export type ActivityFilter = ActivityType | ActivityAuthorFilter | null;
+
 @Component({
   apollo: {
     group: {
@@ -116,6 +192,8 @@ type IActivitySkeleton = IActivity | { skeleton: string };
           preferredUsername: this.preferredUsername,
           page: 1,
           limit: PAGINATION_LIMIT,
+          type: this.activityType,
+          author: this.activityAuthor,
         };
       },
     },
@@ -157,6 +235,44 @@ export default class Timeline extends Vue {
 
   usernameWithDomain = usernameWithDomain;
 
+  ActivityType = ActivityType;
+
+  ActivityAuthorFilter = ActivityAuthorFilter;
+
+  get activityType(): ActivityType | undefined {
+    if (this.$route?.query?.type) {
+      return this.$route?.query?.type as ActivityType;
+    }
+    return undefined;
+  }
+
+  set activityType(type: ActivityType | undefined) {
+    this.$router.push({
+      name: RouteName.TIMELINE,
+      params: {
+        preferredUsername: this.preferredUsername,
+      },
+      query: { ...this.$route.query, type },
+    });
+  }
+
+  get activityAuthor(): ActivityAuthorFilter | undefined {
+    if (this.$route?.query?.author) {
+      return this.$route?.query?.author as ActivityAuthorFilter;
+    }
+    return undefined;
+  }
+
+  set activityAuthor(author: ActivityAuthorFilter | undefined) {
+    this.$router.push({
+      name: RouteName.TIMELINE,
+      params: {
+        preferredUsername: this.preferredUsername,
+      },
+      query: { ...this.$route.query, author },
+    });
+  }
+
   get activity(): Paginate<IActivitySkeleton> {
     if (this.group) {
       return this.group.activity;
diff --git a/lib/graphql/resolvers/activity.ex b/lib/graphql/resolvers/activity.ex
index e76de34f7..9b7f589a6 100644
--- a/lib/graphql/resolvers/activity.ex
+++ b/lib/graphql/resolvers/activity.ex
@@ -13,13 +13,19 @@ defmodule Mobilizon.GraphQL.Resolvers.Activity do
 
   require Logger
 
-  def group_activity(%Actor{type: :Group, id: group_id}, %{page: page, limit: limit}, %{
+  def group_activity(%Actor{type: :Group, id: group_id}, %{page: page, limit: limit} = args, %{
         context: %{current_user: %User{role: role} = user}
       }) do
     with {:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
          {:member, true} <- {:member, Actors.is_member?(actor_id, group_id) or is_moderator(role)} do
       %Page{total: total, elements: elements} =
-        Activities.list_group_activities_for_member(group_id, actor_id, [], page, limit)
+        Activities.list_group_activities_for_member(
+          group_id,
+          actor_id,
+          [type: Map.get(args, :type), author: Map.get(args, :author)],
+          page,
+          limit
+        )
 
       elements =
         Enum.map(elements, fn %Activity{} = activity ->
diff --git a/lib/graphql/schema/activity.ex b/lib/graphql/schema/activity.ex
index 9a03b3f79..7901c87d5 100644
--- a/lib/graphql/schema/activity.ex
+++ b/lib/graphql/schema/activity.ex
@@ -19,6 +19,11 @@ defmodule Mobilizon.GraphQL.Schema.ActivityType do
     value(:member, description: "Activities concerning members")
   end
 
+  enum :activity_author do
+    value(:self, description: "Activities created by the current actor")
+    value(:by, description: "Activities created by others")
+  end
+
   object :activity_param_item do
     field(:key, :string)
     field(:value, :string)
diff --git a/lib/graphql/schema/actors/group.ex b/lib/graphql/schema/actors/group.ex
index 83c2f9b6d..59d4690f1 100644
--- a/lib/graphql/schema/actors/group.ex
+++ b/lib/graphql/schema/actors/group.ex
@@ -153,6 +153,7 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
       arg(:limit, :integer, default_value: 10, description: "The limit of activity items per page")
 
       arg(:type, :activity_type, description: "Filter by type of activity")
+      arg(:author, :activity_author, description: "Filter by activity author")
       resolve(&Activity.group_activity/3)
       description("The group activity")
     end
diff --git a/lib/mobilizon/activities/activities.ex b/lib/mobilizon/activities/activities.ex
index bb263c798..8c04a4e66 100644
--- a/lib/mobilizon/activities/activities.ex
+++ b/lib/mobilizon/activities/activities.ex
@@ -93,6 +93,7 @@ defmodule Mobilizon.Activities do
     )
     |> where([a, m], a.inserted_at >= m.member_since)
     |> filter_object_type(Keyword.get(filters, :type))
+    |> filter_author(Keyword.get(filters, :author), actor_asking_id)
     |> order_by(desc: :inserted_at)
     |> preload([:author, :group])
     |> Page.build_page(page, limit)
@@ -158,12 +159,21 @@ defmodule Mobilizon.Activities do
 
   def activity_types, do: @activity_types
 
-  @spec filter_object_type(Query.t(), atom()) :: Query.t()
-  defp filter_object_type(query, :type) do
-    where(query, [q], q.type == ^:type)
+  @spec filter_object_type(Query.t(), atom() | nil) :: Query.t()
+  defp filter_object_type(query, nil), do: query
+
+  defp filter_object_type(query, type) do
+    where(query, [q], q.type == ^type)
   end
 
-  defp filter_object_type(query, _) do
-    query
+  @spec filter_author(Query.t(), atom() | nil, integer() | String.t()) :: Query.t()
+  defp filter_author(query, nil, _), do: query
+
+  defp filter_author(query, :self, actor_asking_id) do
+    where(query, [q], q.author_id == ^actor_asking_id)
+  end
+
+  defp filter_author(query, :by, actor_asking_id) do
+    where(query, [q], q.author_id != ^actor_asking_id)
   end
 end
diff --git a/test/graphql/resolvers/activity_test.exs b/test/graphql/resolvers/activity_test.exs
index b3d149fea..437c0e55d 100644
--- a/test/graphql/resolvers/activity_test.exs
+++ b/test/graphql/resolvers/activity_test.exs
@@ -21,6 +21,7 @@ defmodule Mobilizon.GraphQL.Resolvers.ActivityTest do
     query GroupTimeline(
     $preferredUsername: String!
     $type: ActivityType
+    $author: ActivityAuthor
     $page: Int
     $limit: Int
     ) {
@@ -29,7 +30,7 @@ defmodule Mobilizon.GraphQL.Resolvers.ActivityTest do
       preferredUsername
       domain
       name
-      activity(type: $type, page: $page, limit: $limit) {
+      activity(type: $type, author: $author, page: $page, limit: $limit) {
         total
         elements {
           id
@@ -210,6 +211,92 @@ defmodule Mobilizon.GraphQL.Resolvers.ActivityTest do
                event.uuid
     end
 
+    test "group_activity/3 list group activities filtered by type", %{
+      conn: conn,
+      group: %Actor{preferred_username: preferred_username, id: group_id} = group
+    } do
+      user = insert(:user)
+      actor = insert(:actor, user: user)
+
+      insert(:member,
+        parent: group,
+        actor: actor,
+        role: :member,
+        member_since: DateTime.truncate(DateTime.utc_now(), :second)
+      )
+
+      event = insert(:event, attributed_to: group, organizer_actor: actor)
+      post = insert(:post, author: actor, attributed_to: group)
+      EventActivity.insert_activity(event, subject: "event_created")
+      PostActivity.insert_activity(post, subject: "post_created")
+      assert %{success: 2, failure: 0} == Oban.drain_queue(queue: :activity)
+      assert Activities.list_activities() |> length() == 2
+
+      res =
+        conn
+        |> auth_conn(user)
+        |> AbsintheHelpers.graphql_query(
+          query: @group_activities_query,
+          variables: %{preferredUsername: preferred_username, type: "POST"}
+        )
+
+      assert res["errors"] == nil
+      assert res["data"]["group"]["id"] == to_string(group_id)
+      assert res["data"]["group"]["activity"]["total"] == 1
+      activity = hd(res["data"]["group"]["activity"]["elements"])
+      assert activity["object"]["id"] == to_string(post.id)
+      assert activity["subject"] == "post_created"
+
+      assert Enum.find(activity["subjectParams"], &(&1["key"] == "post_title"))["value"] ==
+               post.title
+
+      assert Enum.find(activity["subjectParams"], &(&1["key"] == "post_slug"))["value"] ==
+               post.slug
+    end
+
+    test "group_activity/3 list group activities filtered by author", %{
+      conn: conn,
+      group: %Actor{preferred_username: preferred_username, id: group_id} = group
+    } do
+      user = insert(:user)
+      actor = insert(:actor, user: user)
+
+      insert(:member,
+        parent: group,
+        actor: actor,
+        role: :member,
+        member_since: DateTime.truncate(DateTime.utc_now(), :second)
+      )
+
+      event = insert(:event, attributed_to: group, organizer_actor: actor)
+      post = insert(:post, attributed_to: group)
+      EventActivity.insert_activity(event, subject: "event_created")
+      PostActivity.insert_activity(post, subject: "post_created")
+      assert %{success: 2, failure: 0} == Oban.drain_queue(queue: :activity)
+      assert Activities.list_activities() |> length() == 2
+
+      res =
+        conn
+        |> auth_conn(user)
+        |> AbsintheHelpers.graphql_query(
+          query: @group_activities_query,
+          variables: %{preferredUsername: preferred_username, author: "BY"}
+        )
+
+      assert res["errors"] == nil
+      assert res["data"]["group"]["id"] == to_string(group_id)
+      assert res["data"]["group"]["activity"]["total"] == 1
+      activity = hd(res["data"]["group"]["activity"]["elements"])
+      assert activity["object"]["id"] == to_string(post.id)
+      assert activity["subject"] == "post_created"
+
+      assert Enum.find(activity["subjectParams"], &(&1["key"] == "post_title"))["value"] ==
+               post.title
+
+      assert Enum.find(activity["subjectParams"], &(&1["key"] == "post_slug"))["value"] ==
+               post.slug
+    end
+
     test "group_activity/3 list group activities from deleted object", %{
       conn: conn,
       group: %Actor{preferred_username: preferred_username, id: group_id} = group