diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
new file mode 100644
index 000000000..37fa3565b
--- /dev/null
+++ b/app/controllers/autocomplete_controller.rb
@@ -0,0 +1,16 @@
+class AutocompleteController < ApplicationController
+ respond_to :xml, :json
+
+ def index
+ @tags = Tag.names_matches_with_aliases(params[:query], params.fetch(:limit, 10).to_i)
+
+ if request.variant.opensearch?
+ expires_in 1.hour
+ results = [params[:query], @tags.map(&:pretty_name)]
+ respond_with(results)
+ else
+ # XXX
+ respond_with(@tags.map(&:attributes))
+ end
+ end
+end
diff --git a/app/views/static/opensearch.xml.erb b/app/views/static/opensearch.xml.erb
index 3c67241f8..8652a2f9c 100644
--- a/app/views/static/opensearch.xml.erb
+++ b/app/views/static/opensearch.xml.erb
@@ -4,4 +4,5 @@
<%= Danbooru.config.app_name %> search
<%= root_url %>favicon.ico
+
diff --git a/test/functional/autocomplete_controller_test.rb b/test/functional/autocomplete_controller_test.rb
new file mode 100644
index 000000000..51c064029
--- /dev/null
+++ b/test/functional/autocomplete_controller_test.rb
@@ -0,0 +1,17 @@
+require "test_helper"
+
+class AutocompleteControllerTest < ActionDispatch::IntegrationTest
+ context "Autocomplete controller" do
+ context "index action" do
+ setup do
+ create(:tag, name: "azur_lane")
+ end
+
+ should "work for opensearch queries" do
+ get autocomplete_index_path(query: "azur", variant: "opensearch"), as: :json
+ assert_response :success
+ assert_equal(["azur", ["azur lane"]], response.parsed_body)
+ end
+ end
+ end
+end