diff --git a/integrations/api_admin_test.go b/integrations/api_admin_test.go
index f801b08d39..690edad757 100644
--- a/integrations/api_admin_test.go
+++ b/integrations/api_admin_test.go
@@ -9,10 +9,10 @@ import (
 	"net/http"
 	"testing"
 
-	"github.com/stretchr/testify/assert"
-
 	"code.gitea.io/gitea/models"
 	api "code.gitea.io/sdk/gitea"
+
+	"github.com/stretchr/testify/assert"
 )
 
 func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) {
diff --git a/integrations/api_issue_test.go b/integrations/api_issue_test.go
index 97207f3368..0ac2f3b67e 100644
--- a/integrations/api_issue_test.go
+++ b/integrations/api_issue_test.go
@@ -5,13 +5,13 @@
 package integrations
 
 import (
+	"fmt"
 	"net/http"
 	"testing"
 
 	"code.gitea.io/gitea/models"
 	api "code.gitea.io/sdk/gitea"
 
-	"fmt"
 	"github.com/stretchr/testify/assert"
 )
 
diff --git a/integrations/api_repo_test.go b/integrations/api_repo_test.go
index 3fa2ae21dc..62237e2be3 100644
--- a/integrations/api_repo_test.go
+++ b/integrations/api_repo_test.go
@@ -67,9 +67,9 @@ func TestAPISearchRepo(t *testing.T) {
 		expectedResults
 	}{
 		{name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50", expectedResults: expectedResults{
-			nil:   {count: 17},
-			user:  {count: 17},
-			user2: {count: 17}},
+			nil:   {count: 19},
+			user:  {count: 19},
+			user2: {count: 19}},
 		},
 		{name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10", expectedResults: expectedResults{
 			nil:   {count: 10},
diff --git a/models/fixtures/repo_topic.yml b/models/fixtures/repo_topic.yml
index 58937031cd..7041ccfd09 100644
--- a/models/fixtures/repo_topic.yml
+++ b/models/fixtures/repo_topic.yml
@@ -9,3 +9,11 @@
 -
   repo_id: 1
   topic_id: 3
+
+-
+  repo_id: 33
+  topic_id: 1
+
+-
+  repo_id: 33
+  topic_id: 4
diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml
index 54f3ed7b09..c2987b9658 100644
--- a/models/fixtures/repository.yml
+++ b/models/fixtures/repository.yml
@@ -407,3 +407,25 @@
   lower_name: utf8
   name: utf8
   is_private: false
+
+-
+  id: 34
+  owner_id: 21
+  lower_name: golang
+  name: golang
+  is_private: false
+  num_stars: 0
+  num_forks: 0
+  num_issues: 0
+  is_mirror: false
+
+-
+  id: 35
+  owner_id: 21
+  lower_name: graphql
+  name: graphql
+  is_private: false
+  num_stars: 0
+  num_forks: 0
+  num_issues: 0
+  is_mirror: false
diff --git a/models/fixtures/topic.yml b/models/fixtures/topic.yml
index b6b94ff4d9..c868b207cb 100644
--- a/models/fixtures/topic.yml
+++ b/models/fixtures/topic.yml
@@ -1,7 +1,7 @@
 -
   id: 1
   name: golang
-  repo_count: 1
+  repo_count: 2
 
 -
   id: 2
@@ -11,3 +11,7 @@
 - id: 3
   name: SQL
   repo_count: 1
+
+- id: 4
+  name: graphql
+  repo_count: 1
diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml
index 55606e42b9..b3850e3599 100644
--- a/models/fixtures/user.yml
+++ b/models/fixtures/user.yml
@@ -314,3 +314,18 @@
   avatar_email: user20@example.com
   num_repos: 4
   is_active: true
+
+-
+  id: 21
+  lower_name: user21
+  name: user21
+  full_name: User 21
+  email: user21@example.com
+  passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
+  type: 0 # individual
+  salt: ZogKvWdyEx
+  is_admin: false
+  avatar: avatar21
+  avatar_email: user21@example.com
+  num_repos: 2
+  is_active: true
diff --git a/models/repo_list.go b/models/repo_list.go
index b1527b73c9..c7e292d8a9 100644
--- a/models/repo_list.go
+++ b/models/repo_list.go
@@ -131,6 +131,8 @@ type SearchRepoOptions struct {
 	// True -> include just mirrors
 	// False -> include just non-mirrors
 	Mirror util.OptionalBool
+	// only search topic name
+	TopicOnly bool
 }
 
 //SearchOrderBy is used to sort the result
@@ -184,7 +186,7 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
 
 			if opts.Collaborate != util.OptionalBoolFalse {
 				collaborateCond := builder.And(
-					builder.Expr("id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)", opts.OwnerID),
+					builder.Expr("repository.id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)", opts.OwnerID),
 					builder.Neq{"owner_id": opts.OwnerID})
 				if !opts.Private {
 					collaborateCond = collaborateCond.And(builder.Expr("owner_id NOT IN (SELECT org_id FROM org_user WHERE org_user.uid = ? AND org_user.is_public = ?)", opts.OwnerID, false))
@@ -202,7 +204,14 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
 	}
 
 	if opts.Keyword != "" {
-		cond = cond.And(builder.Like{"lower_name", strings.ToLower(opts.Keyword)})
+		var keywordCond = builder.NewCond()
+		if opts.TopicOnly {
+			keywordCond = keywordCond.Or(builder.Like{"topic.name", strings.ToLower(opts.Keyword)})
+		} else {
+			keywordCond = keywordCond.Or(builder.Like{"lower_name", strings.ToLower(opts.Keyword)})
+			keywordCond = keywordCond.Or(builder.Like{"topic.name", strings.ToLower(opts.Keyword)})
+		}
+		cond = cond.And(keywordCond)
 	}
 
 	if opts.Fork != util.OptionalBoolNone {
@@ -224,9 +233,15 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
 		sess.Join("INNER", "star", "star.repo_id = repository.id")
 	}
 
+	if opts.Keyword != "" {
+		sess.Join("LEFT", "repo_topic", "repo_topic.repo_id = repository.id")
+		sess.Join("LEFT", "topic", "repo_topic.topic_id = topic.id")
+	}
+
 	count, err := sess.
 		Where(cond).
 		Count(new(Repository))
+
 	if err != nil {
 		return nil, 0, fmt.Errorf("Count: %v", err)
 	}
@@ -236,11 +251,23 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
 		sess.Join("INNER", "star", "star.repo_id = repository.id")
 	}
 
+	if opts.Keyword != "" {
+		sess.Join("LEFT", "repo_topic", "repo_topic.repo_id = repository.id")
+		sess.Join("LEFT", "topic", "repo_topic.topic_id = topic.id")
+	}
+
+	if opts.Keyword != "" {
+		sess.Select("repository.*")
+		sess.GroupBy("repository.id")
+		sess.OrderBy("repository." + opts.OrderBy.String())
+	} else {
+		sess.OrderBy(opts.OrderBy.String())
+	}
+
 	repos := make(RepositoryList, 0, opts.PageSize)
 	if err = sess.
 		Where(cond).
 		Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
-		OrderBy(opts.OrderBy.String()).
 		Find(&repos); err != nil {
 		return nil, 0, fmt.Errorf("Repo: %v", err)
 	}
diff --git a/models/repo_list_test.go b/models/repo_list_test.go
index 4b5d659ce2..8f4947dbb2 100644
--- a/models/repo_list_test.go
+++ b/models/repo_list_test.go
@@ -147,10 +147,10 @@ func TestSearchRepositoryByName(t *testing.T) {
 			count: 14},
 		{name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
 			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true},
-			count: 17},
+			count: 19},
 		{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
 			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
-			count: 21},
+			count: 23},
 		{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
 			opts:  &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
 			count: 13},
@@ -159,7 +159,7 @@ func TestSearchRepositoryByName(t *testing.T) {
 			count: 11},
 		{name: "AllPublic/PublicRepositoriesOfOrganization",
 			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse},
-			count: 17},
+			count: 19},
 	}
 
 	for _, testCase := range testCases {
@@ -222,3 +222,28 @@ func TestSearchRepositoryByName(t *testing.T) {
 		})
 	}
 }
+
+func TestSearchRepositoryByTopicName(t *testing.T) {
+	assert.NoError(t, PrepareTestDatabase())
+
+	testCases := []struct {
+		name  string
+		opts  *SearchRepoOptions
+		count int
+	}{
+		{name: "AllPublic/SearchPublicRepositoriesFromTopicAndName",
+			opts:  &SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql"},
+			count: 2},
+		{name: "AllPublic/OnlySearchPublicRepositoriesFromTopic",
+			opts:  &SearchRepoOptions{OwnerID: 21, AllPublic: true, Keyword: "graphql", TopicOnly: true},
+			count: 1},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			_, count, err := SearchRepositoryByName(testCase.opts)
+			assert.NoError(t, err)
+			assert.Equal(t, int64(testCase.count), count)
+		})
+	}
+}
diff --git a/models/ssh_key.go b/models/ssh_key.go
index 997e8ee997..9c839755c8 100644
--- a/models/ssh_key.go
+++ b/models/ssh_key.go
@@ -18,14 +18,14 @@ import (
 	"sync"
 	"time"
 
-	"github.com/Unknwon/com"
-	"github.com/go-xorm/xorm"
-	"golang.org/x/crypto/ssh"
-
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/process"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/util"
+
+	"github.com/Unknwon/com"
+	"github.com/go-xorm/xorm"
+	"golang.org/x/crypto/ssh"
 )
 
 const (
diff --git a/models/topic_test.go b/models/topic_test.go
index ef374e557b..65e52afb12 100644
--- a/models/topic_test.go
+++ b/models/topic_test.go
@@ -15,7 +15,7 @@ func TestAddTopic(t *testing.T) {
 
 	topics, err := FindTopics(&FindTopicOptions{})
 	assert.NoError(t, err)
-	assert.EqualValues(t, 3, len(topics))
+	assert.EqualValues(t, 4, len(topics))
 
 	topics, err = FindTopics(&FindTopicOptions{
 		Limit: 2,
@@ -32,7 +32,7 @@ func TestAddTopic(t *testing.T) {
 	assert.NoError(t, SaveTopics(2, "golang"))
 	topics, err = FindTopics(&FindTopicOptions{})
 	assert.NoError(t, err)
-	assert.EqualValues(t, 3, len(topics))
+	assert.EqualValues(t, 4, len(topics))
 
 	topics, err = FindTopics(&FindTopicOptions{
 		RepoID: 2,
@@ -47,7 +47,7 @@ func TestAddTopic(t *testing.T) {
 
 	topics, err = FindTopics(&FindTopicOptions{})
 	assert.NoError(t, err)
-	assert.EqualValues(t, 4, len(topics))
+	assert.EqualValues(t, 5, len(topics))
 
 	topics, err = FindTopics(&FindTopicOptions{
 		RepoID: 2,
diff --git a/models/user_test.go b/models/user_test.go
index 20de1a64be..4713b6864c 100644
--- a/models/user_test.go
+++ b/models/user_test.go
@@ -77,13 +77,13 @@ func TestSearchUsers(t *testing.T) {
 	}
 
 	testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1},
-		[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20})
+		[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21})
 
 	testUserSuccess(&SearchUserOptions{Page: 1, IsActive: util.OptionalBoolFalse},
 		[]int64{9})
 
 	testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
-		[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20})
+		[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21})
 
 	testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
 		[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go
index 90acde2eea..e48b100af6 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -91,6 +91,7 @@ func Search(ctx *context.APIContext) {
 		OwnerID:     ctx.QueryInt64("uid"),
 		Page:        ctx.QueryInt("page"),
 		PageSize:    convert.ToCorrectPageSize(ctx.QueryInt("limit")),
+		TopicOnly:   ctx.QueryBool("topic"),
 		Collaborate: util.OptionalBoolNone,
 	}
 
diff --git a/routers/home.go b/routers/home.go
index 0aa907658c..7a23e8765e 100644
--- a/routers/home.go
+++ b/routers/home.go
@@ -122,6 +122,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
 	}
 
 	keyword := strings.Trim(ctx.Query("q"), " ")
+	topicOnly := ctx.QueryBool("topic")
 
 	repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{
 		Page:      page,
@@ -131,6 +132,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
 		Keyword:   keyword,
 		OwnerID:   opts.OwnerID,
 		AllPublic: true,
+		TopicOnly: topicOnly,
 	})
 	if err != nil {
 		ctx.ServerError("SearchRepositoryByName", err)
diff --git a/routers/user/profile.go b/routers/user/profile.go
index fb731e715c..827226d3e1 100644
--- a/routers/user/profile.go
+++ b/routers/user/profile.go
@@ -105,6 +105,8 @@ func Profile(ctx *context.Context) {
 		page = 1
 	}
 
+	topicOnly := ctx.QueryBool("topic")
+
 	var (
 		repos   []*models.Repository
 		count   int64
@@ -174,6 +176,7 @@ func Profile(ctx *context.Context) {
 				PageSize:    setting.UI.User.RepoPagingNum,
 				Starred:     true,
 				Collaborate: util.OptionalBoolFalse,
+				TopicOnly:   topicOnly,
 			})
 			if err != nil {
 				ctx.ServerError("SearchRepositoryByName", err)
@@ -217,6 +220,7 @@ func Profile(ctx *context.Context) {
 				IsProfile:   true,
 				PageSize:    setting.UI.User.RepoPagingNum,
 				Collaborate: util.OptionalBoolFalse,
+				TopicOnly:   topicOnly,
 			})
 			if err != nil {
 				ctx.ServerError("SearchRepositoryByName", err)
diff --git a/templates/explore/repo_list.tmpl b/templates/explore/repo_list.tmpl
index b8f4490c11..e769e4b7fd 100644
--- a/templates/explore/repo_list.tmpl
+++ b/templates/explore/repo_list.tmpl
@@ -20,7 +20,7 @@
 			{{if .Topics }}
 				<div>
 				{{range .Topics}}
-					{{if ne . "" }}<div class="ui green basic label topic">{{.}}</div>{{end}}
+					{{if ne . "" }}<a href="/explore/repos?q={{.}}&topic=1"><div class="ui green basic label topic">{{.}}</div></a>{{end}}
 				{{end}}
 				</div>
 			{{end}}
diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl
index f14f2b9a28..d5a1416cf6 100644
--- a/templates/repo/home.tmpl
+++ b/templates/repo/home.tmpl
@@ -24,8 +24,8 @@
 			{{end}}
 		</div>
 		<div class="ui repo-topic" id="repo-topic">
-		{{range .Topics}}<div class="ui green basic label topic" style="cursor:pointer;">{{.Name}}</div>{{end}}
-		{{if .IsRepositoryAdmin}}<a id="manage_topic" style="cursor:pointer;margin-left:10px;">{{.i18n.Tr "repo.topic.manage_topics"}}</a>{{end}}
+		{{range .Topics}}<a class="ui green basic label topic" style="cursor:pointer;" href="/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}
+		{{if .IsRepositoryAdmin}}<a id="manage_topic" style="cursor:pointer;margin-left:10px;" href="/explore/repos?q={{.Name}}&topic=1">{{.i18n.Tr "repo.topic.manage_topics"}}</a>{{end}}
 		</div>
 		{{if .IsRepositoryAdmin}}
 		<div class="ui repo-topic-edit grid form segment error" id="topic_edit" >
@@ -34,7 +34,7 @@
 					<div class="ui fluid multiple search selection dropdown">
 						<input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if lt (Add $i 1) (len $.Topics)}},{{end}}{{end}}">
 						{{range .Topics}}
-						<a class="ui green basic label topic transition visible" data-value="{{.Name}}" style="display: inline-block !important;">{{.Name}}<i class="delete icon"></i></a>
+						<a class="ui green basic label topic transition visible" data-value="{{.Name}}" style="display: inline-block !important;" href="/explore/repos?q={{.Name}}&topic=1">{{.Name}}<i class="delete icon"></i></a>
 						{{end}}
 						<div class="text"></div>
 					</div>