diff --git a/store/forms.py b/store/forms.py
index 61ef12b75fdd177854ac3b1a4e87cb5b118c2911..ec125c50b01db198e8520e6836d8a0bf19e113d9 100644
--- a/store/forms.py
+++ b/store/forms.py
@@ -32,3 +32,8 @@ class LoginForm(FlaskForm):
         "Enter your Favourite Colour", [validators.Length(min=3, max=35)]
     )
     submit = SubmitField("Login", render_kw={"class": "button"})
+
+
+class SearchForm(FlaskForm):
+    query = StringField("Query", [validators.DataRequired()])
+    submit = SubmitField("Submit")
diff --git a/store/routes.py b/store/routes.py
index a7e6463be4ffbbb2d608a600e99521f94e123804..d807d216286466e0c2ab96d9ac0dd53cb6924f27 100644
--- a/store/routes.py
+++ b/store/routes.py
@@ -457,3 +457,21 @@ def ItemPage(item_id):
         item_description=item_description,
         item_id=item_id,
     )
+
+
+@app.context_processor
+def base():
+    """So that search works on every page, and so that the search form does not need to be passed
+    to every template a context processor is used to inject a variable (in this case search_form)
+    into every template.
+    """
+    form = SearchForm()
+    return dict(search_form=form)
+
+
+@app.route("/search", methods=["POST"])
+def search():
+    form = SearchForm()
+    if form.validate_on_submit():
+        items = get_by_keyword(form.query.data)
+        return render_template("search.html", form=form, items=items)
diff --git a/store/templates/base.html b/store/templates/base.html
index e91b02e822abc86e5e0841fe898ad17850c0bf9b..3cfeeca86593d147890ac6f0267b3fbcc39cd5e7 100644
--- a/store/templates/base.html
+++ b/store/templates/base.html
@@ -1,3 +1,4 @@
+{% from "_formhelpers.html" import render_field %}
 <!DOCTYPE html>
 <html lang="en">
 
@@ -8,9 +9,9 @@
 </head>
 
 <body>
-    <header class="header">
-      <a href="{{ url_for('index')}}">ANTIQUES ONLINE</a>
-    </header>
+  <header class="header">
+    <a href="{{ url_for('index')}}">ANTIQUES ONLINE</a>
+  </header>
 
   <nav class="navbar">
     <ul class="navbar-list">
@@ -27,9 +28,10 @@
       <li><a href="{{ url_for('register')}}">Register</a></li>
       {% endif %}
 
-      <form class="search">
-        <button type="submit">Search</button>
-        <input type="text" placeholder="Search...">
+      <form method="POST" action="{{url_for('search')}}" class="search">
+        {{search_form.hidden_tag()}}
+        {{render_field(search_form.query) }}
+        {{search_form.submit()}}
       </form>
     </ul>
   </nav>
@@ -48,12 +50,12 @@
 
   {% endblock %} <!-- Where HTML will go when extended. -->
 
-    <footer class="footer">
-      <br>
-      <p>Contact Info: mail@mail.com</p>
-      <p>Phone Number 0000 000 0000</p>
-      <br>
-    </footer>
+  <footer class="footer">
+    <br>
+    <p>Contact Info: mail@mail.com</p>
+    <p>Phone Number 0000 000 0000</p>
+    <br>
+  </footer>
 </body>
 
 </html>
\ No newline at end of file
diff --git a/store/templates/search.html b/store/templates/search.html
new file mode 100644
index 0000000000000000000000000000000000000000..0922e01408c9436cf4efb9efd314acb7d0018670
--- /dev/null
+++ b/store/templates/search.html
@@ -0,0 +1,53 @@
+{%extends 'base.html' %}
+{% block title %} Search | Antiques Online {% endblock %}
+{% block content %}
+Searching for {{form.query.data}}
+<style>
+    .grid {
+        display: grid;
+        grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+        grid-gap: 10px;
+        grid-auto-rows: minmax(100px, auto);
+    }
+
+    .grid-item {
+        grid-row: span 1;
+        grid-column: span 1;
+        padding: 10px;
+    }
+
+    .grid-item img {
+        max-width: 100%;
+        height: auto;
+    }
+
+    .grid-item h2 {
+        margin: 10px 0;
+        font-size: 18px;
+
+    }
+
+    .grid-item p {
+        margin: 5px 0;
+        font-size: 14px;
+    }
+</style>
+
+<div class="grid">
+    {% if items %}
+    {% for item in items %}
+    <div class="grid-item">
+
+        <img src="static\image_placeholder.png" alt="{{ item.description }}">
+        <h2>{{ item.description }}</h2>
+        <p>£{{ item.price }}</p>
+        <a href="{{url_for('ItemPage', item_id = item.id)}}">View
+            Details</a>
+
+    </div>
+    {% endfor %}
+    {% else %}
+    <p>No items available</p>
+    {% endif %}
+</div>
+{% endblock %}
\ No newline at end of file
diff --git a/store/utility.py b/store/utility.py
index 89384d83fd3558b49bf753a282a7f3d90ac21c9b..9e77b4c0fa75f031b89f9a16c79e316f9d2b6ca9 100644
--- a/store/utility.py
+++ b/store/utility.py
@@ -2,7 +2,7 @@
 
 from difflib import SequenceMatcher
 from store.models import *
-from sqlalchemy import select
+from sqlalchemy import select, func
 import datetime
 from store import *
 import re
@@ -84,26 +84,17 @@ def remove_item(item):
 #     db.session.Item.query()
 
 
-# Search desciption for matching substring.
-# This is most useful for a user keyword search.
 def get_by_keyword(substring):
-    to_search = get_items() + get_item_sets()
-    found = []
-    for descriptable in to_search:
-        if substring.lower() in descriptable.description.lower():
-            found.append(descriptable)
-    return found
-
-
-# Search desciption for matching substring.
-# This is most useful for a user keyword search.
-def get_by_keyword(substring):
-    to_search = get_items() + get_item_sets()
-    found = []
-    for descriptable in to_search:
-        if substring.lower() in descriptable.description.lower():
-            found.append(descriptable)
-    return found
+    """Search desciption for matching substring. Case insensitive.
+    This is most useful for a user keyword search.
+    """
+    substring = substring.lower()
+    return (
+        Item.query.filter(func.lower(Item.description.contains(substring))).all()
+        + ItemSet.query.filter(
+            func.lower(ItemSet.description.contains(substring))
+        ).all()
+    )
 
 
 def get_itemsets_by_item_id(item_id):
@@ -134,6 +125,8 @@ def add_item(description, price, date_sold=None):
 
 
 def update_set(reference_item):
+    """Update the set that the passed item belongs to by removing it from the set and adding a similar item."""
+
     # We need the owning item set to avoid adding duplicates.
     owning_itemsets = get_itemsets_by_item_id(reference_item.id)
     # Sort by closest match. Could also do it based of the description of the set.
diff --git a/unit_tests.py b/unit_tests.py
index f710e56eb746b72e3377a4acd42a2733a51b531e..eb6d6f744bf0e50757e89984f336737202f7d64f 100644
--- a/unit_tests.py
+++ b/unit_tests.py
@@ -1,4 +1,5 @@
 from store import app
+
 """ An instance of the database needs to exist to unit test,
 as the database is seeded from file this can be used as a test database.
 Really there should be an inmemory database for testing purposes, but