diff --git a/js/package.json b/js/package.json
index 97609c267..6aa5d3a05 100644
--- a/js/package.json
+++ b/js/package.json
@@ -25,6 +25,7 @@
     "buefy": "^0.8.2",
     "bulma-divider": "^0.2.0",
     "core-js": "^3.6.4",
+    "date-fns": "^2.15.0",
     "eslint-plugin-cypress": "^2.10.3",
     "graphql": "^15.0.0",
     "graphql-tag": "^2.10.3",
diff --git a/js/src/common.scss b/js/src/common.scss
index 373e1d810..db448b2f5 100644
--- a/js/src/common.scss
+++ b/js/src/common.scss
@@ -26,7 +26,7 @@ input.input {
 }
 
 .section {
-  padding: 1rem 2rem 4rem;
+  padding: 1rem 1% 4rem;
 }
 
 figure img.is-rounded {
diff --git a/js/src/components/Event/AddressAutoComplete.vue b/js/src/components/Event/AddressAutoComplete.vue
index 16e6c96df..83292ce5c 100644
--- a/js/src/components/Event/AddressAutoComplete.vue
+++ b/js/src/components/Event/AddressAutoComplete.vue
@@ -2,7 +2,7 @@
   <b-autocomplete
     :data="addressData"
     v-model="queryText"
-    :placeholder="$t('e.g. 10 Rue Jangot')"
+    :placeholder="placeholder || $t('e.g. 10 Rue Jangot')"
     field="fullName"
     :loading="isFetching"
     @typing="fetchAsyncData"
@@ -45,6 +45,7 @@ import { IConfig } from "../../types/config.model";
 })
 export default class AddressAutoComplete extends Vue {
   @Prop({ required: true }) value!: IAddress;
+  @Prop({ required: false }) placeholder!: string;
 
   addressData: IAddress[] = [];
 
diff --git a/js/src/graphql/search.ts b/js/src/graphql/search.ts
index c517879ae..6f8b516b0 100644
--- a/js/src/graphql/search.ts
+++ b/js/src/graphql/search.ts
@@ -1,8 +1,22 @@
 import gql from "graphql-tag";
 
 export const SEARCH_EVENTS = gql`
-  query SearchEvents($location: String, $radius: Float, $tags: String, $term: String) {
-    searchEvents(location: $location, radius: $radius, tags: $tags, term: $term) {
+  query SearchEvents(
+    $location: String
+    $radius: Float
+    $tags: String
+    $term: String
+    $beginsOn: DateTime
+    $endsOn: DateTime
+  ) {
+    searchEvents(
+      location: $location
+      radius: $radius
+      tags: $tags
+      term: $term
+      beginsOn: $beginsOn
+      endsOn: $endsOn
+    ) {
       total
       elements {
         title
diff --git a/js/src/i18n/en_US.json b/js/src/i18n/en_US.json
index a6e8bf5fd..398e0761a 100644
--- a/js/src/i18n/en_US.json
+++ b/js/src/i18n/en_US.json
@@ -730,5 +730,18 @@
   "Delete post": "Delete post",
   "Update post": "Update post",
   "Posts": "Posts",
-  "Register an account on {instanceName}!": "Register an account on {instanceName}!"
+  "Register an account on {instanceName}!": "Register an account on {instanceName}!",
+  "Key words": "Key words",
+  "For instance: London": "For instance: London",
+  "Radius": "Radius",
+  "Today": "Today",
+  "Tomorrow": "Tomorrow",
+  "This weekend": "This weekend",
+  "This week": "This week",
+  "Next week": "Next week",
+  "This month": "This month",
+  "Next month": "Next month",
+  "Any day": "Any day",
+  "{nb} km": "{nb} km",
+  "any distance": "any distance"
 }
diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json
index e464c0e20..7eb4b669b 100644
--- a/js/src/i18n/fr_FR.json
+++ b/js/src/i18n/fr_FR.json
@@ -730,5 +730,18 @@
   "Delete post": "Supprimer le billet",
   "Update post": "Mettre à jour le billet",
   "Posts": "Billets",
-  "Register an account on {instanceName}!": "S'inscrire sur {instanceName} !"
+  "Register an account on {instanceName}!": "S'inscrire sur {instanceName} !",
+  "Key words": "Mots clés",
+  "For instance: London": "Par exemple : Lyon",
+  "Radius": "Rayon",
+  "Today": "Aujourd'hui",
+  "Tomorrow": "Demain",
+  "This weekend": "Ce weekend",
+  "This week": "Cette semaine",
+  "Next week": "La semaine prochaine",
+  "This month": "Ce mois-ci",
+  "Next month": "Le mois-prochain",
+  "Any day": "N'importe quand",
+  "{nb} km": "{nb} km",
+  "any distance": "peu importe"
 }
diff --git a/js/src/main.ts b/js/src/main.ts
index 784e26f8f..b425b56ee 100644
--- a/js/src/main.ts
+++ b/js/src/main.ts
@@ -10,6 +10,7 @@ import TimeAgo from "javascript-time-ago";
 import App from "./App.vue";
 import router from "./router";
 import { NotifierPlugin } from "./plugins/notifier";
+import { DateFnsPlugin } from "./plugins/dateFns";
 import filters from "./filters";
 import { i18n } from "./utils/i18n";
 import messages from "./i18n";
@@ -31,6 +32,7 @@ import(`javascript-time-ago/locale/${locale}`).then((localeFile) => {
 
 Vue.use(Buefy);
 Vue.use(NotifierPlugin);
+Vue.use(DateFnsPlugin, { locale });
 Vue.use(filters);
 Vue.use(VueMeta);
 Vue.use(VueScrollTo);
diff --git a/js/src/plugins/dateFns.ts b/js/src/plugins/dateFns.ts
new file mode 100644
index 000000000..c989092f6
--- /dev/null
+++ b/js/src/plugins/dateFns.ts
@@ -0,0 +1,14 @@
+import Vue from "vue";
+import Locale from "date-fns";
+
+declare module "vue/types/vue" {
+  interface Vue {
+    $dateFnsLocale: Locale;
+  }
+}
+
+export function DateFnsPlugin(vue: typeof Vue, { locale }: { locale: string }): void {
+  import(`date-fns/locale/${locale}/index.js`).then((localeEntity) => {
+    Vue.prototype.$dateFnsLocale = localeEntity;
+  });
+}
diff --git a/js/src/router/event.ts b/js/src/router/event.ts
index 0431c9e30..e287e893a 100644
--- a/js/src/router/event.ts
+++ b/js/src/router/event.ts
@@ -8,7 +8,6 @@ const participations = () =>
 const editEvent = () => import(/* webpackChunkName: "edit-event" */ "@/views/Event/Edit.vue");
 const event = () => import(/* webpackChunkName: "event" */ "@/views/Event/Event.vue");
 const myEvents = () => import(/* webpackChunkName: "my-events" */ "@/views/Event/MyEvents.vue");
-const explore = () => import(/* webpackChunkName: "explore" */ "@/views/Event/Explore.vue");
 
 export enum EventRouteName {
   EVENT_LIST = "EventList",
@@ -43,7 +42,7 @@ export const eventRoutes: RouteConfig[] = [
   {
     path: "/events/explore",
     name: EventRouteName.EXPLORE,
-    component: explore,
+    redirect: { name: "Search" },
     meta: { requiredAuth: false },
   },
   {
diff --git a/js/src/router/index.ts b/js/src/router/index.ts
index 19091f961..1084ede7b 100644
--- a/js/src/router/index.ts
+++ b/js/src/router/index.ts
@@ -49,7 +49,7 @@ const router = new Router({
     ...discussionRoutes,
     ...errorRoutes,
     {
-      path: "/search/:searchTerm/:searchType?",
+      path: "/search/:searchTerm?/:searchType?",
       name: RouteName.SEARCH,
       component: Search,
       props: true,
diff --git a/js/src/views/Event/Explore.vue b/js/src/views/Event/Explore.vue
deleted file mode 100644
index 2c8981e17..000000000
--- a/js/src/views/Event/Explore.vue
+++ /dev/null
@@ -1,116 +0,0 @@
-<template>
-  <div class="section container">
-    <h1 class="title">{{ $t("Explore") }}</h1>
-    <section class="hero">
-      <div class="hero-body">
-        <form @submit.prevent="submit()">
-          <b-field
-            :label="$t('Event')"
-            grouped
-            group-multiline
-            label-position="on-border"
-            label-for="search"
-          >
-            <b-input
-              icon="magnify"
-              type="search"
-              id="search"
-              size="is-large"
-              expanded
-              v-model="searchTerm"
-              :placeholder="$t('For instance: London, Taekwondo, Architecture…')"
-            />
-            <p class="control">
-              <b-button
-                @click="submit"
-                type="is-info"
-                size="is-large"
-                v-bind:disabled="searchTerm.trim().length === 0"
-                >{{ $t("Search") }}</b-button
-              >
-            </p>
-          </b-field>
-        </form>
-      </div>
-    </section>
-    <section class="events-featured">
-      <b-loading :active.sync="$apollo.loading"></b-loading>
-      <h2 class="title">{{ $t("Featured events") }}</h2>
-      <div v-if="events.length > 0" class="columns is-multiline">
-        <div class="column is-one-third-desktop" v-for="event in events" :key="event.uuid">
-          <EventCard :event="event" />
-        </div>
-      </div>
-      <b-message v-else-if="events.length === 0 && $apollo.loading === false" type="is-danger">{{
-        $t("No events found")
-      }}</b-message>
-    </section>
-  </div>
-</template>
-
-<script lang="ts">
-import { Component, Vue } from "vue-property-decorator";
-import EventCard from "@/components/Event/EventCard.vue";
-import { FETCH_EVENTS } from "@/graphql/event";
-import { IEvent } from "@/types/event.model";
-import RouteName from "../../router/name";
-
-@Component({
-  components: {
-    EventCard,
-  },
-  apollo: {
-    events: {
-      query: FETCH_EVENTS,
-    },
-  },
-  metaInfo() {
-    return {
-      // if no subcomponents specify a metaInfo.title, this title will be used
-      title: this.$t("Explore") as string,
-      // all titles will be injected into this template
-      titleTemplate: "%s | Mobilizon",
-    };
-  },
-})
-export default class Explore extends Vue {
-  events: IEvent[] = [];
-
-  searchTerm = "";
-
-  submit() {
-    this.$router.push({
-      name: RouteName.SEARCH,
-      params: { searchTerm: this.searchTerm },
-    });
-  }
-}
-</script>
-
-<style scoped lang="scss">
-@import "@/variables.scss";
-
-main > .container {
-  background: $white;
-
-  .hero-body {
-    padding: 1rem 1.5rem;
-  }
-}
-
-h1.title {
-  margin-top: 1.5rem;
-}
-
-h3.title {
-  margin-bottom: 1.5rem;
-}
-
-.events-featured {
-  margin: 25px auto;
-
-  .columns {
-    margin: 1rem auto 3rem;
-  }
-}
-</style>
diff --git a/js/src/views/Search.vue b/js/src/views/Search.vue
index 8b5af747d..3a10508c4 100644
--- a/js/src/views/Search.vue
+++ b/js/src/views/Search.vue
@@ -1,16 +1,62 @@
 <template>
-  <section class="container">
-    <form @submit.prevent="processSearch" v-if="!actualTag">
-      <b-field :label="$t('Event')">
-        <b-input size="is-large" v-model="search" />
-      </b-field>
-      <b-field :label="$t('Location')">
-        <address-auto-complete v-model="location" />
-      </b-field>
-      <b-button native-type="submit">{{ $t("Go") }}</b-button>
-    </form>
-    <b-loading :active.sync="$apollo.loading" />
-    <b-tabs v-model="activeTab" type="is-boxed" class="searchTabs" @change="changeTab">
+  <div class="section container">
+    <h1 class="title">{{ $t("Explore") }}</h1>
+    <section class="hero is-light">
+      <div class="hero-body">
+        <form @submit.prevent="submit()">
+          <b-field :label="$t('Key words')" label-for="search" expanded>
+            <b-input
+              icon="magnify"
+              type="search"
+              id="search"
+              size="is-large"
+              expanded
+              v-model="search"
+              :placeholder="$t('For instance: London, Taekwondo, Architecture…')"
+            />
+          </b-field>
+          <b-field grouped group-multiline position="is-right" expanded>
+            <b-field :label="$t('Location')" label-for="location">
+              <address-auto-complete
+                v-model="location"
+                id="location"
+                :placeholder="$t('For instance: London')"
+              />
+            </b-field>
+            <b-field :label="$t('Radius')" label-for="radius">
+              <b-select v-model="radius" id="radius">
+                <option
+                  v-for="(radiusOption, index) in radiusOptions"
+                  :key="index"
+                  :value="radiusOption"
+                  >{{ radiusString(radiusOption) }}</option
+                >
+              </b-select>
+            </b-field>
+            <b-field :label="$t('Date')" label-for="date">
+              <b-select v-model="when" id="date">
+                <option v-for="(option, index) in options" :key="index" :value="option">{{
+                  option.label
+                }}</option>
+              </b-select>
+            </b-field>
+          </b-field>
+        </form>
+      </div>
+    </section>
+    <section class="events-featured" v-if="searchEvents.initial">
+      <b-loading :active.sync="$apollo.loading"></b-loading>
+      <h2 class="title">{{ $t("Featured events") }}</h2>
+      <div v-if="events.length > 0" class="columns is-multiline">
+        <div class="column is-one-third-desktop" v-for="event in events" :key="event.uuid">
+          <EventCard :event="event" />
+        </div>
+      </div>
+      <b-message v-else-if="events.length === 0 && $apollo.loading === false" type="is-danger">{{
+        $t("No events found")
+      }}</b-message>
+    </section>
+    <b-tabs v-else v-model="activeTab" type="is-boxed" class="searchTabs">
       <b-tab-item>
         <template slot="header">
           <b-icon icon="calendar"></b-icon>
@@ -32,43 +78,45 @@
           $t("No events found")
         }}</b-message>
       </b-tab-item>
-      <!--            <b-tab-item>-->
-      <!--                <template slot="header">-->
-      <!--                    <b-icon icon="account-multiple"></b-icon>-->
-      <!--                    <span>-->
-      <!--                        {{ $t('Groups') }} <b-tag rounded>{{ searchGroups.total }}</b-tag>-->
-      <!--                    </span>-->
-      <!--                </template>-->
-      <!--                <div v-if="searchGroups.total > 0" class="columns is-multiline">-->
-      <!--                    <div class="column is-one-quarter-desktop is-half-mobile"-->
-      <!--                         v-for="group in groups"-->
-      <!--                         :key="group.uuid">-->
-      <!--                        <group-card :group="group" />-->
-      <!--                    </div>-->
-      <!--                </div>-->
-      <!--                <b-message v-else-if="$apollo.loading === false" type="is-danger">-->
-      <!--                    {{ $t('No groups found') }}-->
-      <!--                </b-message>-->
-      <!--            </b-tab-item>-->
     </b-tabs>
-  </section>
+  </div>
 </template>
+
 <script lang="ts">
 import { Component, Prop, Vue, Watch } from "vue-property-decorator";
-import { SEARCH_EVENTS, SEARCH_GROUPS } from "../graphql/search";
-import RouteName from "../router/name";
 import EventCard from "../components/Event/EventCard.vue";
-import GroupCard from "../components/Group/GroupCard.vue";
-import AddressAutoComplete from "../components/Event/AddressAutoComplete.vue";
-import { Group, IGroup } from "../types/actor";
+import { FETCH_EVENTS } from "../graphql/event";
+import { IEvent } from "../types/event.model";
+import RouteName from "../router/name";
 import { IAddress, Address } from "../types/address.model";
 import { SearchEvent, SearchGroup } from "../types/search.model";
+import AddressAutoComplete from "../components/Event/AddressAutoComplete.vue";
 import ngeohash from "ngeohash";
+import { SEARCH_EVENTS, SEARCH_GROUPS } from "../graphql/search";
+import { Paginate } from "../types/paginate";
+import {
+  endOfToday,
+  addDays,
+  startOfDay,
+  endOfDay,
+  endOfWeek,
+  addWeeks,
+  startOfWeek,
+  endOfMonth,
+  addMonths,
+  startOfMonth,
+  eachWeekendOfInterval,
+} from "date-fns";
+
+interface ISearchTimeOption {
+  label: string;
+  start?: Date;
+  end?: Date | null;
+}
 
 enum SearchTabs {
   EVENTS = 0,
   GROUPS = 1,
-  PERSONS = 2, // not used right now
 }
 
 const tabsName: { events: number; groups: number } = {
@@ -77,7 +125,12 @@ const tabsName: { events: number; groups: number } = {
 };
 
 @Component({
+  components: {
+    EventCard,
+    AddressAutoComplete,
+  },
   apollo: {
+    events: FETCH_EVENTS,
     searchEvents: {
       query: SEARCH_EVENTS,
       variables() {
@@ -85,105 +138,117 @@ const tabsName: { events: number; groups: number } = {
           term: this.search,
           tags: this.actualTag,
           location: this.geohash,
+          beginsOn: this.start,
+          endsOn: this.end,
+          radius: this.radius,
         };
       },
+      debounce: 300,
       skip() {
-        return !this.search && !this.actualTag;
-      },
-    },
-    searchGroups: {
-      query: SEARCH_GROUPS,
-      variables() {
-        return {
-          searchText: this.search,
-        };
-      },
-      skip() {
-        return !this.search || this.isURL(this.search);
+        return !this.search && !this.actualTag && !this.geohash && this.end === null;
       },
     },
   },
-  components: {
-    GroupCard,
-    EventCard,
-    AddressAutoComplete,
+  metaInfo() {
+    return {
+      // if no subcomponents specify a metaInfo.title, this title will be used
+      title: this.$t("Explore events") as string,
+      // all titles will be injected into this template
+      titleTemplate: "%s | Mobilizon",
+    };
   },
 })
 export default class Search extends Vue {
-  @Prop({ type: String, required: false }) searchTerm!: string;
-
-  @Prop({ type: String, required: false }) tag!: string;
-
+  @Prop({ type: String, required: false, default: "" }) searchTerm!: string;
   @Prop({ type: String, required: false, default: "events" }) searchType!: "events" | "groups";
 
-  searchEvents: SearchEvent = { total: 0, elements: [] };
+  events: IEvent[] = [];
 
-  searchGroups: SearchGroup = { total: 0, elements: [] };
+  searchEvents: Paginate<IEvent> & { initial: boolean } = { total: 0, elements: [], initial: true };
+
+  search = this.searchTerm;
 
   activeTab: SearchTabs = tabsName[this.searchType];
 
-  search: string = this.searchTerm;
-  actualTag: string = this.tag;
   location: IAddress = new Address();
 
-  @Watch("searchEvents")
-  async redirectURLToEvent() {
-    if (this.searchEvents.total === 1 && this.isURL(this.searchTerm)) {
-      return await this.$router.replace({
-        name: RouteName.EVENT,
-        params: { uuid: this.searchEvents.elements[0].uuid },
-      });
+  options: ISearchTimeOption[] = [
+    {
+      label: this.$t("Today") as string,
+      start: new Date(),
+      end: endOfToday(),
+    },
+    {
+      label: this.$t("Tomorrow") as string,
+      start: startOfDay(addDays(new Date(), 1)),
+      end: endOfDay(addDays(new Date(), 1)),
+    },
+    {
+      label: this.$t("This weekend") as string,
+      start: this.weekend.start,
+      end: this.weekend.end,
+    },
+    {
+      label: this.$t("This week") as string,
+      start: new Date(),
+      end: endOfWeek(new Date(), { locale: this.$dateFnsLocale }),
+    },
+    {
+      label: this.$t("Next week") as string,
+      start: startOfWeek(addWeeks(new Date(), 1), { locale: this.$dateFnsLocale }),
+      end: endOfWeek(addWeeks(new Date(), 1), { locale: this.$dateFnsLocale }),
+    },
+    {
+      label: this.$t("This month") as string,
+      start: new Date(),
+      end: endOfMonth(new Date()),
+    },
+    {
+      label: this.$t("Next month") as string,
+      start: startOfMonth(addMonths(new Date(), 1)),
+      end: endOfMonth(addMonths(new Date(), 1)),
+    },
+    {
+      label: this.$t("Any day") as string,
+      start: undefined,
+      end: undefined,
+    },
+  ];
+
+  when: ISearchTimeOption = {
+    label: this.$t("Any day") as string,
+    start: undefined,
+    end: null,
+  };
+
+  radiusString = (radius: number | null) => {
+    if (radius) {
+      return this.$tc("{nb} km", radius, { nb: radius });
     }
-  }
+    return this.$t("any distance");
+  };
 
-  changeTab(index: number) {
-    switch (index) {
-      case SearchTabs.EVENTS:
-        this.$router.push({
-          name: RouteName.SEARCH,
-          params: { searchTerm: this.searchTerm, searchType: "events" },
-        });
-        break;
-      case SearchTabs.GROUPS:
-        this.$router.push({
-          name: RouteName.SEARCH,
-          params: { searchTerm: this.searchTerm, searchType: "groups" },
-        });
-        break;
-    }
-  }
+  radiusOptions: (number | null)[] = [1, 5, 10, 25, 50, 100, 150, null];
 
-  @Watch("search")
-  changeTabForResult() {
-    if (this.searchEvents.total === 0 && this.searchGroups.total > 0) {
-      this.activeTab = SearchTabs.GROUPS;
-    }
-    if (this.searchGroups.total === 0 && this.searchEvents.total > 0) {
-      this.activeTab = SearchTabs.EVENTS;
-    }
-  }
+  radius: number | undefined = undefined;
 
-  @Watch("search")
-  @Watch("$route")
-  async loadSearch() {
-    (await this.$apollo.queries.searchEvents.refetch()) &&
-      this.$apollo.queries.searchGroups.refetch();
-  }
-
-  get groups(): IGroup[] {
-    return this.searchGroups.elements.map((group) => Object.assign(new Group(), group));
-  }
-
-  isURL(url: string): boolean {
-    const a = document.createElement("a");
-    a.href = url;
-    return (a.host && a.host !== window.location.host) as boolean;
-  }
-
-  processSearch() {
+  submit() {
     this.$apollo.queries.searchEvents.refetch();
   }
 
+  @Watch("searchTerm")
+  updateSearchTerm() {
+    this.search = this.searchTerm;
+  }
+
+  get weekend(): { start: Date; end: Date } {
+    const now = new Date();
+    const endOfWeekDate = endOfWeek(now, { locale: this.$dateFnsLocale });
+    const startOfWeekDate = startOfWeek(now, { locale: this.$dateFnsLocale });
+    const [start, end] = eachWeekendOfInterval({ start: startOfWeekDate, end: endOfWeekDate });
+    return { start: startOfDay(start), end: endOfDay(end) };
+  }
+
   get geohash() {
     if (this.location && this.location.geom) {
       const [lon, lat] = this.location.geom.split(";");
@@ -191,16 +256,47 @@ export default class Search extends Vue {
     }
     return undefined;
   }
+
+  get start(): Date | undefined {
+    return this.when.start;
+  }
+
+  get end(): Date | undefined | null {
+    return this.when.end;
+  }
 }
 </script>
-<style lang="scss">
-@import "~bulma/sass/utilities/_all";
-@import "~bulma/sass/components/tabs";
-@import "~buefy/src/scss/components/tabs";
-@import "~bulma/sass/elements/tag";
 
-.searchTabs .tab-content {
-  background: #fff;
-  min-height: 10em;
+<style scoped lang="scss">
+@import "@/variables.scss";
+
+main > .container {
+  background: $white;
+
+  .hero-body {
+    padding: 1rem 1.5rem;
+  }
+}
+
+h1.title {
+  margin-top: 1.5rem;
+}
+
+h3.title {
+  margin-bottom: 1.5rem;
+}
+
+.events-featured {
+  margin: 25px auto;
+
+  .columns {
+    margin: 1rem auto 3rem;
+  }
+}
+
+form {
+  /deep/ .field label.label {
+    margin-bottom: 0;
+  }
 }
 </style>
diff --git a/js/yarn.lock b/js/yarn.lock
index ce0a49a75..d67b50900 100644
--- a/js/yarn.lock
+++ b/js/yarn.lock
@@ -4406,6 +4406,11 @@ date-fns@^1.27.2:
   resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
   integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
 
+date-fns@^2.15.0:
+  version "2.15.0"
+  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.15.0.tgz#424de6b3778e4e69d3ff27046ec136af58ae5d5f"
+  integrity sha512-ZCPzAMJZn3rNUvvQIMlXhDr4A+Ar07eLeGsGREoWU19a3Pqf5oYa+ccd+B3F6XVtQY6HANMFdOQ8A+ipFnvJdQ==
+
 de-indent@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
diff --git a/lib/graphql/schema/search.ex b/lib/graphql/schema/search.ex
index d0d71247d..02859d686 100644
--- a/lib/graphql/schema/search.ex
+++ b/lib/graphql/schema/search.ex
@@ -51,6 +51,8 @@ defmodule Mobilizon.GraphQL.Schema.SearchType do
       arg(:radius, :float, default_value: 50)
       arg(:page, :integer, default_value: 1)
       arg(:limit, :integer, default_value: 10)
+      arg(:begins_on, :datetime)
+      arg(:ends_on, :datetime)
 
       resolve(&Search.search_events/3)
     end
diff --git a/lib/mobilizon/events/events.ex b/lib/mobilizon/events/events.ex
index dbe2407e9..af877fb3f 100644
--- a/lib/mobilizon/events/events.ex
+++ b/lib/mobilizon/events/events.ex
@@ -463,9 +463,10 @@ defmodule Mobilizon.Events do
     term
     |> normalize_search_string()
     |> events_for_search_query()
+    |> events_for_begins_on(args)
+    |> events_for_ends_on(args)
     |> events_for_tags(args)
     |> events_for_location(args)
-    |> filter_future_events(true)
     |> filter_local_or_from_followed_instances_events()
     |> order_by([q], asc: q.id)
     |> Page.build_page(page, limit)
@@ -1296,6 +1297,29 @@ defmodule Mobilizon.Events do
     )
   end
 
+  @spec events_for_begins_on(Ecto.Query.t(), map()) :: Ecto.Query.t()
+  defp events_for_begins_on(query, args) do
+    begins_on = Map.get(args, :begins_on, DateTime.utc_now())
+
+    query
+    |> where([q], q.begins_on >= ^begins_on)
+  end
+
+  @spec events_for_ends_on(Ecto.Query.t(), map()) :: Ecto.Query.t()
+  defp events_for_ends_on(query, args) do
+    ends_on = Map.get(args, :ends_on)
+
+    if is_nil(ends_on),
+      do: query,
+      else:
+        where(
+          query,
+          [q],
+          (is_nil(q.ends_on) and q.begins_on <= ^ends_on) or
+            q.ends_on <= ^ends_on
+        )
+  end
+
   @spec events_for_tags(Ecto.Query.t(), map()) :: Ecto.Query.t()
   defp events_for_tags(query, %{tags: tags}) when is_valid_string?(tags) do
     query
@@ -1307,6 +1331,9 @@ defmodule Mobilizon.Events do
   defp events_for_tags(query, _args), do: query
 
   @spec events_for_location(Ecto.Query.t(), map()) :: Ecto.Query.t()
+  defp events_for_location(query, %{radius: radius}) when is_nil(radius),
+    do: query
+
   defp events_for_location(query, %{location: location, radius: radius})
        when is_valid_string?(location) and not is_nil(radius) do
     with {lon, lat} <- Geohax.decode(location),
diff --git a/test/graphql/resolvers/search_test.exs b/test/graphql/resolvers/search_test.exs
index 9a4103a68..cb5f634a9 100644
--- a/test/graphql/resolvers/search_test.exs
+++ b/test/graphql/resolvers/search_test.exs
@@ -15,8 +15,8 @@ defmodule Mobilizon.GraphQL.Resolvers.SearchTest do
 
   describe "search events/3" do
     @search_events_query """
-    query SearchEvents($location: String, $radius: Float, $tags: String, $term: String) {
-      searchEvents(location: $location, radius: $radius, tags: $tags, term: $term) {
+    query SearchEvents($location: String, $radius: Float, $tags: String, $term: String, $beginsOn: DateTime, $endsOn: DateTime) {
+      searchEvents(location: $location, radius: $radius, tags: $tags, term: $term, beginsOn: $beginsOn, endsOn: $endsOn) {
         total,
         elements {
           id
@@ -145,6 +145,41 @@ defmodule Mobilizon.GraphQL.Resolvers.SearchTest do
                event.uuid
     end
 
+    test "finds events by begins_on and ends_on", %{conn: conn} do
+      now = DateTime.utc_now()
+
+      # TODO
+      event =
+        insert(:event,
+          title: "Tour du monde",
+          begins_on: DateTime.add(now, 3600 * 24 * 3),
+          ends_on: DateTime.add(now, 3600 * 24 * 10)
+        )
+
+      insert(:event,
+        title: "Autre événement",
+        begins_on: DateTime.add(now, 3600 * 24 * 30),
+        ends_on: nil
+      )
+
+      Workers.BuildSearch.insert_search_event(event)
+
+      res =
+        AbsintheHelpers.graphql_query(conn,
+          query: @search_events_query,
+          variables: %{
+            beginsOn: now |> DateTime.add(86_400) |> DateTime.to_iso8601(),
+            endsOn: now |> DateTime.add(1_728_000) |> DateTime.to_iso8601()
+          }
+        )
+
+      assert res["errors"] == nil
+      assert res["data"]["searchEvents"]["total"] == 1
+
+      assert hd(res["data"]["searchEvents"]["elements"])["uuid"] ==
+               event.uuid
+    end
+
     test "finds events with multiple criteria", %{conn: conn} do
       {lon, lat} = {45.75, 4.85}
       point = %Geo.Point{coordinates: {lon, lat}, srid: 4326}