diff --git a/models/fixtures/repo_transfer.yml b/models/fixtures/repo_transfer.yml
index db92c95248..b12e6b207f 100644
--- a/models/fixtures/repo_transfer.yml
+++ b/models/fixtures/repo_transfer.yml
@@ -21,3 +21,11 @@
   repo_id: 32
   created_unix: 1553610671
   updated_unix: 1553610671
+
+-
+  id: 4
+  doer_id: 3
+  recipient_id: 1
+  repo_id: 5
+  created_unix: 1553610671
+  updated_unix: 1553610671
diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go
index 7b890c9e5c..8643d0c2ca 100644
--- a/routers/api/v1/repo/transfer.go
+++ b/routers/api/v1/repo/transfer.go
@@ -108,22 +108,19 @@ func Transfer(ctx *context.APIContext) {
 	oldFullname := ctx.Repo.Repository.FullName()
 
 	if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, ctx.Repo.Repository, teams); err != nil {
-		if repo_model.IsErrRepoTransferInProgress(err) {
+		switch {
+		case repo_model.IsErrRepoTransferInProgress(err):
 			ctx.APIError(http.StatusConflict, err)
-			return
-		}
-
-		if repo_model.IsErrRepoAlreadyExist(err) {
+		case repo_model.IsErrRepoAlreadyExist(err):
 			ctx.APIError(http.StatusUnprocessableEntity, err)
+		case repo_service.IsRepositoryLimitReached(err):
+			ctx.APIError(http.StatusForbidden, err)
+		case errors.Is(err, user_model.ErrBlockedUser):
+			ctx.APIError(http.StatusForbidden, err)
+		default:
+			ctx.APIErrorInternal(err)
 			return
 		}
-
-		if errors.Is(err, user_model.ErrBlockedUser) {
-			ctx.APIError(http.StatusForbidden, err)
-		} else {
-			ctx.APIErrorInternal(err)
-		}
-		return
 	}
 
 	if ctx.Repo.Repository.Status == repo_model.RepositoryPendingTransfer {
@@ -169,6 +166,8 @@ func AcceptTransfer(ctx *context.APIContext) {
 			ctx.APIError(http.StatusNotFound, err)
 		case errors.Is(err, util.ErrPermissionDenied):
 			ctx.APIError(http.StatusForbidden, err)
+		case repo_service.IsRepositoryLimitReached(err):
+			ctx.APIError(http.StatusForbidden, err)
 		default:
 			ctx.APIErrorInternal(err)
 		}
diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go
index 54b7448a89..e260ea36dd 100644
--- a/routers/web/repo/repo.go
+++ b/routers/web/repo/repo.go
@@ -305,11 +305,15 @@ func CreatePost(ctx *context.Context) {
 }
 
 func handleActionError(ctx *context.Context, err error) {
-	if errors.Is(err, user_model.ErrBlockedUser) {
+	switch {
+	case errors.Is(err, user_model.ErrBlockedUser):
 		ctx.Flash.Error(ctx.Tr("repo.action.blocked_user"))
-	} else if errors.Is(err, util.ErrPermissionDenied) {
+	case repo_service.IsRepositoryLimitReached(err):
+		limit := err.(repo_service.LimitReachedError).Limit
+		ctx.Flash.Error(ctx.TrN(limit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", limit))
+	case errors.Is(err, util.ErrPermissionDenied):
 		ctx.HTTPError(http.StatusNotFound)
-	} else {
+	default:
 		ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.PathParam("action")), err)
 	}
 }
diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go
index a0edb1e11a..e30986e86e 100644
--- a/routers/web/repo/setting/setting.go
+++ b/routers/web/repo/setting/setting.go
@@ -848,6 +848,9 @@ func handleSettingsPostTransfer(ctx *context.Context) {
 			ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil)
 		} else if repo_model.IsErrRepoTransferInProgress(err) {
 			ctx.RenderWithErr(ctx.Tr("repo.settings.transfer_in_progress"), tplSettingsOptions, nil)
+		} else if repo_service.IsRepositoryLimitReached(err) {
+			limit := err.(repo_service.LimitReachedError).Limit
+			ctx.RenderWithErr(ctx.TrN(limit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", limit), tplSettingsOptions, nil)
 		} else if errors.Is(err, user_model.ErrBlockedUser) {
 			ctx.RenderWithErr(ctx.Tr("repo.settings.transfer.blocked_user"), tplSettingsOptions, nil)
 		} else {
diff --git a/services/repository/transfer.go b/services/repository/transfer.go
index a589bc469d..4e44b90df2 100644
--- a/services/repository/transfer.go
+++ b/services/repository/transfer.go
@@ -20,10 +20,22 @@ import (
 	"code.gitea.io/gitea/modules/gitrepo"
 	"code.gitea.io/gitea/modules/globallock"
 	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/util"
 	notify_service "code.gitea.io/gitea/services/notify"
 )
 
+type LimitReachedError struct{ Limit int }
+
+func (LimitReachedError) Error() string {
+	return "Repository limit has been reached"
+}
+
+func IsRepositoryLimitReached(err error) bool {
+	_, ok := err.(LimitReachedError)
+	return ok
+}
+
 func getRepoWorkingLockKey(repoID int64) string {
 	return fmt.Sprintf("repo_working_%d", repoID)
 }
@@ -49,6 +61,11 @@ func AcceptTransferOwnership(ctx context.Context, repo *repo_model.Repository, d
 			return err
 		}
 
+		if !doer.IsAdmin && !repoTransfer.Recipient.CanCreateRepo() {
+			limit := util.Iif(repoTransfer.Recipient.MaxRepoCreation >= 0, repoTransfer.Recipient.MaxRepoCreation, setting.Repository.MaxCreationLimit)
+			return LimitReachedError{Limit: limit}
+		}
+
 		if !repoTransfer.CanUserAcceptOrRejectTransfer(ctx, doer) {
 			return util.ErrPermissionDenied
 		}
@@ -399,6 +416,11 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use
 		return err
 	}
 
+	if !doer.IsAdmin && !newOwner.CanCreateRepo() {
+		limit := util.Iif(newOwner.MaxRepoCreation >= 0, newOwner.MaxRepoCreation, setting.Repository.MaxCreationLimit)
+		return LimitReachedError{Limit: limit}
+	}
+
 	var isDirectTransfer bool
 	oldOwnerName := repo.OwnerName
 
diff --git a/services/repository/transfer_test.go b/services/repository/transfer_test.go
index 16a8fb6e1e..bf71c7ca2e 100644
--- a/services/repository/transfer_test.go
+++ b/services/repository/transfer_test.go
@@ -1,4 +1,4 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
+// Copyright 2025 The Gitea Authors. All rights reserved.
 // SPDX-License-Identifier: MIT
 
 package repository
@@ -14,11 +14,14 @@ import (
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/test"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/services/feed"
 	notify_service "code.gitea.io/gitea/services/notify"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 var notifySync sync.Once
@@ -125,3 +128,40 @@ func TestRepositoryTransfer(t *testing.T) {
 	err = RejectRepositoryTransfer(db.DefaultContext, repo2, doer)
 	assert.True(t, repo_model.IsErrNoPendingTransfer(err))
 }
+
+// Test transfer rejections
+func TestRepositoryTransferRejection(t *testing.T) {
+	require.NoError(t, unittest.PrepareTestDatabase())
+	// Set limit to 0 repositories so no repositories can be transferred
+	defer test.MockVariableValue(&setting.Repository.MaxCreationLimit, 0)()
+
+	// Admin case
+	doerAdmin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 5})
+
+	transfer, err := repo_model.GetPendingRepositoryTransfer(db.DefaultContext, repo)
+	require.NoError(t, err)
+	require.NotNil(t, transfer)
+	require.NoError(t, transfer.LoadRecipient(db.DefaultContext))
+
+	require.True(t, transfer.Recipient.CanCreateRepo()) // admin is not subject to limits
+
+	// Administrator should not be affected by the limits so transfer should be successful
+	assert.NoError(t, AcceptTransferOwnership(db.DefaultContext, repo, doerAdmin))
+
+	// Non admin user case
+	doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10})
+	repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21})
+
+	transfer, err = repo_model.GetPendingRepositoryTransfer(db.DefaultContext, repo)
+	require.NoError(t, err)
+	require.NotNil(t, transfer)
+	require.NoError(t, transfer.LoadRecipient(db.DefaultContext))
+
+	require.False(t, transfer.Recipient.CanCreateRepo()) // regular user is subject to limits
+
+	// Cannot accept because of the limit
+	err = AcceptTransferOwnership(db.DefaultContext, repo, doer)
+	assert.Error(t, err)
+	assert.True(t, IsRepositoryLimitReached(err))
+}