diff --git a/build.gradle b/build.gradle
index 2bc40119073eb28d5de1c42825e41e177a753f49..ace6ecf23ddb939e56918a48ca5e28f7aed824c6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -23,6 +23,7 @@ repositories {
 }
 
 dependencies {
+    implementation 'com.google.code.gson:gson:2.8.9'
     implementation 'commons-io:commons-io:2.8.0'
     implementation 'io.jsonwebtoken:jjwt:0.9.1'
     implementation 'org.springframework.session:spring-session-jdbc:2.4.3'
diff --git a/src/main/java/com/example/clientproject/web/restControllers/ShopSearch.java b/src/main/java/com/example/clientproject/web/restControllers/ShopSearch.java
new file mode 100644
index 0000000000000000000000000000000000000000..10e2013334d34db0133cdad585ef95f87514be53
--- /dev/null
+++ b/src/main/java/com/example/clientproject/web/restControllers/ShopSearch.java
@@ -0,0 +1,139 @@
+package com.example.clientproject.web.restControllers;
+
+import com.example.clientproject.data.shops.Shops;
+import com.example.clientproject.data.shops.ShopsRepo;
+import com.example.clientproject.data.tags.Tags;
+import com.example.clientproject.data.tags.TagsRepo;
+import com.example.clientproject.services.RecommendationGenerator;
+import com.google.gson.Gson;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpSession;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@RestController
+public class ShopSearch {
+    @Autowired
+    ShopsRepo shopsRepo;
+
+    @Autowired
+    TagsRepo tagsRepo;
+
+    @Autowired
+    RecommendationGenerator recommendationGenerator;
+
+    @GetMapping("/shop/search")
+    public String searchShops(@RequestParam(value = "q", required = false) String query,
+                              @RequestParam(value = "p", required = false) Integer page,
+                              @RequestParam(value = "t", required = false) List<String> tags,
+                              HttpSession session) throws Exception {
+        final Integer ITEMS_PER_PAGE = 6;
+
+        //Get all the active shops
+        List<Shops> allShops = shopsRepo.findActiveShops();
+
+        //Filter the shops using the query provided
+        if(query != null){
+            allShops = allShops
+                    .stream()
+                    .filter(s -> s.getShopName().toLowerCase(Locale.ROOT).strip().contains(query.toLowerCase(Locale.ROOT).strip()))
+                    .collect(Collectors.toList());
+        }
+
+        //Filter using the tags provided
+        if(tags!=null){
+            List<Long> validTagIds = new ArrayList<>();
+            for (String t : tags){
+                Optional<Tags> tagsOptional = tagsRepo.findByTagNameIgnoreCase(t);
+                if(tagsOptional.isPresent()){
+                    Long tagId = tagsOptional.get().getTagId();
+                    if (!validTagIds.contains(tagId)){
+                        validTagIds.add(tagId);
+                    }
+                }
+            }
+            List<Shops> validShops = new ArrayList<>();
+            for (Shops s : allShops){
+                boolean match = false;
+                for (Tags t : s.getShopTags()){
+                    if (validTagIds.contains(t.getTagId())){
+                        match = true;
+                        break;
+                    }
+                }
+                if (match){
+                    validShops.add(s);
+                }
+            }
+            allShops = validShops;
+        }
+
+        //Paginate
+        boolean hasNextPage = false;
+        if (allShops.size() > ITEMS_PER_PAGE){
+            if(page==null){
+                page = 1;
+            }
+            List<List<Shops>> pages = getPages(allShops, ITEMS_PER_PAGE);
+            if(page > pages.size()){
+                page = 1;
+            }
+            if (pages.size() >= page){
+                allShops = pages.get(page-1);
+            }
+            if (pages.size() >= page + 1){
+                hasNextPage = true;
+            }
+        }
+
+        //Sort in order of relevance
+        allShops = recommendationGenerator.getRecommendations(session, allShops);
+
+        //Convert to required format
+        List<HashMap<String, String>> formattedShops = new ArrayList<>();
+        for(Shops shop : allShops){
+            HashMap<String,String> data = new HashMap<>();
+            data.put("name",shop.getShopName());
+            data.put("banner",shop.getShopBanner());
+            data.put("id", String.valueOf(shop.getShopId()));
+            data.put("category",shop.getCategory().getCategoryName());
+            data.put("website",shop.getShopWebsite());
+            Integer reward_count = shop.getStampBoard().getRewards().size();
+            data.put("reward_count",String.valueOf(reward_count));
+            if(reward_count != 0){
+                data.put("next_reward_name",shop.getStampBoard().getRewards().get(0).getRewardName());
+                data.put("next_reward_pos",String.valueOf(shop.getStampBoard().getRewards().get(0).getRewardStampLocation()));
+            }else{
+                data.put("next_reward_name","No Rewards");
+            }
+            formattedShops.add(data);
+        }
+
+        Map<String,Object> returnMap = new HashMap<>();
+        returnMap.put("shops",formattedShops);
+        returnMap.put("hasNextPage", hasNextPage);
+
+        Gson gson = new Gson();
+        String json = gson.toJson(returnMap);
+
+        return json;
+
+    }
+
+    public <T> List<List<T>> getPages(Collection<T> c, Integer pageSize) {
+        if (c == null)
+            return Collections.emptyList();
+        List<T> list = new ArrayList<T>(c);
+        if (pageSize == null || pageSize <= 0 || pageSize > list.size())
+            pageSize = list.size();
+        int numPages = (int) Math.ceil((double)list.size() / (double)pageSize);
+        List<List<T>> pages = new ArrayList<List<T>>(numPages);
+        for (int pageNum = 0; pageNum < numPages;)
+            pages.add(list.subList(pageNum * pageSize, Math.min(++pageNum * pageSize, list.size())));
+        return pages;
+    }
+}
diff --git a/src/main/resources/static/imgs/uploaded/02b3324f_113c_4c98_8ad6_7f1cf28f74c9.jpg b/src/main/resources/static/imgs/uploaded/02b3324f_113c_4c98_8ad6_7f1cf28f74c9.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7ff5ef91293d6692910338be0f6b54dd16674c6a
Binary files /dev/null and b/src/main/resources/static/imgs/uploaded/02b3324f_113c_4c98_8ad6_7f1cf28f74c9.jpg differ
diff --git a/src/main/resources/static/imgs/uploaded/1eab1fb2_7744_4eb6_9505_8a1e9b59981b.jpg b/src/main/resources/static/imgs/uploaded/1eab1fb2_7744_4eb6_9505_8a1e9b59981b.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7ff5ef91293d6692910338be0f6b54dd16674c6a
Binary files /dev/null and b/src/main/resources/static/imgs/uploaded/1eab1fb2_7744_4eb6_9505_8a1e9b59981b.jpg differ
diff --git a/src/main/resources/static/imgs/uploaded/7615374a_c54e_4854_ad96_14b719c5af9c.jpg b/src/main/resources/static/imgs/uploaded/7615374a_c54e_4854_ad96_14b719c5af9c.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7ff5ef91293d6692910338be0f6b54dd16674c6a
Binary files /dev/null and b/src/main/resources/static/imgs/uploaded/7615374a_c54e_4854_ad96_14b719c5af9c.jpg differ
diff --git a/src/main/resources/static/imgs/uploaded/8d96f724_c612_4da0_9ab0_26a0ea2ad161.jpg b/src/main/resources/static/imgs/uploaded/8d96f724_c612_4da0_9ab0_26a0ea2ad161.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7ff5ef91293d6692910338be0f6b54dd16674c6a
Binary files /dev/null and b/src/main/resources/static/imgs/uploaded/8d96f724_c612_4da0_9ab0_26a0ea2ad161.jpg differ
diff --git a/src/main/resources/static/js/searchBar.js b/src/main/resources/static/js/searchBar.js
new file mode 100644
index 0000000000000000000000000000000000000000..ebce210a4fbe540bbc37b9e7735226c86aede06e
--- /dev/null
+++ b/src/main/resources/static/js/searchBar.js
@@ -0,0 +1,115 @@
+var tags = [];
+var query = "";
+var page = 1;
+
+var searchBar = document.getElementById("main-search");
+var tagGroup = document.getElementById("search-tag-group");
+
+function updateSearch(e){
+    page = 1;
+    query = searchBar.value;
+    doSearch(false)
+}
+
+function removeTag(i){
+    tags.splice(i, 1);
+    page=1;
+    updateUI()
+    doSearch(false)
+}
+
+function addTag(e){
+    if(e.key== "Enter"){
+        if (searchBar.value != ""){
+            page = 1;
+            query = "";
+            tags.push(searchBar.value.toLowerCase());
+            searchBar.value = "";
+            updateUI();
+            doSearch(false);
+        }
+    }
+}
+
+function updateUI(){
+    tagGroup.innerHTML = "";
+    for(let [i,tag] of tags.entries()){
+        tagGroup.innerHTML += `
+            <div class="control mr-3">
+                <div class="tags has-addons">
+                    <span class="tag gradient">${tag}</span>
+                    <a class="tag is-delete" onclick="removeTag(${i})"></a>
+                </div>
+            </div>`
+    }
+}
+
+function doSearch(fromNextPageBtn){
+    let url = "/shop/search"
+    url += "?q=" + query
+    url += "&p=" + page.toString()
+    for (let t of tags){
+        url += "&t=" + t.toString()
+    }
+    fetch(url)
+        .then(e=>e.json())
+        .then(data=>{
+            if(!fromNextPageBtn){
+                document.getElementById("business_card_container").innerHTML = "";
+            }
+            for(let shop of data["shops"]){
+                addShop(shop);
+            }
+            if(data["hasNextPage"] == true){
+                document.getElementById("loadMoreBtn").style.display = "flex";
+            }else{
+                document.getElementById("loadMoreBtn").setAttribute('style', 'display:none!important');
+            }
+        });
+}
+
+function addShop(shopInfo){
+    let img_path = shopInfo["banner"]
+    let title = shopInfo["name"]
+    let reward_text = shopInfo["next_reward_name"] + " at " + shopInfo["next_reward_pos"] + " stamps"
+    let reward_amount = shopInfo["reward_count"]
+    let shopId = shopInfo["id"]
+
+    document.getElementById("business_card_container").innerHTML +=`
+<div class="business_container box" style="position:relative;">
+    <div class="image" style="background-image:url(${img_path});"></div>
+    <div class="favouriteStar starContainer" onclick="favouriteBusiness(this,${shopId});">
+        <span class="icon favouriteStar">
+            <i class="far fa-star"></i>
+        </span>
+        <span class="icon favouriteStar">
+            <i class="fas fa-star"></i>
+        </span>
+    </div>
+    <div class="content">
+        <h1 class="title is-4 mb-1">${title}</h1>
+        <p class="mb-1">${reward_text}</p>
+        <div class="is-full-width" style="display:flex;justify-content: space-between;align-items: center">
+            <div class="level-left">
+                <span class="icon is-small is-left ml-1 mr-1">
+                    <i class="fas fa-gift"></i>
+                </span>
+                <p>${reward_amount}</p>
+            </div>
+            <div class="level-right">
+                <button class="button is-rounded gradient" onclick="redirect(${shopId})">
+                    View Shop
+                    <span class="icon is-small is-left ml-1">
+                        <i class="fas fa-arrow-right"></i>
+                    </span>
+                </button>
+            </div>
+        </div>
+    </div>
+</div>`
+}
+
+function loadNextPage(){
+    page++;
+    doSearch(true);
+}
\ No newline at end of file
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html
index 7d36dcf8a9fd8fda6c08e8e55ffa21909878a404..041a8e83fd9889a493a376d54c50066a4192ba85 100644
--- a/src/main/resources/templates/index.html
+++ b/src/main/resources/templates/index.html
@@ -62,22 +62,24 @@
 		<div class="container is-full-width is-flex is-justify-content-center is-align-items-center is-flex-direction-column mb-4">
 			<h1 class="title is-3">Where else can I earn rewards?</h1>
 			<div class="control has-icons-left mb-2" style="width: 60%;">
-				<input class="input" type="text" placeholder="Enter Brands or keywords e.g. Vegan, Clothing etc..">
+				<input class="input" type="text" placeholder="Enter Brands or keywords e.g. Vegan, Clothing etc.."
+				oninput="updateSearch(event)" onkeydown="addTag(event)" id="main-search">
 				<span class="icon is-small is-left">
 					<i class="fas fa-search"></i>
 				</span>
 			</div>
 			<!--Tags-->
-			<div class="field is-grouped is-grouped-multiline" style="width: 60%;">
-				<div th:each="tag: ${tags}" th:include="fragments/tag.html :: tag"
-					 th:with="text=${tag}"></div>
+			<div class="field is-grouped is-grouped-multiline" style="width: 60%;" id="search-tag-group">
+<!--				<div th:each="tag: ${tags}" th:include="fragments/tag.html :: tag"-->
+<!--					 th:with="text=${tag}"></div>-->
 			</div>
 		</div>
 
-		<div class="container is-full-width is-flex is-justify-content-center is-align-items-center is-flex-wrap-wrap">
-			<div th:each="shop,i: ${normalShops}" th:include="fragments/business_card.html :: business_card"
-				 th:with="title=${shop.shopName}, reward_text='Free coffee at 6 stamps', reward_amount=4,
-				img_path=${shop.shopImage}, shopId=${shop.shopId}"></div>
+		<div class="container is-full-width is-flex is-justify-content-center is-align-items-center is-flex-wrap-wrap"
+			 id="business_card_container">
+		</div>
+		<div class="is-full-width is-flex is-justify-content-center is-align-items-center" id="loadMoreBtn" style="display: none!important;;">
+			<a onclick="loadNextPage()">Load More</a>
 		</div>
 
 	</div>
@@ -97,5 +99,7 @@
 			</div>
 		</div>
 	</div>
+	<script src="/js/searchBar.js"></script>
+	<script>doSearch(false);</script>
 </body>
 </html>