diff --git a/bookwyrm/templates/opensearch.xml b/bookwyrm/templates/opensearch.xml
new file mode 100644
index 000000000..50167144a
--- /dev/null
+++ b/bookwyrm/templates/opensearch.xml
@@ -0,0 +1,13 @@
+{% load i18n %}
+{% load static %}
+
+
+ BW
+ {% blocktrans with site_name=site.name %}{{ site_name }} search{% endblocktrans %}
+ UTF-8
+ {% if site.favicon %}{% get_media_prefix %}{{ site.favicon }}{% else %}{% static "images/favicon.ico" %}{% endif %}
+
+
+
+ {% url 'search' %}
+
diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py
index 32ef4af0d..1d34ea86c 100644
--- a/bookwyrm/urls.py
+++ b/bookwyrm/urls.py
@@ -41,6 +41,7 @@ urlpatterns = [
re_path(r"^nodeinfo/2\.0/?$", views.nodeinfo),
re_path(r"^api/v1/instance/?$", views.instance_info),
re_path(r"^api/v1/instance/peers/?$", views.peers),
+ re_path(r"^opensearch.xml$", views.opensearch, name="opensearch"),
# polling updates
re_path("^api/updates/notifications/?$", views.get_notification_count),
re_path("^api/updates/stream/(?P
[a-z]+)/?$", views.get_unread_status_count),
diff --git a/bookwyrm/views/wellknown.py b/bookwyrm/views/wellknown.py
index 426d0cdcf..71c885441 100644
--- a/bookwyrm/views/wellknown.py
+++ b/bookwyrm/views/wellknown.py
@@ -128,3 +128,8 @@ def peers(_):
def host_meta(request):
"""meta of the host"""
return TemplateResponse(request, "host_meta.xml", {"DOMAIN": DOMAIN})
+
+@require_GET
+def opensearch(request):
+ """Open Search xml spec"""
+ return TemplateResponse(request, "opensearch.xml")