diff --git a/js/src/components/Event/AddressAutoComplete.vue b/js/src/components/Event/AddressAutoComplete.vue
index e98c2fbb3..f156469b5 100644
--- a/js/src/components/Event/AddressAutoComplete.vue
+++ b/js/src/components/Event/AddressAutoComplete.vue
@@ -71,6 +71,7 @@ import { IConfig } from "../../types/config.model";
 })
 export default class AddressAutoComplete extends Vue {
   @Prop({ required: true }) value!: IAddress;
+  @Prop({ required: false, default: false }) type!: string | false;
 
   addressData: IAddress[] = [];
 
@@ -118,13 +119,17 @@ export default class AddressAutoComplete extends Vue {
     }
 
     this.isFetching = true;
+    const variables: { query: string; locale: string; type?: string } = {
+      query,
+      locale: this.$i18n.locale,
+    };
+    if (this.type) {
+      variables.type = this.type;
+    }
     const result = await this.$apollo.query({
       query: ADDRESS,
       fetchPolicy: "network-only",
-      variables: {
-        query,
-        locale: this.$i18n.locale,
-      },
+      variables,
     });
 
     this.addressData = result.data.searchAddress.map(
@@ -144,7 +149,7 @@ export default class AddressAutoComplete extends Vue {
 
   @Watch("value")
   updateEditing(): void {
-    if (!(this.value && this.value.id)) return;
+    if (!this.value?.id) return;
     this.selected = this.value;
     const address = new Address(this.selected);
     this.queryText = `${address.poiInfos.name} ${address.poiInfos.alternativeName}`;
diff --git a/js/src/components/Event/EventCard.vue b/js/src/components/Event/EventCard.vue
index f41a537bb..f6f8a7ee0 100644
--- a/js/src/components/Event/EventCard.vue
+++ b/js/src/components/Event/EventCard.vue
@@ -39,13 +39,24 @@
           />
         </div>
         <div class="media-content">
-          <p class="event-title">{{ event.title }}</p>
-          <div class="event-subtitle" v-if="event.physicalAddress">
+          <p class="event-title" :title="event.title">{{ event.title }}</p>
+          <div
+            class="event-subtitle"
+            v-if="event.physicalAddress"
+            :title="
+              isDescriptionDifferentFromLocality
+                ? `${event.physicalAddress.description}, ${event.physicalAddress.locality}`
+                : event.physicalAddress.description
+            "
+          >
             <!--            <p>{{ $t('By @{username}', { username: actor.preferredUsername }) }}</p>-->
-            <span>
+            <span v-if="isDescriptionDifferentFromLocality">
               {{ event.physicalAddress.description }},
               {{ event.physicalAddress.locality }}
             </span>
+            <span v-else>
+              {{ event.physicalAddress.description }}
+            </span>
           </div>
         </div>
       </div>
@@ -130,6 +141,14 @@ export default class EventCard extends Vue {
       this.event.organizerActor || this.mergedOptions.organizerActor
     );
   }
+
+  get isDescriptionDifferentFromLocality(): boolean {
+    return (
+      this.event?.physicalAddress?.description !==
+        this.event?.physicalAddress?.locality &&
+      this.event?.physicalAddress?.description !== undefined
+    );
+  }
 }
 </script>
 
diff --git a/js/src/components/Event/RecentEventCardWrapper.vue b/js/src/components/Event/RecentEventCardWrapper.vue
new file mode 100644
index 000000000..bee20a990
--- /dev/null
+++ b/js/src/components/Event/RecentEventCardWrapper.vue
@@ -0,0 +1,35 @@
+<template>
+  <div>
+    <p class="time">
+      {{
+        formatDistanceToNow(new Date(event.publishAt || event.insertedAt), {
+          locale: $dateFnsLocale,
+          addSuffix: true,
+        }) || $t("Right now")
+      }}
+    </p>
+    <EventCard :event="event" />
+  </div>
+</template>
+<script lang="ts">
+import { IEvent } from "@/types/event.model";
+import { formatDistanceToNow } from "date-fns";
+import { Component, Prop, Vue } from "vue-property-decorator";
+import EventCard from "./EventCard.vue";
+
+@Component({
+  components: {
+    EventCard,
+  },
+})
+export default class RecentEventCardWrapper extends Vue {
+  @Prop({ required: true, type: Object }) event!: IEvent;
+
+  formatDistanceToNow = formatDistanceToNow;
+}
+</script>
+<style lang="scss" scoped>
+p.time {
+  color: $orange-2;
+}
+</style>
diff --git a/js/src/graphql/address.ts b/js/src/graphql/address.ts
index 9e3fdbc0e..527b86258 100644
--- a/js/src/graphql/address.ts
+++ b/js/src/graphql/address.ts
@@ -15,10 +15,11 @@ originId
 `;
 
 export const ADDRESS = gql`
-    query($query:String!, $locale: String) {
+    query($query:String!, $locale: String, $type: AddressSearchType) {
         searchAddress(
             query: $query,
-            locale: $locale
+            locale: $locale,
+            type: $type
         ) {
             ${$addressFragment}
         }
diff --git a/js/src/graphql/event.ts b/js/src/graphql/event.ts
index 834a25f80..1012798c2 100644
--- a/js/src/graphql/event.ts
+++ b/js/src/graphql/event.ts
@@ -207,6 +207,7 @@ export const FETCH_EVENTS = gql`
         endsOn
         status
         visibility
+        insertedAt
         picture {
           id
           url
@@ -673,3 +674,26 @@ export const FETCH_GROUP_EVENTS = gql`
     }
   }
 `;
+
+export const CLOSE_EVENTS = gql`
+  query CloseEvents($location: String, $radius: Float) {
+    searchEvents(location: $location, radius: $radius, page: 1, limit: 10) {
+      total
+      elements {
+        id
+        title
+        uuid
+        beginsOn
+        picture {
+          id
+          url
+        }
+        tags {
+          slug
+          title
+        }
+        __typename
+      }
+    }
+  }
+`;
diff --git a/js/src/graphql/user.ts b/js/src/graphql/user.ts
index 158be5e71..b794ef1a8 100644
--- a/js/src/graphql/user.ts
+++ b/js/src/graphql/user.ts
@@ -125,6 +125,11 @@ export const USER_SETTINGS_FRAGMENT = gql`
     notificationBeforeEvent
     notificationPendingParticipation
     notificationPendingMembership
+    location {
+      range
+      geohash
+      name
+    }
   }
 `;
 
@@ -149,6 +154,7 @@ export const SET_USER_SETTINGS = gql`
     $notificationBeforeEvent: Boolean
     $notificationPendingParticipation: NotificationPendingEnum
     $notificationPendingMembership: NotificationPendingEnum
+    $location: LocationInput
   ) {
     setUserSettings(
       timezone: $timezone
@@ -157,6 +163,7 @@ export const SET_USER_SETTINGS = gql`
       notificationBeforeEvent: $notificationBeforeEvent
       notificationPendingParticipation: $notificationPendingParticipation
       notificationPendingMembership: $notificationPendingMembership
+      location: $location
     ) {
       ...UserSettingFragment
     }
diff --git a/js/src/i18n/en_US.json b/js/src/i18n/en_US.json
index 669100da6..c03e07c49 100644
--- a/js/src/i18n/en_US.json
+++ b/js/src/i18n/en_US.json
@@ -850,5 +850,14 @@
   "{instanceName} is an instance of {mobilizon_link}, a free software built with the community.": "{instanceName} is an instance of {mobilizon_link}, a free software built with the community.",
   "Open a topic on our forum": "Open a topic on our forum",
   "Open an issue on our bug tracker (advanced users)": "Open an issue on our bug tracker (advanced users)",
-  "Unable to copy to clipboard": "Unable to copy to clipboard"
+  "Unable to copy to clipboard": "Unable to copy to clipboard",
+  "{count} km": "{count} km",
+  "City or region": "City or region",
+  "Select a radius": "Select a radius",
+  "Your city or region and the radius will only be used to suggest you events nearby.": "Your city or region and the radius will only be used to suggest you events nearby.",
+  "Your upcoming events": "Your upcoming events",
+  "Last published events": "Last published events",
+  "On {instance}": "On {instance}",
+  "Close events": "Close events",
+  "Within {number} kilometers of {place}": "|Within one kilometer of {place}|Within {number} kilometers of {place}"
 }
diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json
index 30038166f..d0c26e188 100644
--- a/js/src/i18n/fr_FR.json
+++ b/js/src/i18n/fr_FR.json
@@ -945,5 +945,14 @@
   "{instanceName} is an instance of {mobilizon_link}, a free software built with the community.": "{instanceName} est une instance de {mobilizon_link}, un logiciel libre construit de manière communautaire.",
   "Open a topic on our forum": "Ouvrir un sujet sur notre forum",
   "Open an issue on our bug tracker (advanced users)": "Ouvrir un ticket sur notre système de suivi des bugs (utilisateur⋅ices avancé⋅es)",
-  "Unable to copy to clipboard": "Impossible de copier dans le presse-papiers"
+  "Unable to copy to clipboard": "Impossible de copier dans le presse-papiers",
+  "Select a radius": "Select a radius",
+  "{count} km": "{count} km",
+  "City or region": "Ville ou région",
+  "Your city or region and the radius will only be used to suggest you events nearby.": "Votre ville ou région et le rayon seront uniquement utilisé pour vous suggérer des événements proches.",
+  "Your upcoming events": "Vos événements à venir",
+  "Last published events": "Derniers événements publiés",
+  "On {instance}": "Sur {instance}",
+  "Close events": "Événements proches",
+  "Within {number} kilometers of {place}": "|Dans un rayon d'un kilomètre de {place}|Dans un rayon de {number} kilomètres de {place}"
 }
diff --git a/js/src/types/address.model.ts b/js/src/types/address.model.ts
index 7ba83b53d..c1cee4738 100644
--- a/js/src/types/address.model.ts
+++ b/js/src/types/address.model.ts
@@ -66,9 +66,9 @@ export class Address implements IAddress {
     let alternativeName = "";
     let poiIcon: IPOIIcon = poiIcons.default;
     // Google Maps doesn't have a type
-    if (this.type == null && this.description === this.street)
+    if (this.type == null && this.description === this.street) {
       this.type = "house";
-
+    }
     switch (this.type) {
       case "house":
         name = this.description;
@@ -123,6 +123,9 @@ export class Address implements IAddress {
     if (name && alternativeName) {
       return `${name}, ${alternativeName}`;
     }
+    if (name) {
+      return name;
+    }
     return "";
   }
 
diff --git a/js/src/types/current-user.model.ts b/js/src/types/current-user.model.ts
index 2c834c552..051b31f7f 100644
--- a/js/src/types/current-user.model.ts
+++ b/js/src/types/current-user.model.ts
@@ -12,13 +12,20 @@ export interface ICurrentUser {
   defaultActor?: IPerson;
 }
 
+export interface IUserPreferredLocation {
+  range?: number;
+  name?: string;
+  geohash?: string;
+}
+
 export interface IUserSettings {
-  timezone: string;
-  notificationOnDay: boolean;
-  notificationEachWeek: boolean;
-  notificationBeforeEvent: boolean;
-  notificationPendingParticipation: INotificationPendingEnum;
-  notificationPendingMembership: INotificationPendingEnum;
+  timezone?: string;
+  notificationOnDay?: boolean;
+  notificationEachWeek?: boolean;
+  notificationBeforeEvent?: boolean;
+  notificationPendingParticipation?: INotificationPendingEnum;
+  notificationPendingMembership?: INotificationPendingEnum;
+  location?: IUserPreferredLocation;
 }
 
 export interface IUser extends ICurrentUser {
diff --git a/js/src/types/enums.ts b/js/src/types/enums.ts
index 18c5d7da6..80f36a31e 100644
--- a/js/src/types/enums.ts
+++ b/js/src/types/enums.ts
@@ -178,3 +178,7 @@ export enum GroupVisibility {
   UNLISTED = "UNLISTED",
   PRIVATE = "PRIVATE",
 }
+
+export enum AddressSearchType {
+  ADMINISTRATIVE = "ADMINISTRATIVE",
+}
diff --git a/js/src/views/Home.vue b/js/src/views/Home.vue
index 1a8a27520..d5ba7ae83 100644
--- a/js/src/views/Home.vue
+++ b/js/src/views/Home.vue
@@ -47,20 +47,23 @@
       </div>
     </section>
     <div
-      id="featured_events"
+      id="recent_events"
       class="container section"
       v-if="config && (!currentUser.id || !currentActor.id)"
     >
-      <section class="events-featured">
-        <h2 class="title">{{ $t("Featured events") }}</h2>
+      <section class="events-recent">
+        <h2 class="is-size-2 has-text-weight-bold">
+          {{ $t("Last published events") }}
+        </h2>
+        <p>
+          {{ $t("On {instance}", { instance: config.name }) }}
+          <b-loading :active.sync="$apollo.loading" />
+        </p>
         <b-loading :active.sync="$apollo.loading" />
-        <div
-          v-if="filteredFeaturedEvents.length > 0"
-          class="columns is-multiline"
-        >
+        <div v-if="this.events.total > 0" class="columns is-multiline">
           <div
             class="column is-one-third-desktop"
-            v-for="event in filteredFeaturedEvents.slice(0, 6)"
+            v-for="event in this.events.elements.slice(0, 6)"
             :key="event.uuid"
           >
             <EventCard :event="event" />
@@ -185,11 +188,12 @@
           })
         }}</b-message>
       </section>
+      <!-- Your upcoming events -->
       <section
         v-if="currentActor.id && goingToEvents.size > 0"
         class="container"
       >
-        <h3 class="title">{{ $t("Upcoming") }}</h3>
+        <h3 class="title">{{ $t("Your upcoming events") }}</h3>
         <b-loading :active.sync="$apollo.loading" />
         <div v-for="row of goingToEvents" class="upcoming-events" :key="row[0]">
           <span
@@ -231,6 +235,7 @@
           >
         </span>
       </section>
+      <!-- Last week events -->
       <section v-if="currentActor && lastWeekEvents.length > 0">
         <h3 class="title">{{ $t("Last week") }}</h3>
         <b-loading :active.sync="$apollo.loading" />
@@ -244,19 +249,59 @@
           />
         </div>
       </section>
-      <section class="events-featured">
-        <h2 class="title">{{ $t("Featured events") }}</h2>
-        <b-loading :active.sync="$apollo.loading" />
-        <div
-          v-if="filteredFeaturedEvents.length > 0"
-          class="columns is-multiline"
-        >
+      <!-- Events close to you -->
+      <section class="events-close" v-if="closeEvents.total > 0">
+        <h2 class="is-size-2 has-text-weight-bold">
+          {{ $t("Close events") }}
+        </h2>
+        <p>
+          {{
+            $tc(
+              "Within {number} kilometers of {place}",
+              loggedUser.settings.location.radius,
+              {
+                number: loggedUser.settings.location.radius,
+                place: loggedUser.settings.location.name,
+              }
+            )
+          }}
+          <router-link :to="{ name: RouteName.PREFERENCES }">
+            <b-icon
+              class="clickable"
+              icon="pencil"
+              :title="$t('Change')"
+              size="is-small"
+            />
+          </router-link>
+          <b-loading :active.sync="$apollo.loading" />
+        </p>
+        <div class="columns is-multiline">
           <div
             class="column is-one-third-desktop"
-            v-for="event in filteredFeaturedEvents.slice(0, 6)"
+            v-for="event in closeEvents.elements.slice(0, 3)"
             :key="event.uuid"
           >
-            <EventCard :event="event" />
+            <event-card :event="event" />
+          </div>
+        </div>
+      </section>
+      <hr class="home-separator" />
+      <section class="events-recent">
+        <h2 class="is-size-2 has-text-weight-bold">
+          {{ $t("Last published events") }}
+        </h2>
+        <p>
+          {{ $t("On {instance}", { instance: config.name }) }}
+          <b-loading :active.sync="$apollo.loading" />
+        </p>
+
+        <div v-if="this.events.total > 0" class="columns is-multiline">
+          <div
+            class="column is-one-third-desktop"
+            v-for="event in this.events.elements.slice(0, 6)"
+            :key="event.uuid"
+          >
+            <recent-event-card-wrapper :event="event" />
           </div>
         </div>
         <b-message v-else type="is-danger"
@@ -279,9 +324,10 @@ import { ParticipantRole } from "@/types/enums";
 import { Paginate } from "@/types/paginate";
 import { supportsWebPFormat } from "@/utils/support";
 import { IParticipant, Participant } from "../types/participant.model";
-import { FETCH_EVENTS } from "../graphql/event";
+import { CLOSE_EVENTS, FETCH_EVENTS } from "../graphql/event";
 import EventListCard from "../components/Event/EventListCard.vue";
 import EventCard from "../components/Event/EventCard.vue";
+import RecentEventCardWrapper from "../components/Event/RecentEventCardWrapper.vue";
 import {
   CURRENT_ACTOR_CLIENT,
   LOGGED_USER_PARTICIPATIONS,
@@ -330,7 +376,24 @@ import Subtitle from "../components/Utils/Subtitle.vue";
           (participation: IParticipant) => new Participant(participation)
         ),
       skip() {
-        return this.currentUser.isLoggedIn === false;
+        return this.currentUser?.isLoggedIn === false;
+      },
+    },
+    closeEvents: {
+      query: CLOSE_EVENTS,
+      variables() {
+        return {
+          location: this.loggedUser?.settings?.location?.geohash,
+          radius: this.loggedUser?.settings?.location?.radius,
+        };
+      },
+      update: (data) => data.searchEvents,
+      skip() {
+        return (
+          this.currentUser?.isLoggedIn === false &&
+          this.loggedUser?.settings?.location?.geohash &&
+          this.loggedUser?.settings?.location?.radius
+        );
       },
     },
   },
@@ -339,6 +402,7 @@ import Subtitle from "../components/Utils/Subtitle.vue";
     DateComponent,
     EventListCard,
     EventCard,
+    RecentEventCardWrapper,
     "settings-onboard": () => import("./User/SettingsOnboard.vue"),
   },
   metaInfo() {
@@ -364,7 +428,7 @@ export default class Home extends Vue {
 
   country = { name: null };
 
-  currentUser!: ICurrentUser;
+  currentUser!: IUser;
 
   loggedUser!: ICurrentUser;
 
@@ -378,6 +442,8 @@ export default class Home extends Vue {
 
   supportsWebPFormat = supportsWebPFormat;
 
+  closeEvents: Paginate<IEvent> = { elements: [], total: 0 };
+
   // get displayed_name() {
   //   return this.loggedPerson && this.loggedPerson.name === null
   //     ? this.loggedPerson.preferredUsername
@@ -499,21 +565,6 @@ export default class Home extends Vue {
     return res;
   }
 
-  /**
-   * Return all events from server excluding the ones shown as participating
-   */
-  get filteredFeaturedEvents(): IEvent[] {
-    return this.events.elements.filter(
-      ({ id }) =>
-        !this.thisWeekGoingToEvents
-          .filter(
-            (participation) => participation.role === ParticipantRole.CREATOR
-          )
-          .map(({ event: { id: eventId } }) => eventId)
-          .includes(id)
-    );
-  }
-
   eventDeleted(eventid: string): void {
     this.currentUserParticipations = this.currentUserParticipations.filter(
       (participation) => participation.event.id !== eventid
@@ -549,7 +600,7 @@ main > div > .container {
   color: rgba(0, 0, 0, 0.87);
 }
 
-.events-featured {
+.events-recent {
   & > h3 {
     padding-left: 0.75rem;
   }
@@ -620,7 +671,7 @@ section.hero {
   }
 }
 
-#featured_events {
+#recent_events {
   padding: 1rem 0;
   min-height: calc(100vh - 400px);
   z-index: 10;
@@ -686,4 +737,12 @@ section.hero {
 #homepage {
   background: $white;
 }
+
+.home-separator {
+  background-color: $orange-2;
+}
+
+.clickable {
+  cursor: pointer;
+}
 </style>
diff --git a/js/src/views/Search.vue b/js/src/views/Search.vue
index 765c96899..143fad907 100644
--- a/js/src/views/Search.vue
+++ b/js/src/views/Search.vue
@@ -409,7 +409,7 @@ export default class Search extends Vue {
   }
 
   get geohash(): string | undefined {
-    if (this.location && this.location.geom) {
+    if (this.location?.geom) {
       const [lon, lat] = this.location.geom.split(";");
       return ngeohash.encode(lat, lon, 6);
     }
diff --git a/js/src/views/Settings/Notifications.vue b/js/src/views/Settings/Notifications.vue
index aed37cf67..c204471a9 100644
--- a/js/src/views/Settings/Notifications.vue
+++ b/js/src/views/Settings/Notifications.vue
@@ -135,13 +135,14 @@ import RouteName from "../../router/name";
 export default class Notifications extends Vue {
   loggedUser!: IUser;
 
-  notificationOnDay = true;
+  notificationOnDay: boolean | undefined = true;
 
-  notificationEachWeek = false;
+  notificationEachWeek: boolean | undefined = false;
 
-  notificationBeforeEvent = false;
+  notificationBeforeEvent: boolean | undefined = false;
 
-  notificationPendingParticipation = INotificationPendingEnum.NONE;
+  notificationPendingParticipation: INotificationPendingEnum | undefined =
+    INotificationPendingEnum.NONE;
 
   notificationPendingParticipationValues: Record<string, unknown> = {};
 
diff --git a/js/src/views/Settings/Preferences.vue b/js/src/views/Settings/Preferences.vue
index 925243ea0..4cc9ff690 100644
--- a/js/src/views/Settings/Preferences.vue
+++ b/js/src/views/Settings/Preferences.vue
@@ -26,7 +26,7 @@
           </option>
         </b-select>
       </b-field>
-      <b-field :label="$t('Timezone')">
+      <b-field :label="$t('Timezone')" v-if="selectedTimezone">
         <b-select
           :placeholder="$t('Select a timezone')"
           :loading="!config || !loggedUser"
@@ -55,11 +55,46 @@
       <b-message v-else type="is-danger">{{
         $t("Unable to detect timezone.")
       }}</b-message>
+      <hr />
+      <b-field grouped>
+        <b-field :label="$t('City or region')" expanded>
+          <address-auto-complete
+            v-if="
+              loggedUser && loggedUser.settings && loggedUser.settings.location
+            "
+            :type="AddressSearchType.ADMINISTRATIVE"
+            v-model="address"
+          >
+          </address-auto-complete>
+        </b-field>
+        <b-field :label="$t('Radius')">
+          <b-select
+            :placeholder="$t('Select a radius')"
+            v-model="locationRange"
+          >
+            <option
+              v-for="index in [1, 5, 10, 25, 50, 100]"
+              :key="index"
+              :value="index"
+            >
+              {{ $tc("{count} km", index, { count: index }) }}
+            </option>
+          </b-select>
+        </b-field>
+      </b-field>
+      <p>
+        {{
+          $t(
+            "Your city or region and the radius will only be used to suggest you events nearby."
+          )
+        }}
+      </p>
     </div>
   </div>
 </template>
 <script lang="ts">
 import { Component, Vue, Watch } from "vue-property-decorator";
+import ngeohash from "ngeohash";
 import { saveLocaleData } from "@/utils/auth";
 import { TIMEZONES } from "../../graphql/config";
 import {
@@ -68,22 +103,28 @@ import {
   UPDATE_USER_LOCALE,
 } from "../../graphql/user";
 import { IConfig } from "../../types/config.model";
-import { IUser } from "../../types/current-user.model";
+import { IUser, IUserSettings } from "../../types/current-user.model";
 import langs from "../../i18n/langs.json";
 import RouteName from "../../router/name";
+import AddressAutoComplete from "../../components/Event/AddressAutoComplete.vue";
+import { AddressSearchType } from "@/types/enums";
+import { Address, IAddress } from "@/types/address.model";
 
 @Component({
   apollo: {
     config: TIMEZONES,
     loggedUser: USER_SETTINGS,
   },
+  components: {
+    AddressAutoComplete,
+  },
 })
 export default class Preferences extends Vue {
   config!: IConfig;
 
   loggedUser!: IUser;
 
-  selectedTimezone: string | null = null;
+  selectedTimezone: string | undefined = undefined;
 
   locale: string | null = null;
 
@@ -91,14 +132,16 @@ export default class Preferences extends Vue {
 
   langs: Record<string, string> = langs;
 
+  AddressSearchType = AddressSearchType;
+
   @Watch("loggedUser")
   setSavedTimezone(loggedUser: IUser): void {
-    if (loggedUser && loggedUser.settings.timezone) {
+    if (loggedUser?.settings?.timezone) {
       this.selectedTimezone = loggedUser.settings.timezone;
     } else {
       this.selectedTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
     }
-    if (loggedUser && loggedUser.locale) {
+    if (loggedUser?.locale) {
       this.locale = loggedUser.locale;
     } else {
       this.locale = this.$i18n.locale;
@@ -145,12 +188,7 @@ export default class Preferences extends Vue {
   @Watch("selectedTimezone")
   async updateTimezone(): Promise<void> {
     if (this.selectedTimezone !== this.loggedUser.settings.timezone) {
-      await this.$apollo.mutate<{ setUserSetting: string }>({
-        mutation: SET_USER_SETTINGS,
-        variables: {
-          timezone: this.selectedTimezone,
-        },
-      });
+      this.updateUserSettings({ timezone: this.selectedTimezone });
     }
   }
 
@@ -166,5 +204,67 @@ export default class Preferences extends Vue {
       saveLocaleData(this.locale);
     }
   }
+
+  get address(): IAddress | null {
+    if (
+      this.loggedUser?.settings?.location?.name &&
+      this.loggedUser?.settings?.location?.geohash
+    ) {
+      const { latitude, longitude } = ngeohash.decode(
+        this.loggedUser?.settings?.location?.geohash
+      );
+      const name = this.loggedUser?.settings?.location?.name;
+      return {
+        description: name,
+        locality: "",
+        type: "administrative",
+        geom: `${longitude};${latitude}`,
+        street: "",
+        postalCode: "",
+        region: "",
+        country: "",
+      };
+    }
+    return null;
+  }
+
+  set address(address: IAddress | null) {
+    if (address && address.geom) {
+      const { geom } = address;
+      const addressObject = new Address(address);
+      const queryText = addressObject.poiInfos.name;
+      const [lon, lat] = geom.split(";");
+      const geohash = ngeohash.encode(lat, lon, 6);
+      if (queryText && geom) {
+        this.updateUserSettings({
+          location: {
+            geohash,
+            name: queryText,
+          },
+        });
+      }
+    }
+  }
+
+  get locationRange(): number | undefined {
+    return this.loggedUser?.settings?.location?.range;
+  }
+
+  set locationRange(locationRange: number | undefined) {
+    if (locationRange) {
+      this.updateUserSettings({
+        location: {
+          range: locationRange,
+        },
+      });
+    }
+  }
+
+  private async updateUserSettings(userSettings: IUserSettings) {
+    await this.$apollo.mutate<{ setUserSetting: string }>({
+      mutation: SET_USER_SETTINGS,
+      variables: userSettings,
+    });
+  }
 }
 </script>
diff --git a/lib/graphql/resolvers/address.ex b/lib/graphql/resolvers/address.ex
index f2d5fc402..a2476a8dd 100644
--- a/lib/graphql/resolvers/address.ex
+++ b/lib/graphql/resolvers/address.ex
@@ -14,7 +14,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Address do
   @spec search(map, map, map) :: {:ok, [Address.t()]}
   def search(
         _parent,
-        %{query: query, locale: locale, page: _page, limit: _limit},
+        %{query: query, locale: locale, page: _page, limit: _limit} = args,
         %{context: %{ip: ip}}
       ) do
     geolix = Geolix.lookup(ip)
@@ -25,7 +25,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Address do
         _ -> nil
       end
 
-    addresses = Geospatial.service().search(query, lang: locale, country_code: country_code)
+    addresses =
+      Geospatial.service().search(query,
+        lang: locale,
+        country_code: country_code,
+        type: Map.get(args, :type)
+      )
 
     {:ok, addresses}
   end
diff --git a/lib/graphql/schema/address.ex b/lib/graphql/schema/address.ex
index aba803ee6..bb1c1f622 100644
--- a/lib/graphql/schema/address.ex
+++ b/lib/graphql/schema/address.ex
@@ -56,6 +56,15 @@ defmodule Mobilizon.GraphQL.Schema.AddressType do
     field(:origin_id, :string, description: "The address's original ID from the provider")
   end
 
+  @desc """
+  A list of possible values for the type option to search an address.
+
+  Results may vary depending on the geocoding provider.
+  """
+  enum :address_search_type do
+    value(:administrative, description: "Administrative results (cities, regions,...)")
+  end
+
   object :address_queries do
     @desc "Search for an address"
     field :search_address, type: list_of(:address) do
@@ -73,6 +82,8 @@ defmodule Mobilizon.GraphQL.Schema.AddressType do
 
       arg(:limit, :integer, default_value: 10, description: "The limit of search results per page")
 
+      arg(:type, :address_search_type, description: "Filter by type of results")
+
       resolve(&Address.search/3)
     end
 
diff --git a/lib/graphql/schema/event.ex b/lib/graphql/schema/event.ex
index b10a3b2ca..e74760b8f 100644
--- a/lib/graphql/schema/event.ex
+++ b/lib/graphql/schema/event.ex
@@ -101,7 +101,7 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
     # field(:sessions, list_of(:session))
 
     field(:updated_at, :datetime, description: "When the event was last updated")
-    field(:created_at, :datetime, description: "When the event was created")
+    field(:inserted_at, :datetime, description: "When the event was created")
     field(:options, :event_options, description: "The event options")
   end
 
diff --git a/lib/graphql/schema/user.ex b/lib/graphql/schema/user.ex
index 10ad416ca..af62f798b 100644
--- a/lib/graphql/schema/user.ex
+++ b/lib/graphql/schema/user.ex
@@ -177,6 +177,10 @@ defmodule Mobilizon.GraphQL.Schema.UserType do
       description:
         "When does the user receives a notification about a new pending membership in one of the group they're admin for"
     )
+
+    field(:location, :location,
+      description: "The user's preferred location, where they want to be suggested events"
+    )
   end
 
   @desc "The list of values the for pending notification settings"
@@ -199,6 +203,25 @@ defmodule Mobilizon.GraphQL.Schema.UserType do
     )
   end
 
+  object :location do
+    field(:range, :integer, description: "The range in kilometers the user wants to see events")
+
+    field(:geohash, :string, description: "A geohash representing the user's preferred location")
+
+    field(:name, :string, description: "A string describing the user's preferred  location")
+  end
+
+  @desc """
+  The set of parameters needed to input a location
+  """
+  input_object :location_input do
+    field(:range, :integer, description: "The range in kilometers the user wants to see events")
+
+    field(:geohash, :string, description: "A geohash representing the user's preferred location")
+
+    field(:name, :string, description: "A string describing the user's preferred  location")
+  end
+
   object :user_queries do
     @desc "Get an user"
     field :user, :user do
@@ -343,6 +366,10 @@ defmodule Mobilizon.GraphQL.Schema.UserType do
           "When does the user receives a notification about a new pending membership in one of the group they're admin for"
       )
 
+      arg(:location, :location_input,
+        description: "A geohash of the user's preferred location, where they want to see events"
+      )
+
       resolve(&User.set_user_setting/3)
     end
 
diff --git a/lib/mobilizon/users/setting.ex b/lib/mobilizon/users/setting.ex
index 9d20f5d39..38d762818 100644
--- a/lib/mobilizon/users/setting.ex
+++ b/lib/mobilizon/users/setting.ex
@@ -30,6 +30,8 @@ defmodule Mobilizon.Users.Setting do
 
   @attrs @required_attrs ++ @optional_attrs
 
+  @location_attrs [:name, :range, :geohash]
+
   @primary_key {:user_id, :id, autogenerate: false}
   schema "user_settings" do
     field(:timezone, :string)
@@ -45,6 +47,12 @@ defmodule Mobilizon.Users.Setting do
       default: :one_day
     )
 
+    embeds_one :location, Location, on_replace: :update, primary_key: false do
+      field(:name, :string)
+      field(:range, :integer)
+      field(:geohash, :string)
+    end
+
     belongs_to(:user, User, primary_key: true, type: :id, foreign_key: :id, define_field: false)
 
     timestamps()
@@ -54,6 +62,12 @@ defmodule Mobilizon.Users.Setting do
   def changeset(setting, attrs) do
     setting
     |> cast(attrs, @attrs)
+    |> cast_embed(:location, with: &location_changeset/2)
     |> validate_required(@required_attrs)
   end
+
+  def location_changeset(schema, params) do
+    schema
+    |> cast(params, @location_attrs)
+  end
 end
diff --git a/lib/service/geospatial/addok.ex b/lib/service/geospatial/addok.ex
index 95d38e4ae..908125e8f 100644
--- a/lib/service/geospatial/addok.ex
+++ b/lib/service/geospatial/addok.ex
@@ -40,7 +40,6 @@ defmodule Mobilizon.Service.Geospatial.Addok do
   @spec build_url(atom(), map(), list()) :: String.t()
   defp build_url(method, args, options) do
     limit = Keyword.get(options, :limit, 10)
-    coords = Keyword.get(options, :coords, nil)
     endpoint = Keyword.get(options, :endpoint, @endpoint)
 
     case method do
@@ -48,8 +47,9 @@ defmodule Mobilizon.Service.Geospatial.Addok do
         "#{endpoint}/reverse/?lon=#{args.lon}&lat=#{args.lat}&limit=#{limit}"
 
       :search ->
-        url = "#{endpoint}/search/?q=#{URI.encode(args.q)}&limit=#{limit}"
-        if is_nil(coords), do: url, else: url <> "&lat=#{coords.lat}&lon=#{coords.lon}"
+        "#{endpoint}/search/?q=#{URI.encode(args.q)}&limit=#{limit}"
+        |> add_parameter(options, :country_code)
+        |> add_parameter(options, :type)
     end
   end
 
@@ -89,4 +89,20 @@ defmodule Mobilizon.Service.Geospatial.Addok do
       Map.get(properties, "street")
     end
   end
+
+  @spec add_parameter(String.t(), Keyword.t(), atom()) :: String.t()
+  defp add_parameter(url, options, key) do
+    value = Keyword.get(options, key)
+
+    if is_nil(value), do: url, else: do_add_parameter(url, key, value)
+  end
+
+  @spec do_add_parameter(String.t(), atom(), any()) :: String.t()
+  defp do_add_parameter(url, :coords, coords),
+    do: "#{url}&lat=#{coords.lat}&lon=#{coords.lon}"
+
+  defp do_add_parameter(url, :type, :administrative),
+    do: "#{url}&type=municipality"
+
+  defp do_add_parameter(url, :type, _type), do: url
 end
diff --git a/lib/service/geospatial/google_maps.ex b/lib/service/geospatial/google_maps.ex
index e5c6bc5e4..63f61729f 100644
--- a/lib/service/geospatial/google_maps.ex
+++ b/lib/service/geospatial/google_maps.ex
@@ -29,6 +29,9 @@ defmodule Mobilizon.Service.Geospatial.GoogleMaps do
 
   @api_key_missing_message "API Key required to use Google Maps"
 
+  @geocode_endpoint "https://maps.googleapis.com/maps/api/geocode/json"
+  @details_endpoint "https://maps.googleapis.com/maps/api/place/details/json"
+
   @impl Provider
   @doc """
   Google Maps implementation for `c:Mobilizon.Service.Geospatial.Provider.geocode/3`.
@@ -77,15 +80,13 @@ defmodule Mobilizon.Service.Geospatial.GoogleMaps do
     api_key = Keyword.get(options, :api_key, @api_key)
     if is_nil(api_key), do: raise(ArgumentError, message: @api_key_missing_message)
 
-    url =
-      "https://maps.googleapis.com/maps/api/geocode/json?limit=#{limit}&key=#{api_key}&language=#{
-        lang
-      }"
+    url = "#{@geocode_endpoint}?limit=#{limit}&key=#{api_key}&language=#{lang}"
 
     uri =
       case method do
         :search ->
-          url <> "&address=#{args.q}"
+          "#{url}&address=#{args.q}"
+          |> add_parameter(options, :type)
 
         :geocode ->
           zoom = Keyword.get(options, :zoom, 15)
@@ -95,9 +96,7 @@ defmodule Mobilizon.Service.Geospatial.GoogleMaps do
           url <> "&latlng=#{args.lat},#{args.lon}&result_type=#{result_type}"
 
         :place_details ->
-          "https://maps.googleapis.com/maps/api/place/details/json?key=#{api_key}&placeid=#{
-            args.place_id
-          }"
+          "#{@details_endpoint}?key=#{api_key}&placeid=#{args.place_id}"
       end
 
     URI.encode(uri)
@@ -173,4 +172,17 @@ defmodule Mobilizon.Service.Geospatial.GoogleMaps do
         nil
     end
   end
+
+  @spec add_parameter(String.t(), Keyword.t(), atom()) :: String.t()
+  defp add_parameter(url, options, key, default \\ nil) do
+    value = Keyword.get(options, key, default)
+
+    if is_nil(value), do: url, else: do_add_parameter(url, key, value)
+  end
+
+  @spec do_add_parameter(String.t(), atom(), any()) :: String.t()
+  defp do_add_parameter(url, :type, :administrative),
+    do: "#{url}&components=administrative_area"
+
+  defp do_add_parameter(url, :type, _), do: url
 end
diff --git a/lib/service/geospatial/mimirsbrunn.ex b/lib/service/geospatial/mimirsbrunn.ex
index 01aa191b8..c469f16f8 100644
--- a/lib/service/geospatial/mimirsbrunn.ex
+++ b/lib/service/geospatial/mimirsbrunn.ex
@@ -43,13 +43,13 @@ defmodule Mobilizon.Service.Geospatial.Mimirsbrunn do
   defp build_url(method, args, options) do
     limit = Keyword.get(options, :limit, 10)
     lang = Keyword.get(options, :lang, "en")
-    coords = Keyword.get(options, :coords, nil)
     endpoint = Keyword.get(options, :endpoint, @endpoint)
 
     case method do
       :search ->
-        url = "#{endpoint}/autocomplete?q=#{URI.encode(args.q)}&lang=#{lang}&limit=#{limit}"
-        if is_nil(coords), do: url, else: url <> "&lat=#{coords.lat}&lon=#{coords.lon}"
+        "#{endpoint}/autocomplete?q=#{URI.encode(args.q)}&lang=#{lang}&limit=#{limit}"
+        |> add_parameter(options, :coords)
+        |> add_parameter(options, :type)
 
       :geocode ->
         "#{endpoint}/reverse?lon=#{args.lon}&lat=#{args.lat}"
@@ -143,4 +143,20 @@ defmodule Mobilizon.Service.Geospatial.Mimirsbrunn do
 
   defp get_postal_code(%{"postcode" => nil}), do: nil
   defp get_postal_code(%{"postcode" => postcode}), do: postcode |> String.split(";") |> hd()
+
+  @spec add_parameter(String.t(), Keyword.t(), atom()) :: String.t()
+  defp add_parameter(url, options, key) do
+    value = Keyword.get(options, key)
+
+    if is_nil(value), do: url, else: do_add_parameter(url, key, value)
+  end
+
+  @spec do_add_parameter(String.t(), atom(), any()) :: String.t()
+  defp do_add_parameter(url, :coords, coords),
+    do: "#{url}&lat=#{coords.lat}&lon=#{coords.lon}"
+
+  defp do_add_parameter(url, :type, :administrative),
+    do: "#{url}&type=zone"
+
+  defp do_add_parameter(url, :type, _type), do: url
 end
diff --git a/lib/service/geospatial/nominatim.ex b/lib/service/geospatial/nominatim.ex
index 9e94f8367..e42e4eef2 100644
--- a/lib/service/geospatial/nominatim.ex
+++ b/lib/service/geospatial/nominatim.ex
@@ -41,9 +41,6 @@ defmodule Mobilizon.Service.Geospatial.Nominatim do
     limit = Keyword.get(options, :limit, 10)
     lang = Keyword.get(options, :lang, "en")
     endpoint = Keyword.get(options, :endpoint, @endpoint)
-    country_code = Keyword.get(options, :country_code)
-    zoom = Keyword.get(options, :zoom)
-    api_key = Keyword.get(options, :api_key, @api_key)
 
     url =
       case method do
@@ -53,16 +50,15 @@ defmodule Mobilizon.Service.Geospatial.Nominatim do
           }&addressdetails=1&namedetails=1"
 
         :geocode ->
-          url =
-            "#{endpoint}/reverse?format=geocodejson&lat=#{args.lat}&lon=#{args.lon}&accept-language=#{
-              lang
-            }&addressdetails=1&namedetails=1"
-
-          if is_nil(zoom), do: url, else: url <> "&zoom=#{zoom}"
+          "#{endpoint}/reverse?format=geocodejson&lat=#{args.lat}&lon=#{args.lon}&accept-language=#{
+            lang
+          }&addressdetails=1&namedetails=1"
+          |> add_parameter(options, :zoom)
       end
 
-    url = if is_nil(country_code), do: url, else: "#{url}&countrycodes=#{country_code}"
-    if is_nil(api_key), do: url, else: url <> "&key=#{api_key}"
+    url
+    |> add_parameter(options, :country_code)
+    |> add_parameter(options, :api_key, @api_key)
   end
 
   @spec fetch_features(String.t()) :: list(Address.t())
@@ -146,4 +142,23 @@ defmodule Mobilizon.Service.Geospatial.Nominatim do
       description
     end
   end
+
+  @spec add_parameter(String.t(), Keyword.t(), atom()) :: String.t()
+  defp add_parameter(url, options, key, default \\ nil) do
+    value = Keyword.get(options, key, default)
+
+    if is_nil(value), do: url, else: do_add_parameter(url, key, value)
+  end
+
+  @spec do_add_parameter(String.t(), atom(), any()) :: String.t()
+  defp do_add_parameter(url, :zoom, zoom),
+    do: "#{url}&zoom=#{zoom}"
+
+  @spec do_add_parameter(String.t(), atom(), any()) :: String.t()
+  defp do_add_parameter(url, :country_code, country_code),
+    do: "#{url}&countrycodes=#{country_code}"
+
+  @spec do_add_parameter(String.t(), atom(), any()) :: String.t()
+  defp do_add_parameter(url, :api_key, api_key),
+    do: "#{url}&key=#{api_key}"
 end
diff --git a/lib/service/geospatial/pelias.ex b/lib/service/geospatial/pelias.ex
index f9d82797a..35fd2b5bc 100644
--- a/lib/service/geospatial/pelias.ex
+++ b/lib/service/geospatial/pelias.ex
@@ -8,7 +8,6 @@ defmodule Mobilizon.Service.Geospatial.Pelias do
   alias Mobilizon.Addresses.Address
   alias Mobilizon.Service.Geospatial.Provider
   alias Mobilizon.Service.HTTP.GeospatialClient
-
   require Logger
 
   @behaviour Provider
@@ -41,25 +40,20 @@ defmodule Mobilizon.Service.Geospatial.Pelias do
   defp build_url(method, args, options) do
     limit = Keyword.get(options, :limit, 10)
     lang = Keyword.get(options, :lang, "en")
-    coords = Keyword.get(options, :coords, nil)
     endpoint = Keyword.get(options, :endpoint, @endpoint)
-    country_code = Keyword.get(options, :country_code)
 
     url =
       case method do
         :search ->
-          url =
-            "#{endpoint}/v1/autocomplete?text=#{URI.encode(args.q)}&lang=#{lang}&size=#{limit}"
-
-          if is_nil(coords),
-            do: url,
-            else: url <> "&focus.point.lat=#{coords.lat}&focus.point.lon=#{coords.lon}"
+          "#{endpoint}/v1/autocomplete?text=#{URI.encode(args.q)}&lang=#{lang}&size=#{limit}"
+          |> add_parameter(options, :coords)
+          |> add_parameter(options, :type)
 
         :geocode ->
           "#{endpoint}/v1/reverse?point.lon=#{args.lon}&point.lat=#{args.lat}"
       end
 
-    if is_nil(country_code), do: url, else: "#{url}&boundary.country=#{country_code}"
+    add_parameter(url, options, :country_code)
   end
 
   @spec fetch_features(String.t()) :: list(Address.t())
@@ -120,9 +114,31 @@ defmodule Mobilizon.Service.Geospatial.Pelias do
     "dependency"
   ]
 
+  @spec get_type(map()) :: String.t() | nil
   defp get_type(%{"layer" => layer}) when layer in @administrative_layers, do: "administrative"
   defp get_type(%{"layer" => "address"}), do: "house"
   defp get_type(%{"layer" => "street"}), do: "street"
   defp get_type(%{"layer" => "venue"}), do: "venue"
   defp get_type(%{"layer" => _}), do: nil
+
+  @spec add_parameter(String.t(), Keyword.t(), atom()) :: String.t()
+  def add_parameter(url, options, key) do
+    value = Keyword.get(options, key)
+
+    if is_nil(value), do: url, else: do_add_parameter(url, key, value)
+  end
+
+  @spec do_add_parameter(String.t(), atom(), any()) :: String.t()
+  defp do_add_parameter(url, :coords, value),
+    do: "#{url}&focus.point.lat=#{value.lat}&focus.point.lon=#{value.lon}"
+
+  defp do_add_parameter(url, :type, :administrative),
+    do: "#{url}&layers=coarse"
+
+  defp do_add_parameter(url, :type, _type), do: url
+
+  defp do_add_parameter(url, :country_code, nil), do: url
+
+  defp do_add_parameter(url, :country_code, country_code),
+    do: "#{url}&boundary.country=#{country_code}"
 end
diff --git a/lib/service/geospatial/provider.ex b/lib/service/geospatial/provider.ex
index 20afc49ce..be3193e5c 100644
--- a/lib/service/geospatial/provider.ex
+++ b/lib/service/geospatial/provider.ex
@@ -54,6 +54,8 @@ defmodule Mobilizon.Service.Geospatial.Provider do
 
   * `coords` Map of coordinates (ex: `%{lon: 48.11, lat: -1.77}`) allowing to
   give a geographic priority to the search. Defaults to `nil`.
+  * `type` Filter by type of results. Allowed values:
+     * `:administrative` (cities, regions)
 
   ## Examples
 
diff --git a/priv/repo/migrations/20210210143432_add_location_settings_to_user.exs b/priv/repo/migrations/20210210143432_add_location_settings_to_user.exs
new file mode 100644
index 000000000..8513ce9f4
--- /dev/null
+++ b/priv/repo/migrations/20210210143432_add_location_settings_to_user.exs
@@ -0,0 +1,9 @@
+defmodule Mobilizon.Storage.Repo.Migrations.AddLocationSettingsToUser do
+  use Ecto.Migration
+
+  def change do
+    alter table(:user_settings) do
+      add(:location, :map)
+    end
+  end
+end