diff --git a/js/src/components/NavBar.vue b/js/src/components/NavBar.vue
index 972a55dfa..efbc62f84 100644
--- a/js/src/components/NavBar.vue
+++ b/js/src/components/NavBar.vue
@@ -40,6 +40,10 @@
                 <router-link :to="{ name: 'UpdateIdentity' }" v-translate>My account</router-link>
               </span>
 
+              <span class="navbar-item">
+                <router-link :to="{ name: ActorRouteName.CREATE_GROUP }" v-translate>Create group</router-link>
+              </span>
+
               <a v-translate class="navbar-item" v-on:click="logout()">Log out</a>
             </div>
           </div>
@@ -70,6 +74,7 @@ import { IConfig } from '@/types/config.model';
 import { ICurrentUser } from '@/types/current-user.model';
 import Logo from '@/components/Logo.vue';
 import SearchField from '@/components/SearchField.vue';
+import { ActorRouteName } from '@/router/actor';
 
 @Component({
   apollo: {
@@ -95,6 +100,8 @@ export default class NavBar extends Vue {
   currentUser!: ICurrentUser;
   showNavbar: boolean = false;
 
+  ActorRouteName = ActorRouteName;
+
   @Watch('currentUser')
   async onCurrentUserChanged() {
     // Refresh logged person object
diff --git a/js/src/graphql/actor.ts b/js/src/graphql/actor.ts
index 61543f916..7dc92ca20 100644
--- a/js/src/graphql/actor.ts
+++ b/js/src/graphql/actor.ts
@@ -174,3 +174,34 @@ query($name:String!) {
   }
 }
 `;
+
+export const CREATE_GROUP = gql`
+  mutation CreateGroup(
+    $creatorActorId: Int!,
+    $preferredUsername: String!,
+    $name: String!,
+    $summary: String,
+    $avatar: PictureInput,
+    $banner: PictureInput
+  ) {
+    createGroup(
+      creatorActorId: $creatorActorId,
+      preferredUsername: $preferredUsername,
+      name: $name,
+      summary: $summary,
+      banner: $banner,
+      avatar: $avatar
+    ) {
+      id,
+      preferredUsername,
+      name,
+      summary,
+      avatar {
+        url
+      },
+      banner {
+        url
+      }
+    }
+  }
+`;
diff --git a/js/src/types/actor/group.model.ts b/js/src/types/actor/group.model.ts
index d7dcd0566..c93c4c638 100644
--- a/js/src/types/actor/group.model.ts
+++ b/js/src/types/actor/group.model.ts
@@ -19,4 +19,14 @@ export interface IMember {
 
 export class Group extends Actor implements IGroup {
   members: IMember[] = [];
+
+  constructor(hash: IGroup | {} = {}) {
+    super(hash);
+
+    this.patch(hash);
+  }
+
+  patch (hash: any) {
+    Object.assign(this, hash);
+  }
 }
diff --git a/js/src/views/Group/Create.vue b/js/src/views/Group/Create.vue
index 8440f91ae..6d7727168 100644
--- a/js/src/views/Group/Create.vue
+++ b/js/src/views/Group/Create.vue
@@ -1,87 +1,124 @@
 <template>
-  <section>
-    <h1>
-      <translate>Create a new group</translate>
-    </h1>
-    <div class="columns">
-      <form class="column" @submit="createGroup">
-        <b-field :label="$gettext('Group name')">
-          <b-input aria-required="true" required v-model="group.preferred_username"/>
-        </b-field>
+  <div class="root">
+    <h1 v-translate>Create a new group</h1>
 
-        <b-field :label="$gettext('Group full name')">
-          <b-input aria-required="true" required v-model="group.name"/>
-        </b-field>
+    <div>
+      <b-field :label="$gettext('Group name')">
+        <b-input aria-required="true" required v-model="group.preferred_username"/>
+      </b-field>
 
-        <b-field :label="$gettext('Description')">
-          <b-input aria-required="true" required v-model="group.summary" type="textarea"/>
-        </b-field>
+      <b-field :label="$gettext('Group full name')">
+        <b-input aria-required="true" required v-model="group.name"/>
+      </b-field>
 
-        <button class="button is-primary">
-          <translate>Create my group</translate>
-        </button>
-      </form>
+      <b-field :label="$gettext('Description')">
+        <b-input aria-required="true" required v-model="group.description" type="textarea"/>
+      </b-field>
+
+      <div>
+        Avatar
+        <picture-upload v-model="avatarFile"></picture-upload>
+      </div>
+
+      <div>
+        Banner
+        <picture-upload v-model="avatarFile"></picture-upload>
+      </div>
+
+      <button class="button is-primary" @click="createGroup()">
+        <translate>Create my group</translate>
+      </button>
     </div>
-  </section>
+  </div>
 </template>
 
+<style lang="scss" scoped>
+  .root {
+    width: 400px;
+    margin: auto;
+  }
+</style>
+
 <script lang="ts">
 import { Component, Vue } from 'vue-property-decorator';
+import { Group, IPerson } from '@/types/actor';
+import { CREATE_GROUP, LOGGED_PERSON } from '@/graphql/actor';
+import { RouteName } from '@/router';
+import PictureUpload from '@/components/PictureUpload.vue';
 
-@Component({})
+@Component({
+  components: {
+    PictureUpload,
+  },
+  apollo: {
+    loggedPerson: {
+      query: LOGGED_PERSON,
+    },
+  },
+})
 export default class CreateGroup extends Vue {
-  e1 = 0;
-  // FIXME: correctly type group
-  group: {
-    preferred_username: string;
-    name: string;
-    summary: string;
-    address?: any;
-  } = {
-    preferred_username: '',
-    name: '',
-    summary: '',
-    // category: null,
-  };
-  categories = [];
+  loggedPerson!: IPerson;
 
-  mounted() {
-    this.fetchCategories();
+  group = new Group();
+
+  avatarFile: File | null = null;
+  bannerFile: File | null = null;
+
+  async createGroup() {
+    try {
+      await this.$apollo.mutate({
+        mutation: CREATE_GROUP,
+        variables: this.buildVariables(),
+        update: (store, { data: { createGroup } }) => {
+          // TODO: update group list cache
+        },
+      });
+
+      this.$router.push({ name: RouteName.GROUP, params: { identityName: this.group.preferredUsername } });
+
+      this.$notifier.success(
+        this.$gettextInterpolate('Group %{displayName} created', { displayName: this.group.displayName() }),
+      );
+    } catch (err) {
+      this.handleError(err);
+    }
   }
 
-  createGroup() {
-    // this.group.organizer = "/accounts/" + this.$store.state.user.id;
-    // FIXME: remove eventFetch
-    // eventFetch('/groups', this.$store, { method: 'POST', body: JSON.stringify({ group: this.group }) })
-    //   .then(response => response.json())
-    //   .then((data) => {
-    //     this.loading = false;
-    //     this.$router.push({ path: 'Group', params: { id: data.id } });
-    //   });
-  }
+  private buildVariables() {
+    let avatarObj = {};
+    let bannerObj = {};
 
-  fetchCategories() {
-    // FIXME: remove eventFetch
-    // eventFetch('/categories', this.$store)
-    //   .then(response => response.json())
-    //   .then((data) => {
-    //     this.loading = false;
-    //     this.categories = data.data;
-    //   });
-  }
+    if (this.avatarFile) {
+      avatarObj = {
+        avatar: {
+          picture: {
+            name: this.avatarFile.name,
+            alt: `${this.group.preferredUsername}'s avatar`,
+            file: this.avatarFile,
+          },
+        },
+      };
+    }
 
-  getAddressData(addressData) {
-    this.group.address = {
-      geo: {
-        latitude: addressData.latitude,
-        longitude: addressData.longitude,
-      },
-      country: addressData.country,
-      locality: addressData.city,
-      region: addressData.administrative_area_level_1,
-      postalCode: addressData.postalCode,
-      street: `${addressData.street_number} ${addressData.route}`,
+    if (this.bannerFile) {
+      bannerObj = {
+        picture: {
+          name: this.bannerFile.name,
+          alt: `${this.group.preferredUsername}'s banner`,
+          file: this.bannerFile,
+        },
+      };
+    }
+
+    const currentActor = {
+      creatorActorId: this.loggedPerson.id,
     };
+
+    return Object.assign({}, this.group, avatarObj, bannerObj, currentActor);
+  }
+
+  private handleError(err: any) {
+    console.error(err);
   }
 }
 </script>
diff --git a/js/src/vue-apollo.ts b/js/src/vue-apollo.ts
index d39a67361..91e513fea 100644
--- a/js/src/vue-apollo.ts
+++ b/js/src/vue-apollo.ts
@@ -107,7 +107,7 @@ const apolloClient = new ApolloClient({
   cache,
   link,
   connectToDevTools: true,
-  resolvers: buildCurrentUserResolver(cache)
+  resolvers: buildCurrentUserResolver(cache),
 });
 
 export const apolloProvider = new VueApollo({
diff --git a/lib/mobilizon_web/api/groups.ex b/lib/mobilizon_web/api/groups.ex
index 8c6e8f64b..5033792b3 100644
--- a/lib/mobilizon_web/api/groups.ex
+++ b/lib/mobilizon_web/api/groups.ex
@@ -3,7 +3,7 @@ defmodule MobilizonWeb.API.Groups do
   API for Events
   """
   alias Mobilizon.Actors
-  alias Mobilizon.Actors.Actor
+  alias Mobilizon.Users.User
   alias Mobilizon.Service.ActivityPub
   alias Mobilizon.Service.ActivityPub.Utils, as: ActivityPubUtils
   alias MobilizonWeb.API.Utils
@@ -11,24 +11,26 @@ defmodule MobilizonWeb.API.Groups do
   @doc """
   Create a group
   """
-  @spec create_group(map()) :: {:ok, Activity.t(), Group.t()} | any()
+  @spec create_group(User.t(), map()) :: {:ok, Activity.t(), Group.t()} | any()
   def create_group(
+        user,
         %{
           preferred_username: title,
-          description: description,
-          admin_actor_username: admin_actor_username
+          summary: summary,
+          creator_actor_id: creator_actor_id,
+          avatar: avatar,
+          banner: banner
         } = args
       ) do
-    with {:bad_actor, %Actor{url: url} = actor} <-
-           {:bad_actor, Actors.get_local_actor_by_name(admin_actor_username)},
+    with {:is_owned, true, actor} <- User.owns_actor(user, creator_actor_id),
          {:existing_group, nil} <- {:existing_group, Actors.get_group_by_title(title)},
          title <- String.trim(title),
          visibility <- Map.get(args, :visibility, :public),
          {content_html, tags, to, cc} <-
-           Utils.prepare_content(actor, description, visibility, [], nil),
+           Utils.prepare_content(actor, summary, visibility, [], nil),
          group <-
            ActivityPubUtils.make_group_data(
-             url,
+             actor.url,
              to,
              title,
              content_html,
@@ -43,10 +45,10 @@ defmodule MobilizonWeb.API.Groups do
       })
     else
       {:existing_group, _} ->
-        {:error, :existing_group_name}
+        {:error, "A group with this name already exists"}
 
-      {:bad_actor, _} ->
-        {:error, :bad_admin_actor}
+      {:is_owned, _} ->
+        {:error, "Actor id is not owned by authenticated user"}
     end
   end
 end
diff --git a/lib/mobilizon_web/resolvers/group.ex b/lib/mobilizon_web/resolvers/group.ex
index 96b4214eb..902d77640 100644
--- a/lib/mobilizon_web/resolvers/group.ex
+++ b/lib/mobilizon_web/resolvers/group.ex
@@ -27,8 +27,11 @@ defmodule MobilizonWeb.Resolvers.Group do
   Lists all groups
   """
   def list_groups(_parent, %{page: page, limit: limit}, _resolution) do
-    {:ok,
-     Actors.list_groups(page, limit) |> Enum.map(fn actor -> Person.proxify_pictures(actor) end)}
+    {
+      :ok,
+      Actors.list_groups(page, limit)
+      |> Enum.map(fn actor -> Person.proxify_pictures(actor) end)
+    }
   end
 
   @doc """
@@ -39,7 +42,7 @@ defmodule MobilizonWeb.Resolvers.Group do
         args,
         %{
           context: %{
-            current_user: _user
+            current_user: user
           }
         }
       ) do
@@ -52,26 +55,22 @@ defmodule MobilizonWeb.Resolvers.Group do
            },
            %Actor{} = group
          } <-
-           MobilizonWeb.API.Groups.create_group(args) do
+           MobilizonWeb.API.Groups.create_group(
+             user,
+             %{
+               preferred_username: args.preferred_username,
+               creator_actor_id: args.creator_actor_id,
+               name: Map.get(args, "name", args.preferred_username),
+               summary: args.summary,
+               avatar: Map.get(args, "avatar"),
+               banner: Map.get(args, "banner")
+             }
+           ) do
       {
         :ok,
         group
       }
     end
-
-    # with %Actor{id: actor_id} <- Actors.get_local_actor_by_name(actor_username),
-    #      {:user_actor, true} <-
-    #        {:user_actor, actor_id in Enum.map(Actors.get_actors_for_user(user), & &1.id)},
-    #      {:ok, %Actor{} = group} <- Actors.create_group(%{preferred_username: preferred_username}) do
-    #   {:ok, group}
-    # else
-    #   {:error, %Ecto.Changeset{errors: [url: {"has already been taken", []}]}} ->
-    #     {:error, :group_name_not_available}
-
-    #   err ->
-    #     Logger.error(inspect(err))
-    #     err
-    # end
   end
 
   def create_group(_parent, _args, _resolution) do
@@ -138,12 +137,18 @@ defmodule MobilizonWeb.Resolvers.Group do
              actor_id: actor.id,
              role: role
            }) do
-      {:ok,
-       %{
-         parent: group |> Person.proxify_pictures(),
-         actor: actor |> Person.proxify_pictures(),
-         role: role
-       }}
+      {
+        :ok,
+        %{
+          parent:
+            group
+            |> Person.proxify_pictures(),
+          actor:
+            actor
+            |> Person.proxify_pictures(),
+          role: role
+        }
+      }
     else
       {:is_owned, false} ->
         {:error, "Actor id is not owned by authenticated user"}
diff --git a/lib/mobilizon_web/schema/actors/group.ex b/lib/mobilizon_web/schema/actors/group.ex
index c3c65b407..30226039d 100644
--- a/lib/mobilizon_web/schema/actors/group.ex
+++ b/lib/mobilizon_web/schema/actors/group.ex
@@ -95,13 +95,14 @@ defmodule MobilizonWeb.Schema.Actors.GroupType do
     @desc "Create a group"
     field :create_group, :group do
       arg(:preferred_username, non_null(:string), description: "The name for the group")
-      arg(:name, :string, description: "The displayed name for the group")
-      arg(:description, :string, description: "The summary for the group", default_value: "")
 
-      arg(:admin_actor_username, :string,
-        description: "The actor's username which will be the admin (otherwise user's default one)"
+      arg(:creator_actor_id, non_null(:integer),
+        description: "The identity that creates the group"
       )
 
+      arg(:name, :string, description: "The displayed name for the group")
+      arg(:summary, :string, description: "The summary for the group", default_value: "")
+
       arg(:avatar, :picture_input,
         description:
           "The avatar for the group, either as an object or directly the ID of an existing Picture"
diff --git a/lib/mobilizon_web/schema/event.ex b/lib/mobilizon_web/schema/event.ex
index 96f8bea30..25a6ffcac 100644
--- a/lib/mobilizon_web/schema/event.ex
+++ b/lib/mobilizon_web/schema/event.ex
@@ -72,7 +72,7 @@ defmodule MobilizonWeb.Schema.EventType do
 
   @desc "The list of visibility options for an event"
   enum :event_visibility do
-    value(:public, description: "Publically listed and federated. Can be shared.")
+    value(:public, description: "Publicly listed and federated. Can be shared.")
     value(:unlisted, description: "Visible only to people with the link - or invited")
 
     value(:private,
diff --git a/test/mobilizon_web/resolvers/group_resolver_test.exs b/test/mobilizon_web/resolvers/group_resolver_test.exs
index 00334af92..7373b2f14 100644
--- a/test/mobilizon_web/resolvers/group_resolver_test.exs
+++ b/test/mobilizon_web/resolvers/group_resolver_test.exs
@@ -15,12 +15,37 @@ defmodule MobilizonWeb.Resolvers.GroupResolverTest do
   end
 
   describe "Group Resolver" do
-    test "create_group/3 creates a group", %{conn: conn, user: user, actor: actor} do
+    test "create_group/3 should check the user owns the identity", %{conn: conn, user: user} do
+      another_actor = insert(:actor)
+
       mutation = """
           mutation {
             createGroup(
               preferred_username: "#{@new_group_params.groupname}",
-              admin_actor_username: "#{actor.preferred_username}"
+              creator_actor_id: #{another_actor.id}
+            ) {
+                preferred_username,
+                type
+              }
+            }
+      """
+
+      res =
+        conn
+        |> auth_conn(user)
+        |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
+
+      assert hd(json_response(res, 200)["errors"])["message"] ==
+               "Actor id is not owned by authenticated user"
+    end
+
+    test "create_group/3 creates a group and check a group with this name does not already exist",
+         %{conn: conn, user: user, actor: actor} do
+      mutation = """
+          mutation {
+            createGroup(
+              preferred_username: "#{@new_group_params.groupname}",
+              creator_actor_id: #{actor.id}
             ) {
                 preferred_username,
                 type
@@ -42,7 +67,7 @@ defmodule MobilizonWeb.Resolvers.GroupResolverTest do
           mutation {
             createGroup(
               preferred_username: "#{@new_group_params.groupname}",
-              admin_actor_username: "#{actor.preferred_username}",
+              creator_actor_id: #{actor.id},
             ) {
                 preferred_username,
                 type
@@ -55,7 +80,8 @@ defmodule MobilizonWeb.Resolvers.GroupResolverTest do
         |> auth_conn(user)
         |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
 
-      assert hd(json_response(res, 200)["errors"])["message"] == "existing_group_name"
+      assert hd(json_response(res, 200)["errors"])["message"] ==
+               "A group with this name already exists"
     end
 
     test "list_groups/3 returns all public or unlisted groups", context do
diff --git a/test/mobilizon_web/resolvers/participant_resolver_test.exs b/test/mobilizon_web/resolvers/participant_resolver_test.exs
index 80ad79cad..c2a3bbb48 100644
--- a/test/mobilizon_web/resolvers/participant_resolver_test.exs
+++ b/test/mobilizon_web/resolvers/participant_resolver_test.exs
@@ -7,7 +7,9 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
   @event %{
     description: "some body",
     title: "some title",
-    begins_on: DateTime.utc_now() |> DateTime.truncate(:second),
+    begins_on:
+      DateTime.utc_now()
+      |> DateTime.truncate(:second),
     uuid: "b5126423-f1af-43e4-a923-002a03003ba4",
     url: "some url",
     category: "meeting"
@@ -171,7 +173,9 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
 
       assert json_response(res, 200)["data"]["participants"] == [
                %{
-                 "actor" => %{"preferredUsername" => participant2.actor.preferred_username},
+                 "actor" => %{
+                   "preferredUsername" => participant2.actor.preferred_username
+                 },
                  "role" => "creator"
                }
              ]
@@ -339,7 +343,9 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
 
       assert json_response(res, 200)["data"]["participants"] == [
                %{
-                 "actor" => %{"preferredUsername" => context.actor.preferred_username},
+                 "actor" => %{
+                   "preferredUsername" => context.actor.preferred_username
+                 },
                  "role" => "creator"
                }
              ]
@@ -356,14 +362,26 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
         context.conn
         |> get("/api", AbsintheHelpers.query_skeleton(query, "participants"))
 
-      assert json_response(res, 200)["data"]["participants"] == [
+      sorted_participants =
+        json_response(res, 200)["data"]["participants"]
+        |> Enum.sort_by(
+          &(&1
+            |> Map.get("actor")
+            |> Map.get("preferredUsername"))
+        )
+
+      assert sorted_participants == [
                %{
-                 "actor" => %{"preferredUsername" => participant2.actor.preferred_username},
-                 "role" => "participant"
+                 "actor" => %{
+                   "preferredUsername" => context.actor.preferred_username
+                 },
+                 "role" => "creator"
                },
                %{
-                 "actor" => %{"preferredUsername" => context.actor.preferred_username},
-                 "role" => "creator"
+                 "actor" => %{
+                   "preferredUsername" => participant2.actor.preferred_username
+                 },
+                 "role" => "participant"
                }
              ]
     end