diff --git a/src/components/OcSearchResultCard/OcSearchResultCard.vue b/src/components/OcSearchResultCard/OcSearchResultCard.vue
index fe3051a1a9bfd146802d5ac20a1510746d7b4923..a3a5f618f42cfa069261a555372be9543d00ee45 100644
--- a/src/components/OcSearchResultCard/OcSearchResultCard.vue
+++ b/src/components/OcSearchResultCard/OcSearchResultCard.vue
@@ -1,20 +1,36 @@
 <template>
   <div
-    class="rounded-md shadow-oc"
+    class="rounded-md shadow-md border bg-gray-100"
   >
   <div class="p-4">
     <div id="title" class="font-semibold text-2xl">
-      <i :class="icon" :title="t('resourceType.'+dcatType)" class="mr-1"></i>
-      {{ title }}
+      <i :class="icon" :title="t('resourceType.'+dcatType)" class="mr-2"></i>
+      <OcLink
+        :to="{
+          name: 'community.resource',
+          params: {
+            identifier: identifier,
+            community: props.community.name,
+            resource: dcatType
+          }
+        }"
+      >
+        {{ title }}
+      </OcLink>
+    </div>
+    <div id="subtitle" class="text-sm text-gray-600">
+      {{ t('search.searchResult.id') }}: {{ identifier }} <span v-if="version">/ {{ t('search.searchResult.version') }} {{ version }}</span>
     </div>
-    <div id="subtitle" class="text-sm text-gray-600">{{ subtitle }}</div>
     <div id="creators" class="mt-1 mb-1">{{ creators }}</div>
     <div id="description">
       <p class="font-medium">{{ t('search.searchResult.description') }}</p>
       <p class="text-justify text-sm">{{ description }}</p>
     </div>
   </div>
-  <div id="footer" class="pl-4 pr-4 pt-2 pb-2 bg-gray-100 rounded-b-md text-sm flex flex-row gap-2 justify-between">
+  <div id="footer" class="pl-4 pr-4 pt-2 pb-2 bg-gray-200 rounded-b-md text-sm flex flex-row gap-2 justify-between">
+    <span v-if="dcatType===ResourceType.CATALOG">
+      {{ catalogues }} {{ t('search.searchResult.catalogues') }} {{ t('and') }} {{ datasets }} {{ t('search.searchResult.datasets') }}
+    </span>
     <span v-if="dcatType===ResourceType.DATASET">{{ distributions +' '+  t('search.searchResult.distributions')}}</span>
     <span v-if="parentCatalogueTitle">{{ t('search.searchResult.parentCatalogue') + ' : ' + parentCatalogueTitle }}</span>
   </div>
@@ -22,11 +38,12 @@
 </template>
 <script setup lang="ts">
 import { useTranslateValue } from '@/composables/useTranslateValue'
-import type { OcSearchResult } from '@/declarations';
+import type { OcCommunity, OcSearchResult } from '@/declarations';
 import { iconsDict } from '@/helpers/icons';
 import { ResourceType, type2ResourceType } from '@/helpers/resourceType';
 import { computed, type PropType } from 'vue'
 import { useI18n } from 'vue-i18n';
+import OcLink from '../OcLink.vue';
 
 const { t } = useI18n()
 const { translateValue } = useTranslateValue()
@@ -35,24 +52,34 @@ const props = defineProps({
   searchResult: {
     type: Object as PropType<OcSearchResult>,
     required: true
+  },
+  community: {
+    type: Object as PropType<OcCommunity>,
+    required: true
   }
 })
 
 const title = computed(() => translateValue(props.searchResult.title))
 const dcatType = computed(() => {
-  const compatibleTypes = props.searchResult['@type'].filter((type) => type in type2ResourceType)
-  return type2ResourceType[compatibleTypes[0]] ?? ResourceType.UNKNOWN
+  if (Array.isArray(props.searchResult['@type'])){
+    const compatibleTypes = props.searchResult['@type'].filter((type) => type in type2ResourceType)
+    return type2ResourceType[compatibleTypes[0]] ?? ResourceType.UNKNOWN
+  } else {
+    return type2ResourceType[props.searchResult['@type']] ?? ResourceType.UNKNOWN
+  }
 })
 const icon = computed(() => {
   return iconsDict[dcatType.value as string] ?? ''
 })
-const subtitle = computed(() => {
-  return props.searchResult.identifier + " / Version " + "1.0.0"
-})
+
+const identifier = computed(() => props.searchResult.identifier)
+const version = computed(() => props.searchResult.version)
 const creators = computed(() => props.searchResult.creator?.map((creator) => creator.name).join('; '))
 const description = computed(() => translateValue(props.searchResult.description))
 const parentCatalogueTitle = computed(() => translateValue(props.searchResult.parentCatalog?.title))
 const parentCatalogueIdentifier = computed(() => props.searchResult.parentCatalog?.identifier)
+const catalogues = computed(() => props.searchResult.catalog?.length ?? 0)
+const datasets = computed(() => props.searchResult.dataset?.length ?? 0)
 const distributions = computed(() => props.searchResult.distribution?.length ?? 0)
 </script>
 <style scoped>
diff --git a/src/components/OcSearchResultCardSkeleton/OcSearchResultCardSkeleton.stories.ts b/src/components/OcSearchResultCardSkeleton/OcSearchResultCardSkeleton.stories.ts
new file mode 100644
index 0000000000000000000000000000000000000000..52d7b9f790e912e05fee6eb033abb5bd8463c5ee
--- /dev/null
+++ b/src/components/OcSearchResultCardSkeleton/OcSearchResultCardSkeleton.stories.ts
@@ -0,0 +1,20 @@
+import type { Meta, StoryObj } from '@storybook/vue3'
+
+import OcSearchResultCardSkeleton from './OcSearchResultCardSkeleton.vue'
+
+const meta: Meta<typeof OcSearchResultCardSkeleton> = {
+  component: OcSearchResultCardSkeleton
+}
+
+export default meta
+type Story = StoryObj<typeof OcSearchResultCardSkeleton>
+
+export const Default: Story = {
+  render: (args) => ({
+    components: { OcSearchResultCardSkeleton },
+    setup() {
+      return { args }
+    },
+    template: '<OcSearchResultCardSkeleton v-bind="args" />'
+  }),
+}
diff --git a/src/components/OcSearchResultCardSkeleton/OcSearchResultCardSkeleton.vue b/src/components/OcSearchResultCardSkeleton/OcSearchResultCardSkeleton.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7303e2663e7b6b1683aa2f06bc963adac5cbdd4e
--- /dev/null
+++ b/src/components/OcSearchResultCardSkeleton/OcSearchResultCardSkeleton.vue
@@ -0,0 +1,25 @@
+<template>
+  <div class="bg-gray-100 w-full h-44 rounded-md ">
+    <div class="p-4 h-36">
+      <div id="title" class="font-semibold text-2xl flex flex-row gap-2 mb-2">
+        <p class="w-6 h-6 bg-slate-200 rounded animate-pulse"></p>
+        <p class="rounded bg-slate-200 w-4/5 h-6 text-white font-semibold text-2xl animate-pulse"></p>
+      </div>
+      <div id="subtitle" class="text-sm w-1/5 h-2 bg-slate-200 mb-2"></div>
+      <div id="creators" class="rounded bg-slate-200 w-4/5 h-4 text-white font-semibold text-2xl animate-pulse"></div>
+      <div id="description">
+        <p class="font-medium text-gray-500 animate-pulse">{{ t('search.searchResult.description') }}</p>
+        <p class="rounded text-sm bg-slate-200 h-8 w-full"></p>
+      </div>
+    </div>
+    <div id="footer" class="bg-gray-200 rounded-b-md h-8">
+    </div>
+  </div>  
+</template>
+<script setup lang="ts">
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n()
+</script>
+<style scoped>
+</style>
\ No newline at end of file
diff --git a/src/declarations.ts b/src/declarations.ts
index 40183281a2924b37d380362e1d1b792f4cefb32f..6c2b39a1b09f9d56adf3531aa6ae40ade1d2577e 100644
--- a/src/declarations.ts
+++ b/src/declarations.ts
@@ -7,6 +7,7 @@ import {
   rolePlatformManager,
   roleCatalogManager
 } from '@/ability'
+import type { type2ResourceType } from './helpers/resourceType'
 
 export type Credentials = {
   email: string
@@ -135,12 +136,20 @@ export type OcDistributionSummary = OcResource & {
  * for search page results cards
  */
 export type OcSearchResult = OcResource & {
-  version?: string,
+  version?: string
   creator?: Array<OcPerson | OcOrganization>
+  catalog?: string[]
+  dataset?: string[]
   distribution?: string[]
   parentCatalog?: OcCatalog
 }
 
+/** A representation of a search query */
+export type OcSearchQuery = {
+  queryString?: string
+  resourceType?: Array<keyof typeof type2ResourceType>
+}
+
 export type OcTreeNode = OcResource & {
   children: Array<OcTreeNode>
   catalog?: Array<OcTreeNode | string>
diff --git a/src/helpers/icons.ts b/src/helpers/icons.ts
index 24daa67ecb761ff78122678428fd7da5a4790fa3..5bfb9e427b2cf87144930537c2de834b7d15e5aa 100644
--- a/src/helpers/icons.ts
+++ b/src/helpers/icons.ts
@@ -12,5 +12,6 @@ export const iconsDict:Record<string, string> = {
   catalog: 'fa-solid fa-book-open',
   dataset: 'fa-solid fa-table',
   distribution: 'fa-solid fa-file',
-  service: 'fa-solid fa-cube'
+  service: 'fa-solid fa-cube',
+  search: 'fa-solid fa-magnifying-glass'
 }
diff --git a/src/locales/en.ts b/src/locales/en.ts
index 03f678fe313b0f803f683b936d0a0ca5a220c267..a90d919ac5e37084095fae684c1ddd6b5a311037 100644
--- a/src/locales/en.ts
+++ b/src/locales/en.ts
@@ -126,7 +126,8 @@ export default {
     signIn: 'Sign in',
     signOut: 'Sign out',
     register: 'Request an account',
-    joinCommunity: 'Join a community'
+    joinCommunity: 'Join a community',
+    search: 'Search'
   },
   back: 'Back',
   next: 'Next',
@@ -344,10 +345,14 @@ Greetings,
     }
   },
   search: {
+    title: "Search",
+    resourceType: "Resource type",
     searchResult: {
       description: "Description",
+      datasets: "Datasets",
       distributions: "Distributions",
-      parentCatalogue: "Parent catalogue"
+      parentCatalogue: "Parent catalogue",
+      id: "Id"
     }
   },
   resourceType: {
@@ -356,5 +361,6 @@ Greetings,
     'distribution': 'distribution',
     'service': 'service',
     'unknown': 'unknown',
-  }
+  },
+  and: 'and'
 }
diff --git a/src/locales/fr.ts b/src/locales/fr.ts
index a9b2b781b4064648230d27ec828414719b66e4d0..3bbad3ac77f56a79a5b7e8b49c39b4d05bfae3bb 100644
--- a/src/locales/fr.ts
+++ b/src/locales/fr.ts
@@ -127,7 +127,8 @@ export default {
     signIn: 'Se connecter',
     signOut: 'Déconnexion',
     register: "Demander la création d'un compte",
-    joinCommunity: 'Rejoindre une communauté'
+    joinCommunity: 'Rejoindre une communauté',
+    search: 'Rechercher'
   },
   back: 'Précédent',
   next: 'Suivant',
@@ -359,10 +360,15 @@ Cordialement,
     },
   },
   search: {
+    title: "Rechercher",
+    resourceType: "Type de ressource",
     searchResult: {
       description: "Description",
+      catalogues: "Sous-catalogues",
+      datasets: "Jeux de données",
       distributions: "Distributions",
-      parentCatalogue: "Catalogue parent"
+      parentCatalogue: "Catalogue parent",
+      id: "Id"
     }
   },
   resourceType: {
@@ -371,5 +377,6 @@ Cordialement,
     'distribution': 'distribution',
     'service': 'service',
     'unknown': 'inconnu',
-  }
+  },
+  and: 'et'
 }
diff --git a/src/pages/community/[community].search.vue b/src/pages/community/[community].search.vue
new file mode 100644
index 0000000000000000000000000000000000000000..4b383704ecfe447e4e844547cde8530e8363dd90
--- /dev/null
+++ b/src/pages/community/[community].search.vue
@@ -0,0 +1,174 @@
+<template>
+  <OcLayoutSimple
+    :breadcrumb-items="breadcrumbItems"
+    :is-authenticated="accountStore.isAuthenticated"
+  >
+    <div class="flex flex-row">
+      <div class="p-4 pl-8 w-2/12 ml-auto mr-auto">
+        <Panel :header="t('search.resourceType')" toggleable>
+          <template #toggleicon="toggleIconProps">
+            <i v-if="toggleIconProps.collapsed" class="fa-solid fa-chevron-down text-gray-700"/>
+            <i v-else class="fa-solid fa-chevron-up text-gray-700"/>
+          </template>
+          <div class="flex items-center gap-2" v-for="(dcatType, uri) in type2ResourceType" v-bind:key="uri">
+            <Checkbox v-model="resourceType" :inputId="uri" name="resourceType" :value="uri" />
+            <label for="resourceType" class="capitalize"> {{ t('resourceType.'+dcatType) }} </label>
+          </div>
+        </Panel>
+      </div>
+      <div class="p-4 pl-8 w-9/12 ml-auto mr-auto">
+        <h1 class="font-title text-4xl uppercase font-bold mb-4 text-primary">
+          {{ t('search.title') }}
+        </h1>
+        <IconField class="w-full mt-8 mb-4">
+          <InputText
+            id="search"
+            v-model="queryString"
+            fluid
+            size="large"
+            :placeholder="t('community.homepage.searchBarPlaceholder')"
+          />
+          <InputIcon class="fa-solid fa-magnifying-glass" :onclick="search" />
+        </IconField>
+        <div v-if="!searching">
+          <template v-for="result in searchResultObjects" v-bind:key="result">
+            <OcSearchResultCard :search-result="result" :community="community" class="mb-4" />
+          </template>
+        </div>
+        <div v-else>
+          <OcSearchResultCardSkeleton
+            v-for="n in itemsPerPage"
+            v-bind:key="n"
+            class="mb-4"
+          ></OcSearchResultCardSkeleton>
+        </div>
+        <Paginator
+          class="mt-8"
+          always-show="false"
+          :rows="itemsPerPage"
+          :total-records="searchResultList.length"
+          v-bind:currentPage="currentPage"
+          v-on:page="getResultsForPage($event.page)"
+        />
+      </div>
+    </div>
+  </OcLayoutSimple>
+</template>
+
+<script setup lang="ts">
+import { useTranslateValue } from '@/composables/useTranslateValue'
+import { useAccountData } from '@/dataLoaders/account'
+import { useCommunityData } from '@/dataLoaders/community'
+import type { OcBreadcrumbItem, OcSearchResult } from '@/declarations'
+import OcLayoutSimple from '@/layout/OcLayoutSimple/OcLayoutSimple.vue'
+import { useAccountStore } from '@/stores/account'
+import { computed, ref } from 'vue'
+import { useI18n } from 'vue-i18n'
+import IconField from 'primevue/iconfield'
+import InputText from 'primevue/inputtext'
+import InputIcon from 'primevue/inputicon'
+import OcSearchResultCard from '@/components/OcSearchResultCard/OcSearchResultCard.vue'
+import OcSearchResultCardSkeleton from '@/components/OcSearchResultCardSkeleton/OcSearchResultCardSkeleton.vue'
+import { getSearchResults, searchResources } from '@/sparql/search'
+import Paginator from 'primevue/paginator'
+import Panel from 'primevue/panel'
+import Checkbox from 'primevue/checkbox'
+import { type2ResourceType } from '@/helpers/resourceType'
+
+definePage({
+  name: 'community.search',
+  meta: {
+    needsAuth: false,
+    loaders: [useCommunityData, useAccountData]
+  }
+})
+
+const accountStore = useAccountStore()
+const { t } = useI18n()
+
+const { translateValue } = useTranslateValue()
+
+const { data: community } = useCommunityData()
+
+const itemsPerPage = 5
+const currentPage = ref(0)
+
+const queryString = ref('')
+const resourceType = ref([])
+
+const searchResultList = ref<string[]>([])
+const searchResultObjects = ref<OcSearchResult[]>([])
+
+const searching = ref(false)
+const errorMessage = ref<string | null>(null)
+
+const search = async () => {
+  if (searching.value) {
+    return
+  }
+
+  searching.value = true
+  currentPage.value = 0
+  try {
+    searchResultList.value = await searchResources(
+      {
+        queryString: queryString.value,
+        resourceType: resourceType.value
+      },
+      accountStore.auth
+    )
+  } catch (e) {
+    console.error(e)
+    errorMessage.value = (e as Error).message ?? 'Error'
+  }
+
+  try {
+    searchResultObjects.value = await getSearchResults(
+      searchResultList.value.slice(0, itemsPerPage),
+      accountStore.auth
+    )
+  } catch (e) {
+    console.error(e)
+    errorMessage.value = (e as Error).message ?? 'Error'
+  }
+
+  searching.value = false
+}
+
+const getResultsForPage = async (pagenumber: number) => {
+  searching.value = true
+  try {
+    searchResultObjects.value = await getSearchResults(
+      searchResultList.value.slice(pagenumber * itemsPerPage, (pagenumber + 1) * itemsPerPage),
+      accountStore.auth
+    )
+  } catch (e) {
+    console.error(e)
+    errorMessage.value = (e as Error).message ?? 'Error'
+  }
+  searching.value = false
+}
+
+const breadcrumbItems = computed<OcBreadcrumbItem[]>(() => [
+  {
+    label: translateValue(community.value.title),
+    key: 'community',
+    type: 'community',
+    to: { name: 'community', params: { community: community.value.name } }
+  },
+  {
+    label: t('breadcrumb.search'),
+    key: 'search',
+    type: 'search'
+  }
+])
+</script>
+<style scoped>
+:deep(.p-panel-header) {
+  @apply bg-primary rounded-t text-white mb-2
+}
+
+:deep(.p-panel){
+  @apply bg-gray-100
+}
+</style>
\ No newline at end of file
diff --git a/src/sparql/search.ts b/src/sparql/search.ts
new file mode 100644
index 0000000000000000000000000000000000000000..04e66d9dd7beb84cc352f6d4feb7cfea3f3f1959
--- /dev/null
+++ b/src/sparql/search.ts
@@ -0,0 +1,95 @@
+import type { Credentials, OcSearchQuery, OcSearchResult } from '@/declarations'
+import { executeSparqlConstruct, executeSparqlSelect } from './sparql'
+import type { ContextDefinition } from 'jsonld'
+import { resourceContext } from './resource'
+
+/**
+ * Get results URI of a search query
+ */
+export const searchResources = async (query: OcSearchQuery, auth?: Credentials) => {
+  let typeFilter = ""
+  if (query.resourceType?.length){
+    typeFilter = "FILTER (?type = <"
+    typeFilter += query.resourceType.join('> || ?type = <')
+    typeFilter += ">)"
+  }
+
+  let queryFilter = ""
+  if (query.queryString){
+    queryFilter = `FILTER regex(?o, "${query.queryString}", "i")`
+  }
+
+  const res =  await executeSparqlSelect(
+  `
+      SELECT DISTINCT ?resource
+      WHERE {
+        ?resource rdf:type ?type.
+        ${typeFilter}
+        FILTER EXISTS {
+          VALUES ?p { dct:title dct:description }
+          ?resource ?p ?o
+          ${queryFilter}
+        }
+      }
+    `,
+  {
+    auth: auth,
+  })
+
+  return res.map((result) => result.resource.value)
+}
+
+/** Get searchResults objects from URI list */
+export const getSearchResults = async (resourceUriList: string[], auth?: Credentials) => {
+  const searchResultContext: ContextDefinition = {
+    ...resourceContext,
+    catalog: {
+      '@id': 'http://www.w3.org/ns/dcat#catalog',
+      '@type': '@id',
+      '@container': '@set'
+    },
+    dataset: {
+      '@id': 'http://www.w3.org/ns/dcat#dataset',
+      '@type': '@id',
+      '@container': '@set'
+    },
+    distribution: {
+      '@id': 'http://www.w3.org/ns/dcat#distribution',
+      '@type': '@id',
+      '@container': '@set'
+    },
+  }
+
+  const formattedUris = '<' + resourceUriList.join('> <') + '>'
+
+  return await executeSparqlConstruct<OcSearchResult>(
+  `
+      CONSTRUCT {
+        ?s ?p ?o.
+        ?s oct:graph ?g.
+        ?s dcat:dataset ?dataset
+      }
+      WHERE {
+        VALUES ?s { ${formattedUris} }
+        VALUES ?p {
+          rdf:type
+          dct:identifier
+          dct:title
+          dct:description
+          dcat:version
+          dct:creator
+          dcat:catalog
+          dcat:dataset
+          dcat:distribution
+        }
+        ?s ?p ?o.
+        GRAPH ?g {
+          ?s dct:identifier ?identifier.
+        }
+      }
+    `,
+  {
+    context: searchResultContext,
+    auth: auth,
+  })
+}
\ No newline at end of file
diff --git a/typed-router.d.ts b/typed-router.d.ts
index 2a41d9f69496ada04128300fab81c10a1207a3ff..bb4d634acc9ccb77c5f192111635049f9d9c8bec 100644
--- a/typed-router.d.ts
+++ b/typed-router.d.ts
@@ -30,6 +30,7 @@ declare module 'vue-router/auto-routes' {
     'community.dashboard.dataset': RouteRecordInfo<'community.dashboard.dataset', '/:lang/community/:community/dashboard/dataset', { community: ParamValue<true> }, { community: ParamValue<false> }>,
     'community.datasets.new': RouteRecordInfo<'community.datasets.new', '/:lang/community/:community/dataset/new', { community: ParamValue<true> }, { community: ParamValue<false> }>,
     'community.join': RouteRecordInfo<'community.join', '/:lang/community/:community/join', { community: ParamValue<true> }, { community: ParamValue<false> }>,
+    '/:lang/community/[community].search': RouteRecordInfo<'/:lang/community/[community].search', '/:lang/community/:community/search', { community: ParamValue<true> }, { community: ParamValue<false> }>,
     'connection': RouteRecordInfo<'connection', '/:lang/connection', Record<never, never>, Record<never, never>>,
     'logout': RouteRecordInfo<'logout', '/:lang/logout', Record<never, never>, Record<never, never>>,
     'profile': RouteRecordInfo<'profile', '/:lang/profile', Record<never, never>, Record<never, never>>,