diff --git a/models/repo_list.go b/models/repo_list.go
index d1974d77e7..906b7548d4 100644
--- a/models/repo_list.go
+++ b/models/repo_list.go
@@ -459,6 +459,15 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
 			likes := builder.NewCond()
 			for _, v := range strings.Split(opts.Keyword, ",") {
 				likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)})
+
+				// If the string looks like "org/repo", match against that pattern too
+				if opts.TeamID == 0 && strings.Count(opts.Keyword, "/") == 1 {
+					pieces := strings.Split(opts.Keyword, "/")
+					ownerName := pieces[0]
+					repoName := pieces[1]
+					likes = likes.Or(builder.And(builder.Like{"owner_name", strings.ToLower(ownerName)}, builder.Like{"lower_name", strings.ToLower(repoName)}))
+				}
+
 				if opts.IncludeDescription {
 					likes = likes.Or(builder.Like{"LOWER(description)", strings.ToLower(v)})
 				}
@@ -549,6 +558,10 @@ func searchRepositoryByCondition(ctx context.Context, opts *SearchRepoOptions, c
 
 	if opts.PriorityOwnerID > 0 {
 		opts.OrderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_id = %d THEN 0 ELSE owner_id END, %s", opts.PriorityOwnerID, opts.OrderBy))
+	} else if strings.Count(opts.Keyword, "/") == 1 {
+		// With "owner/repo" search times, prioritise results which match the owner field
+		orgName := strings.Split(opts.Keyword, "/")[0]
+		opts.OrderBy = db.SearchOrderBy(fmt.Sprintf("CASE WHEN owner_name LIKE '%s' THEN 0 ELSE 1 END, %s", orgName, opts.OrderBy))
 	}
 
 	sess := db.GetEngine(ctx)
diff --git a/models/repo_list_test.go b/models/repo_list_test.go
index 4722b073a3..d45e10fb80 100644
--- a/models/repo_list_test.go
+++ b/models/repo_list_test.go
@@ -5,6 +5,7 @@
 package models
 
 import (
+	"strings"
 	"testing"
 
 	"code.gitea.io/gitea/models/db"
@@ -261,6 +262,16 @@ func TestSearchRepository(t *testing.T) {
 			opts:  &SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Template: util.OptionalBoolTrue},
 			count: 2,
 		},
+		{
+			name:  "OwnerSlashRepoSearch",
+			opts:  &SearchRepoOptions{Keyword: "user/repo2", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, OwnerID: 0},
+			count: 3,
+		},
+		{
+			name:  "OwnerSlashSearch",
+			opts:  &SearchRepoOptions{Keyword: "user20/", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, OwnerID: 0},
+			count: 4,
+		},
 	}
 
 	for _, testCase := range testCases {
@@ -285,7 +296,21 @@ func TestSearchRepository(t *testing.T) {
 					assert.NotEmpty(t, repo.Name)
 
 					if len(testCase.opts.Keyword) > 0 {
-						assert.Contains(t, repo.Name, testCase.opts.Keyword)
+						// Keyword match condition is different for search terms of form "owner/repo"
+						if strings.Count(testCase.opts.Keyword, "/") == 1 {
+							// May still match as a whole...
+							wholeMatch := strings.Contains(repo.Name, testCase.opts.Keyword)
+
+							pieces := strings.Split(testCase.opts.Keyword, "/")
+							ownerName := pieces[0]
+							repoName := pieces[1]
+							// ... or match in parts
+							splitMatch := strings.Contains(repo.OwnerName, ownerName) && strings.Contains(repo.Name, repoName)
+
+							assert.True(t, wholeMatch || splitMatch, "Keyword '%s' does not match repo '%s/%s'", testCase.opts.Keyword, repo.Owner.Name, repo.Name)
+						} else {
+							assert.Contains(t, repo.Name, testCase.opts.Keyword)
+						}
 					}
 
 					if !testCase.opts.Private {