diff --git a/integrations/api_repo_test.go b/integrations/api_repo_test.go
index f517ee42cd..b766dd5846 100644
--- a/integrations/api_repo_test.go
+++ b/integrations/api_repo_test.go
@@ -51,6 +51,7 @@ func TestAPISearchRepo(t *testing.T) {
 	user := models.AssertExistsAndLoadBean(t, &models.User{ID: 15}).(*models.User)
 	user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 16}).(*models.User)
 	user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 18}).(*models.User)
+	user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 20}).(*models.User)
 	orgUser := models.AssertExistsAndLoadBean(t, &models.User{ID: 17}).(*models.User)
 
 	// Map of expected results, where key is user for login
@@ -66,9 +67,9 @@ func TestAPISearchRepo(t *testing.T) {
 		expectedResults
 	}{
 		{name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50", expectedResults: expectedResults{
-			nil:   {count: 12},
-			user:  {count: 12},
-			user2: {count: 12}},
+			nil:   {count: 15},
+			user:  {count: 15},
+			user2: {count: 15}},
 		},
 		{name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10", expectedResults: expectedResults{
 			nil:   {count: 10},
@@ -81,9 +82,9 @@ func TestAPISearchRepo(t *testing.T) {
 			user2: {count: 10}},
 		},
 		{name: "RepositoriesByName", requestURL: fmt.Sprintf("/api/v1/repos/search?q=%s", "big_test_"), expectedResults: expectedResults{
-			nil:   {count: 4, repoName: "big_test_"},
-			user:  {count: 4, repoName: "big_test_"},
-			user2: {count: 4, repoName: "big_test_"}},
+			nil:   {count: 7, repoName: "big_test_"},
+			user:  {count: 7, repoName: "big_test_"},
+			user2: {count: 7, repoName: "big_test_"}},
 		},
 		{name: "RepositoriesAccessibleAndRelatedToUser", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user.ID), expectedResults: expectedResults{
 			nil:   {count: 4},
@@ -106,6 +107,34 @@ func TestAPISearchRepo(t *testing.T) {
 			user:  {count: 2, repoOwnerID: orgUser.ID, includesPrivate: true},
 			user2: {count: 1, repoOwnerID: orgUser.ID}},
 		},
+		{name: "RepositoriesAccessibleAndRelatedToUser4", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user4.ID), expectedResults: expectedResults{
+			nil:   {count: 3},
+			user:  {count: 3},
+			user4: {count: 6, includesPrivate: true}}},
+		{name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeSource", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "source"), expectedResults: expectedResults{
+			nil:   {count: 0},
+			user:  {count: 0},
+			user4: {count: 0, includesPrivate: true}}},
+		{name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeFork", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "fork"), expectedResults: expectedResults{
+			nil:   {count: 1},
+			user:  {count: 1},
+			user4: {count: 2, includesPrivate: true}}},
+		{name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeFork/Exclusive", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s&exclusive=1", user4.ID, "fork"), expectedResults: expectedResults{
+			nil:   {count: 1},
+			user:  {count: 1},
+			user4: {count: 2, includesPrivate: true}}},
+		{name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeMirror", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "mirror"), expectedResults: expectedResults{
+			nil:   {count: 2},
+			user:  {count: 2},
+			user4: {count: 4, includesPrivate: true}}},
+		{name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeMirror/Exclusive", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s&exclusive=1", user4.ID, "mirror"), expectedResults: expectedResults{
+			nil:   {count: 1},
+			user:  {count: 1},
+			user4: {count: 2, includesPrivate: true}}},
+		{name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeCollaborative", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "collaborative"), expectedResults: expectedResults{
+			nil:   {count: 0},
+			user:  {count: 0},
+			user4: {count: 0, includesPrivate: true}}},
 	}
 
 	for _, testCase := range testCases {
@@ -113,9 +142,11 @@ func TestAPISearchRepo(t *testing.T) {
 			for userToLogin, expected := range testCase.expectedResults {
 				var session *TestSession
 				var testName string
+				var userID int64
 				if userToLogin != nil && userToLogin.ID > 0 {
 					testName = fmt.Sprintf("LoggedUser%d", userToLogin.ID)
 					session = loginUser(t, userToLogin.Name)
+					userID = userToLogin.ID
 				} else {
 					testName = "AnonymousUser"
 					session = emptyTestSession(t)
@@ -130,6 +161,11 @@ func TestAPISearchRepo(t *testing.T) {
 
 					assert.Len(t, body.Data, expected.count)
 					for _, repo := range body.Data {
+						r := getRepo(t, repo.ID)
+						hasAccess, err := models.HasAccess(userID, r, models.AccessModeRead)
+						assert.NoError(t, err)
+						assert.True(t, hasAccess)
+
 						assert.NotEmpty(t, repo.Name)
 
 						if len(expected.repoName) > 0 {
@@ -150,6 +186,15 @@ func TestAPISearchRepo(t *testing.T) {
 	}
 }
 
+var repoCache = make(map[int64]*models.Repository)
+
+func getRepo(t *testing.T, repoID int64) *models.Repository {
+	if _, ok := repoCache[repoID]; !ok {
+		repoCache[repoID] = models.AssertExistsAndLoadBean(t, &models.Repository{ID: repoID}).(*models.Repository)
+	}
+	return repoCache[repoID]
+}
+
 func TestAPIViewRepo(t *testing.T) {
 	prepareTestEnv(t)
 
diff --git a/models/fixtures/access.yml b/models/fixtures/access.yml
index 9c149b78d0..af2c8a5293 100644
--- a/models/fixtures/access.yml
+++ b/models/fixtures/access.yml
@@ -62,4 +62,16 @@
   id: 11
   user_id: 18
   repo_id: 21
-  mode: 2 # write
\ No newline at end of file
+  mode: 2 # write
+
+-
+  id: 12
+  user_id: 20
+  repo_id: 27
+  mode: 4 # owner
+  
+-
+  id: 13
+  user_id: 20
+  repo_id: 28
+  mode: 4 # owner
\ No newline at end of file
diff --git a/models/fixtures/org_user.yml b/models/fixtures/org_user.yml
index 50d8ef5e68..709a1997b9 100644
--- a/models/fixtures/org_user.yml
+++ b/models/fixtures/org_user.yml
@@ -44,4 +44,12 @@
   org_id: 17
   is_public: false
   is_owner: true
+  num_teams: 1
+
+-
+  id: 7
+  uid: 20
+  org_id: 19
+  is_public: true
+  is_owner: true
   num_teams: 1
\ No newline at end of file
diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml
index eb83dfcff7..91342d076e 100644
--- a/models/fixtures/repository.yml
+++ b/models/fixtures/repository.yml
@@ -201,6 +201,7 @@
   num_closed_pulls: 0
   num_watches: 0
   is_mirror: false
+  is_fork: false
 
 -
   id: 18
@@ -213,6 +214,7 @@
   num_pulls: 0
   num_closed_pulls: 0
   is_mirror: false
+  is_fork: false
 
 -
   id: 19
@@ -225,6 +227,7 @@
   num_pulls: 0
   num_closed_pulls: 0
   is_mirror: false
+  is_fork: false
 
 -
   id: 20
@@ -237,6 +240,7 @@
   num_pulls: 0
   num_closed_pulls: 0
   is_mirror: false
+  is_fork: false
 
 -
   id: 21
@@ -249,6 +253,7 @@
   num_pulls: 0
   num_closed_pulls: 0
   is_mirror: false
+  is_fork: false
 
 -
   id: 22
@@ -261,6 +266,7 @@
   num_pulls: 0
   num_closed_pulls: 0
   is_mirror: false
+  is_fork: false
 
 -
   id: 23
@@ -273,6 +279,7 @@
   num_pulls: 0
   num_closed_pulls: 0
   is_mirror: false
+  is_fork: false
 
 -
   id: 24
@@ -285,3 +292,90 @@
   num_pulls: 0
   num_closed_pulls: 0
   is_mirror: false
+  is_fork: false
+
+-
+  id: 25
+  owner_id: 20
+  lower_name: big_test_public_mirror_5
+  name: big_test_public_mirror_5
+  is_private: false
+  num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_watches: 0
+  is_mirror: true
+  is_fork: false
+
+-
+  id: 26
+  owner_id: 20
+  lower_name: big_test_private_mirror_5
+  name: big_test_private_mirror_5
+  is_private: true
+  num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_watches: 0
+  is_mirror: true
+  is_fork: false
+
+-
+  id: 27
+  owner_id: 19
+  lower_name: big_test_public_mirror_6
+  name: big_test_public_mirror_6
+  is_private: false
+  num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_watches: 0
+  is_mirror: true
+  num_forks: 1
+  is_fork: false
+
+-
+  id: 28
+  owner_id: 19
+  lower_name: big_test_private_mirror_6
+  name: big_test_private_mirror_6
+  is_private: true
+  num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  num_watches: 0
+  is_mirror: true
+  num_forks: 1
+  is_fork: false
+  
+-
+  id: 29
+  fork_id: 27
+  owner_id: 20
+  lower_name: big_test_public_fork_7
+  name: big_test_public_fork_7
+  is_private: false
+  num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  is_mirror: false
+  is_fork: true
+  
+-
+  id: 30
+  fork_id: 28
+  owner_id: 20
+  lower_name: big_test_private_fork_7
+  name: big_test_private_fork_7
+  is_private: true
+  num_issues: 0
+  num_closed_issues: 0
+  num_pulls: 0
+  num_closed_pulls: 0
+  is_mirror: false
+  is_fork: true
\ No newline at end of file
diff --git a/models/fixtures/team.yml b/models/fixtures/team.yml
index 2b2186deae..1d242cb5bb 100644
--- a/models/fixtures/team.yml
+++ b/models/fixtures/team.yml
@@ -37,6 +37,7 @@
   num_repos: 0
   num_members: 1
   unit_types: '[1,2,3,4,5,6,7]'
+
 -
   id: 5
   org_id: 17
@@ -45,4 +46,14 @@
   authorize: 4 # owner
   num_repos: 2
   num_members: 2
+  unit_types: '[1,2,3,4,5,6,7]'
+
+-
+  id: 6
+  org_id: 19
+  lower_name: owners
+  name: Owners
+  authorize: 4 # owner
+  num_repos: 2
+  num_members: 1
   unit_types: '[1,2,3,4,5,6,7]'
\ No newline at end of file
diff --git a/models/fixtures/team_repo.yml b/models/fixtures/team_repo.yml
index 5154453f7b..9e6d745539 100644
--- a/models/fixtures/team_repo.yml
+++ b/models/fixtures/team_repo.yml
@@ -26,4 +26,16 @@
   id: 5
   org_id: 17
   team_id: 5
-  repo_id: 24
\ No newline at end of file
+  repo_id: 24
+
+-
+  id: 6
+  org_id: 19
+  team_id: 6
+  repo_id: 27
+  
+-
+  id: 7
+  org_id: 19
+  team_id: 6
+  repo_id: 28
\ No newline at end of file
diff --git a/models/fixtures/team_user.yml b/models/fixtures/team_user.yml
index 56025bb0b7..b1dfcdfdef 100644
--- a/models/fixtures/team_user.yml
+++ b/models/fixtures/team_user.yml
@@ -38,4 +38,10 @@
   id: 7
   org_id: 17
   team_id: 5
-  uid: 18
\ No newline at end of file
+  uid: 18
+
+-
+  id: 8
+  org_id: 19
+  team_id: 6
+  uid: 20
\ No newline at end of file
diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml
index 1e06255988..60f5e8405a 100644
--- a/models/fixtures/user.yml
+++ b/models/fixtures/user.yml
@@ -281,4 +281,36 @@
   avatar: avatar18
   avatar_email: user18@example.com
   num_repos: 0
+  is_active: true
+
+-
+  id: 19
+  lower_name: user19
+  name: user19
+  full_name: User 19
+  email: user19@example.com
+  passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
+  type: 1 # organization
+  salt: ZogKvWdyEx
+  is_admin: false
+  avatar: avatar19
+  avatar_email: user19@example.com
+  num_repos: 2
+  is_active: true
+  num_members: 1
+  num_teams: 1
+
+-
+  id: 20
+  lower_name: user20
+  name: user20
+  full_name: User 20
+  email: user20@example.com
+  passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
+  type: 0 # individual
+  salt: ZogKvWdyEx
+  is_admin: false
+  avatar: avatar20
+  avatar_email: user20@example.com
+  num_repos: 4
   is_active: true
\ No newline at end of file
diff --git a/models/issue_indexer.go b/models/issue_indexer.go
index 18c6f281b2..e43dfc9fae 100644
--- a/models/issue_indexer.go
+++ b/models/issue_indexer.go
@@ -28,10 +28,11 @@ func populateIssueIndexer() error {
 	batch := indexer.IssueIndexerBatch()
 	for page := 1; ; page++ {
 		repos, _, err := SearchRepositoryByName(&SearchRepoOptions{
-			Page:     page,
-			PageSize: 10,
-			OrderBy:  SearchOrderByID,
-			Private:  true,
+			Page:        page,
+			PageSize:    10,
+			OrderBy:     SearchOrderByID,
+			Private:     true,
+			Collaborate: util.OptionalBoolFalse,
 		})
 		if err != nil {
 			return fmt.Errorf("Repositories: %v", err)
diff --git a/models/repo_list.go b/models/repo_list.go
index 2c4c66ac3e..883e3b98d5 100644
--- a/models/repo_list.go
+++ b/models/repo_list.go
@@ -8,6 +8,8 @@ import (
 	"fmt"
 	"strings"
 
+	"code.gitea.io/gitea/modules/util"
+
 	"github.com/go-xorm/builder"
 )
 
@@ -88,28 +90,28 @@ func (repos MirrorRepositoryList) LoadAttributes() error {
 }
 
 // SearchRepoOptions holds the search options
-// swagger:parameters repoSearch
 type SearchRepoOptions struct {
-	// Keyword to search
-	//
-	// in: query
-	Keyword string `json:"q"`
-	// Owner in we search search
-	//
-	// in: query
-	OwnerID     int64         `json:"uid"`
-	OrderBy     SearchOrderBy `json:"-"`
-	Private     bool          `json:"-"` // Include private repositories in results
-	Collaborate bool          `json:"-"` // Include collaborative repositories
-	Starred     bool          `json:"-"`
-	Page        int           `json:"-"`
-	IsProfile   bool          `json:"-"`
-	AllPublic   bool          `json:"-"` // Include also all public repositories
-	// Limit of result
-	//
-	// maximum: setting.ExplorePagingNum
-	// in: query
-	PageSize int `json:"limit"` // Can be smaller than or equal to setting.ExplorePagingNum
+	Keyword   string
+	OwnerID   int64
+	OrderBy   SearchOrderBy
+	Private   bool // Include private repositories in results
+	Starred   bool
+	Page      int
+	IsProfile bool
+	AllPublic bool // Include also all public repositories
+	PageSize  int  // Can be smaller than or equal to setting.ExplorePagingNum
+	// None -> include collaborative AND non-collaborative
+	// True -> include just collaborative
+	// False -> incude just non-collaborative
+	Collaborate util.OptionalBool
+	// None -> include forks AND non-forks
+	// True -> include just forks
+	// False -> include just non-forks
+	Fork util.OptionalBool
+	// None -> include mirrors AND non-mirrors
+	// True -> include just mirrors
+	// False -> include just non-mirrors
+	Mirror util.OptionalBool
 }
 
 //SearchOrderBy is used to sort the result
@@ -146,17 +148,18 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
 		cond = cond.And(builder.Eq{"is_private": false})
 	}
 
-	starred := false
+	var starred bool
 	if opts.OwnerID > 0 {
 		if opts.Starred {
 			starred = true
-			cond = builder.Eq{
-				"star.uid": opts.OwnerID,
-			}
+			cond = builder.Eq{"star.uid": opts.OwnerID}
 		} else {
-			var accessCond builder.Cond = builder.Eq{"owner_id": opts.OwnerID}
+			var accessCond = builder.NewCond()
+			if opts.Collaborate != util.OptionalBoolTrue {
+				accessCond = builder.Eq{"owner_id": opts.OwnerID}
+			}
 
-			if opts.Collaborate {
+			if opts.Collaborate != util.OptionalBoolFalse {
 				collaborateCond := builder.And(
 					builder.Expr("id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)", opts.OwnerID),
 					builder.Neq{"owner_id": opts.OwnerID})
@@ -167,18 +170,26 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
 				accessCond = accessCond.Or(collaborateCond)
 			}
 
+			if opts.AllPublic {
+				accessCond = accessCond.Or(builder.Eq{"is_private": false})
+			}
+
 			cond = cond.And(accessCond)
 		}
 	}
 
-	if opts.OwnerID > 0 && opts.AllPublic {
-		cond = cond.Or(builder.Eq{"is_private": false})
-	}
-
 	if opts.Keyword != "" {
 		cond = cond.And(builder.Like{"lower_name", strings.ToLower(opts.Keyword)})
 	}
 
+	if opts.Fork != util.OptionalBoolNone {
+		cond = cond.And(builder.Eq{"is_fork": opts.Fork == util.OptionalBoolTrue})
+	}
+
+	if opts.Mirror != util.OptionalBoolNone {
+		cond = cond.And(builder.Eq{"is_mirror": opts.Mirror == util.OptionalBoolTrue})
+	}
+
 	if len(opts.OrderBy) == 0 {
 		opts.OrderBy = SearchOrderByAlphabetically
 	}
diff --git a/models/repo_list_test.go b/models/repo_list_test.go
index 4d125633a5..3bccb1aebe 100644
--- a/models/repo_list_test.go
+++ b/models/repo_list_test.go
@@ -7,6 +7,8 @@ package models
 import (
 	"testing"
 
+	"code.gitea.io/gitea/modules/util"
+
 	"github.com/stretchr/testify/assert"
 )
 
@@ -15,9 +17,10 @@ func TestSearchRepositoryByName(t *testing.T) {
 
 	// test search public repository on explore page
 	repos, count, err := SearchRepositoryByName(&SearchRepoOptions{
-		Keyword:  "repo_12",
-		Page:     1,
-		PageSize: 10,
+		Keyword:     "repo_12",
+		Page:        1,
+		PageSize:    10,
+		Collaborate: util.OptionalBoolFalse,
 	})
 
 	assert.NoError(t, err)
@@ -27,9 +30,10 @@ func TestSearchRepositoryByName(t *testing.T) {
 	assert.Equal(t, int64(1), count)
 
 	repos, count, err = SearchRepositoryByName(&SearchRepoOptions{
-		Keyword:  "test_repo",
-		Page:     1,
-		PageSize: 10,
+		Keyword:     "test_repo",
+		Page:        1,
+		PageSize:    10,
+		Collaborate: util.OptionalBoolFalse,
 	})
 
 	assert.NoError(t, err)
@@ -38,10 +42,11 @@ func TestSearchRepositoryByName(t *testing.T) {
 
 	// test search private repository on explore page
 	repos, count, err = SearchRepositoryByName(&SearchRepoOptions{
-		Keyword:  "repo_13",
-		Page:     1,
-		PageSize: 10,
-		Private:  true,
+		Keyword:     "repo_13",
+		Page:        1,
+		PageSize:    10,
+		Private:     true,
+		Collaborate: util.OptionalBoolFalse,
 	})
 
 	assert.NoError(t, err)
@@ -51,84 +56,110 @@ func TestSearchRepositoryByName(t *testing.T) {
 	assert.Equal(t, int64(1), count)
 
 	repos, count, err = SearchRepositoryByName(&SearchRepoOptions{
-		Keyword:  "test_repo",
-		Page:     1,
-		PageSize: 10,
-		Private:  true,
+		Keyword:     "test_repo",
+		Page:        1,
+		PageSize:    10,
+		Private:     true,
+		Collaborate: util.OptionalBoolFalse,
 	})
 
 	assert.NoError(t, err)
 	assert.Equal(t, int64(3), count)
 	assert.Len(t, repos, 3)
 
+	// Test non existing owner
+	repos, count, err = SearchRepositoryByName(&SearchRepoOptions{OwnerID: NonexistentID})
+
+	assert.NoError(t, err)
+	assert.Empty(t, repos)
+	assert.Equal(t, int64(0), count)
+
 	testCases := []struct {
 		name  string
 		opts  *SearchRepoOptions
 		count int
 	}{
 		{name: "PublicRepositoriesByName",
-			opts:  &SearchRepoOptions{Keyword: "big_test_", PageSize: 10},
-			count: 4},
+			opts:  &SearchRepoOptions{Keyword: "big_test_", PageSize: 10, Collaborate: util.OptionalBoolFalse},
+			count: 7},
 		{name: "PublicAndPrivateRepositoriesByName",
-			opts:  &SearchRepoOptions{Keyword: "big_test_", Page: 1, PageSize: 10, Private: true},
-			count: 8},
+			opts:  &SearchRepoOptions{Keyword: "big_test_", Page: 1, PageSize: 10, Private: true, Collaborate: util.OptionalBoolFalse},
+			count: 14},
 		{name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitFirstPage",
-			opts:  &SearchRepoOptions{Keyword: "big_test_", Page: 1, PageSize: 5, Private: true},
-			count: 8},
+			opts:  &SearchRepoOptions{Keyword: "big_test_", Page: 1, PageSize: 5, Private: true, Collaborate: util.OptionalBoolFalse},
+			count: 14},
 		{name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitSecondPage",
-			opts:  &SearchRepoOptions{Keyword: "big_test_", Page: 2, PageSize: 5, Private: true},
-			count: 8},
+			opts:  &SearchRepoOptions{Keyword: "big_test_", Page: 2, PageSize: 5, Private: true, Collaborate: util.OptionalBoolFalse},
+			count: 14},
+		{name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitThirdPage",
+			opts:  &SearchRepoOptions{Keyword: "big_test_", Page: 3, PageSize: 5, Private: true, Collaborate: util.OptionalBoolFalse},
+			count: 14},
+		{name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitFourthPage",
+			opts:  &SearchRepoOptions{Keyword: "big_test_", Page: 3, PageSize: 5, Private: true, Collaborate: util.OptionalBoolFalse},
+			count: 14},
 		{name: "PublicRepositoriesOfUser",
-			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15},
+			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Collaborate: util.OptionalBoolFalse},
 			count: 2},
 		{name: "PublicRepositoriesOfUser2",
-			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 18},
+			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 18, Collaborate: util.OptionalBoolFalse},
 			count: 0},
+		{name: "PublicRepositoriesOfUser3",
+			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 20, Collaborate: util.OptionalBoolFalse},
+			count: 2},
 		{name: "PublicAndPrivateRepositoriesOfUser",
-			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true},
+			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, Collaborate: util.OptionalBoolFalse},
 			count: 4},
 		{name: "PublicAndPrivateRepositoriesOfUser2",
-			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 18, Private: true},
+			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 18, Private: true, Collaborate: util.OptionalBoolFalse},
 			count: 0},
+		{name: "PublicAndPrivateRepositoriesOfUser3",
+			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 20, Private: true, Collaborate: util.OptionalBoolFalse},
+			count: 4},
 		{name: "PublicRepositoriesOfUserIncludingCollaborative",
-			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Collaborate: true},
+			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15},
 			count: 4},
 		{name: "PublicRepositoriesOfUser2IncludingCollaborative",
-			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 18, Collaborate: true},
+			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 18},
 			count: 1},
+		{name: "PublicRepositoriesOfUser3IncludingCollaborative",
+			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 20},
+			count: 3},
 		{name: "PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
-			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, Collaborate: true},
+			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true},
 			count: 8},
 		{name: "PublicAndPrivateRepositoriesOfUser2IncludingCollaborative",
-			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 18, Private: true, Collaborate: true},
+			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 18, Private: true},
 			count: 4},
+		{name: "PublicAndPrivateRepositoriesOfUser3IncludingCollaborative",
+			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 20, Private: true},
+			count: 6},
 		{name: "PublicRepositoriesOfOrganization",
-			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17},
+			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, Collaborate: util.OptionalBoolFalse},
 			count: 1},
 		{name: "PublicAndPrivateRepositoriesOfOrganization",
-			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, Private: true},
+			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, Private: true, Collaborate: util.OptionalBoolFalse},
 			count: 2},
 		{name: "AllPublic/PublicRepositoriesByName",
-			opts:  &SearchRepoOptions{Keyword: "big_test_", PageSize: 10, AllPublic: true},
-			count: 4},
+			opts:  &SearchRepoOptions{Keyword: "big_test_", PageSize: 10, AllPublic: true, Collaborate: util.OptionalBoolFalse},
+			count: 7},
 		{name: "AllPublic/PublicAndPrivateRepositoriesByName",
-			opts:  &SearchRepoOptions{Keyword: "big_test_", Page: 1, PageSize: 10, Private: true, AllPublic: true},
-			count: 8},
+			opts:  &SearchRepoOptions{Keyword: "big_test_", Page: 1, PageSize: 10, Private: true, AllPublic: true, Collaborate: util.OptionalBoolFalse},
+			count: 14},
 		{name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
-			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Collaborate: true, AllPublic: true},
-			count: 12},
+			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, AllPublic: true},
+			count: 15},
 		{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
-			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, Collaborate: true, AllPublic: true},
-			count: 16},
+			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
+			count: 19},
 		{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
-			opts:  &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, Collaborate: true, AllPublic: true},
-			count: 10},
+			opts:  &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, AllPublic: true},
+			count: 13},
 		{name: "AllPublic/PublicAndPrivateRepositoriesOfUser2IncludingCollaborativeByName",
-			opts:  &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 18, Private: true, Collaborate: true, AllPublic: true},
-			count: 8},
+			opts:  &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 18, Private: true, AllPublic: true},
+			count: 11},
 		{name: "AllPublic/PublicRepositoriesOfOrganization",
-			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true},
-			count: 12},
+			opts:  &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse},
+			count: 15},
 	}
 
 	for _, testCase := range testCases {
@@ -138,27 +169,54 @@ func TestSearchRepositoryByName(t *testing.T) {
 			assert.NoError(t, err)
 			assert.Equal(t, int64(testCase.count), count)
 
-			var expectedLen int
-			if testCase.opts.PageSize*testCase.opts.Page > testCase.count {
-				expectedLen = testCase.count % testCase.opts.PageSize
-			} else {
-				expectedLen = testCase.opts.PageSize
+			page := testCase.opts.Page
+			if page <= 0 {
+				page = 1
 			}
-			assert.Len(t, repos, expectedLen)
+			var expectedLen = testCase.opts.PageSize
+			if testCase.opts.PageSize*page > testCase.count+testCase.opts.PageSize {
+				expectedLen = 0
+			} else if testCase.opts.PageSize*page > testCase.count {
+				expectedLen = testCase.count % testCase.opts.PageSize
+			}
+			if assert.Len(t, repos, expectedLen) {
+				for _, repo := range repos {
+					assert.NotEmpty(t, repo.Name)
 
-			for _, repo := range repos {
-				assert.NotEmpty(t, repo.Name)
+					if len(testCase.opts.Keyword) > 0 {
+						assert.Contains(t, repo.Name, testCase.opts.Keyword)
+					}
 
-				if len(testCase.opts.Keyword) > 0 {
-					assert.Contains(t, repo.Name, testCase.opts.Keyword)
-				}
+					if !testCase.opts.Private {
+						assert.False(t, repo.IsPrivate)
+					}
 
-				if testCase.opts.OwnerID > 0 && !testCase.opts.Collaborate && !testCase.opts.AllPublic {
-					assert.Equal(t, testCase.opts.OwnerID, repo.Owner.ID)
-				}
+					if testCase.opts.Fork == util.OptionalBoolTrue && testCase.opts.Mirror == util.OptionalBoolTrue {
+						assert.True(t, repo.IsFork || repo.IsMirror)
+					} else {
+						switch testCase.opts.Fork {
+						case util.OptionalBoolFalse:
+							assert.False(t, repo.IsFork)
+						case util.OptionalBoolTrue:
+							assert.True(t, repo.IsFork)
+						}
 
-				if !testCase.opts.Private {
-					assert.False(t, repo.IsPrivate)
+						switch testCase.opts.Mirror {
+						case util.OptionalBoolFalse:
+							assert.False(t, repo.IsMirror)
+						case util.OptionalBoolTrue:
+							assert.True(t, repo.IsMirror)
+						}
+					}
+
+					if testCase.opts.OwnerID > 0 && !testCase.opts.AllPublic {
+						switch testCase.opts.Collaborate {
+						case util.OptionalBoolFalse:
+							assert.Equal(t, testCase.opts.OwnerID, repo.Owner.ID)
+						case util.OptionalBoolTrue:
+							assert.NotEqual(t, testCase.opts.OwnerID, repo.Owner.ID)
+						}
+					}
 				}
 			}
 		})
diff --git a/models/user_test.go b/models/user_test.go
index 7ac9ebb0f5..03ab54aaf7 100644
--- a/models/user_test.go
+++ b/models/user_test.go
@@ -63,7 +63,10 @@ func TestSearchUsers(t *testing.T) {
 	testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 2, PageSize: 2},
 		[]int64{7, 17})
 
-	testOrgSuccess(&SearchUserOptions{Page: 3, PageSize: 2},
+	testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 3, PageSize: 2},
+		[]int64{19})
+
+	testOrgSuccess(&SearchUserOptions{Page: 4, PageSize: 2},
 		[]int64{})
 
 	// test users
@@ -73,13 +76,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})
+		[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20})
 
 	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})
+		[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20})
 
 	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/public/swagger.v1.json b/public/swagger.v1.json
index e640b4e831..c269fcb864 100644
--- a/public/swagger.v1.json
+++ b/public/swagger.v1.json
@@ -1102,7 +1102,7 @@
             "type": "integer",
             "format": "int64",
             "x-go-name": "OwnerID",
-            "description": "Owner in we search search",
+            "description": "Repository owner to search",
             "name": "uid",
             "in": "query"
           },
@@ -1113,12 +1113,29 @@
             "description": "Limit of result\n\nmaximum: setting.ExplorePagingNum",
             "name": "limit",
             "in": "query"
+          },
+          {
+            "type": "string",
+            "x-go-name": "SearchMode",
+            "description": "Type of repository to search, related to owner",
+            "name": "mode",
+            "in": "query"
+          },
+          {
+            "type": "boolean",
+            "x-go-name": "OwnerExclusive",
+            "description": "Search only owners repositories\nHas effect only if owner is provided and mode is not \"collaborative\"",
+            "name": "exclusive",
+            "in": "query"
           }
         ],
         "responses": {
           "200": {
             "$ref": "#/responses/SearchResults"
           },
+          "422": {
+            "$ref": "#/responses/validationError"
+          },
           "500": {
             "$ref": "#/responses/SearchError"
           }
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go
index 30e1186c0a..34f4c5fa16 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -6,6 +6,7 @@ package repo
 
 import (
 	"fmt"
+	"net/http"
 	"strings"
 
 	api "code.gitea.io/sdk/gitea"
@@ -15,9 +16,37 @@ import (
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/routers/api/v1/convert"
 )
 
+// SearchRepoOption options when searching repositories
+// swagger:parameters repoSearch
+type SearchRepoOption struct { // TODO: Move SearchRepoOption to Gitea SDK
+	// Keyword to search
+	//
+	// in: query
+	Keyword string `json:"q"`
+	// Repository owner to search
+	//
+	// in: query
+	OwnerID int64 `json:"uid"`
+	// Limit of result
+	//
+	// maximum: setting.ExplorePagingNum
+	// in: query
+	PageSize int `json:"limit"`
+	// Type of repository to search, related to owner
+	//
+	// in: query
+	SearchMode string `json:"mode"`
+	// Search only owners repositories
+	// Has effect only if owner is provided and mode is not "collaborative"
+	//
+	// in: query
+	OwnerExclusive bool `json:"exclusive"`
+}
+
 // Search repositories via options
 func Search(ctx *context.APIContext) {
 	// swagger:route GET /repos/search repository repoSearch
@@ -27,20 +56,44 @@ func Search(ctx *context.APIContext) {
 	//
 	//     Responses:
 	//       200: SearchResults
+	//       422: validationError
 	//       500: SearchError
 
 	opts := &models.SearchRepoOptions{
-		Keyword:  strings.Trim(ctx.Query("q"), " "),
-		OwnerID:  ctx.QueryInt64("uid"),
-		PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")),
+		Keyword:     strings.Trim(ctx.Query("q"), " "),
+		OwnerID:     ctx.QueryInt64("uid"),
+		PageSize:    convert.ToCorrectPageSize(ctx.QueryInt("limit")),
+		Collaborate: util.OptionalBoolNone,
 	}
 
+	if ctx.QueryBool("exclusive") {
+		opts.Collaborate = util.OptionalBoolFalse
+	}
+
+	var mode = ctx.Query("mode")
+	switch mode {
+	case "source":
+		opts.Fork = util.OptionalBoolFalse
+		opts.Mirror = util.OptionalBoolFalse
+	case "fork":
+		opts.Fork = util.OptionalBoolTrue
+	case "mirror":
+		opts.Mirror = util.OptionalBoolTrue
+	case "collaborative":
+		opts.Mirror = util.OptionalBoolFalse
+		opts.Collaborate = util.OptionalBoolTrue
+	case "":
+	default:
+		ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid search mode: \"%s\"", mode))
+		return
+	}
+
+	var err error
 	if opts.OwnerID > 0 {
 		var repoOwner *models.User
 		if ctx.User != nil && ctx.User.ID == opts.OwnerID {
 			repoOwner = ctx.User
 		} else {
-			var err error
 			repoOwner, err = models.GetUserByID(opts.OwnerID)
 			if err != nil {
 				ctx.JSON(500, api.SearchError{
@@ -51,8 +104,8 @@ func Search(ctx *context.APIContext) {
 			}
 		}
 
-		if !repoOwner.IsOrganization() {
-			opts.Collaborate = true
+		if repoOwner.IsOrganization() {
+			opts.Collaborate = util.OptionalBoolFalse
 		}
 
 		// Check visibility.
diff --git a/routers/home.go b/routers/home.go
index d653d1e843..ce4e0be98d 100644
--- a/routers/home.go
+++ b/routers/home.go
@@ -108,14 +108,13 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
 	keyword := strings.Trim(ctx.Query("q"), " ")
 
 	repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{
-		Page:        page,
-		PageSize:    opts.PageSize,
-		OrderBy:     orderBy,
-		Private:     opts.Private,
-		Keyword:     keyword,
-		OwnerID:     opts.OwnerID,
-		Collaborate: true,
-		AllPublic:   true,
+		Page:      page,
+		PageSize:  opts.PageSize,
+		OrderBy:   orderBy,
+		Private:   opts.Private,
+		Keyword:   keyword,
+		OwnerID:   opts.OwnerID,
+		AllPublic: true,
 	})
 	if err != nil {
 		ctx.Handle(500, "SearchRepositoryByName", err)
diff --git a/routers/user/profile.go b/routers/user/profile.go
index b0eab09333..86819de251 100644
--- a/routers/user/profile.go
+++ b/routers/user/profile.go
@@ -15,6 +15,7 @@ import (
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/routers/repo"
 )
 
@@ -157,13 +158,14 @@ func Profile(ctx *context.Context) {
 			}
 		} else {
 			repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{
-				Keyword:  keyword,
-				OwnerID:  ctxUser.ID,
-				OrderBy:  orderBy,
-				Private:  showPrivate,
-				Page:     page,
-				PageSize: setting.UI.User.RepoPagingNum,
-				Starred:  true,
+				Keyword:     keyword,
+				OwnerID:     ctxUser.ID,
+				OrderBy:     orderBy,
+				Private:     showPrivate,
+				Page:        page,
+				PageSize:    setting.UI.User.RepoPagingNum,
+				Starred:     true,
+				Collaborate: util.OptionalBoolFalse,
 			})
 			if err != nil {
 				ctx.Handle(500, "SearchRepositoryByName", err)
@@ -199,14 +201,13 @@ func Profile(ctx *context.Context) {
 			ctx.Data["Total"] = total
 		} else {
 			repos, count, err = models.SearchRepositoryByName(&models.SearchRepoOptions{
-				Keyword:     keyword,
-				OwnerID:     ctxUser.ID,
-				OrderBy:     orderBy,
-				Private:     showPrivate,
-				Page:        page,
-				IsProfile:   true,
-				PageSize:    setting.UI.User.RepoPagingNum,
-				Collaborate: true,
+				Keyword:   keyword,
+				OwnerID:   ctxUser.ID,
+				OrderBy:   orderBy,
+				Private:   showPrivate,
+				Page:      page,
+				IsProfile: true,
+				PageSize:  setting.UI.User.RepoPagingNum,
 			})
 			if err != nil {
 				ctx.Handle(500, "SearchRepositoryByName", err)