diff --git a/models/activities/notification.go b/models/activities/notification.go
index 2f21dc74d1..0a61088167 100644
--- a/models/activities/notification.go
+++ b/models/activities/notification.go
@@ -806,7 +806,7 @@ func getNotificationByID(ctx context.Context, notificationID int64) (*Notificati
 	}
 
 	if !ok {
-		return nil, db.ErrNotExist{ID: notificationID}
+		return nil, db.ErrNotExist{Resource: "notification", ID: notificationID}
 	}
 
 	return notification, nil
diff --git a/models/admin/task.go b/models/admin/task.go
index 07eb61decc..4fa0f10394 100644
--- a/models/admin/task.go
+++ b/models/admin/task.go
@@ -167,6 +167,10 @@ func (err ErrTaskDoesNotExist) Error() string {
 		err.ID, err.RepoID, err.Type)
 }
 
+func (err ErrTaskDoesNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // GetMigratingTask returns the migrating task by repo's id
 func GetMigratingTask(repoID int64) (*Task, error) {
 	task := Task{
diff --git a/models/asymkey/error.go b/models/asymkey/error.go
index 5d2be1f289..3ddeb0498a 100644
--- a/models/asymkey/error.go
+++ b/models/asymkey/error.go
@@ -4,7 +4,11 @@
 
 package asymkey
 
-import "fmt"
+import (
+	"fmt"
+
+	"code.gitea.io/gitea/modules/util"
+)
 
 // ErrKeyUnableVerify represents a "KeyUnableVerify" kind of error.
 type ErrKeyUnableVerify struct {
@@ -36,6 +40,10 @@ func (err ErrKeyNotExist) Error() string {
 	return fmt.Sprintf("public key does not exist [id: %d]", err.ID)
 }
 
+func (err ErrKeyNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrKeyAlreadyExist represents a "KeyAlreadyExist" kind of error.
 type ErrKeyAlreadyExist struct {
 	OwnerID     int64
@@ -54,6 +62,10 @@ func (err ErrKeyAlreadyExist) Error() string {
 		err.OwnerID, err.Fingerprint, err.Content)
 }
 
+func (err ErrKeyAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrKeyNameAlreadyUsed represents a "KeyNameAlreadyUsed" kind of error.
 type ErrKeyNameAlreadyUsed struct {
 	OwnerID int64
@@ -70,6 +82,10 @@ func (err ErrKeyNameAlreadyUsed) Error() string {
 	return fmt.Sprintf("public key already exists [owner_id: %d, name: %s]", err.OwnerID, err.Name)
 }
 
+func (err ErrKeyNameAlreadyUsed) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrGPGNoEmailFound represents a "ErrGPGNoEmailFound" kind of error.
 type ErrGPGNoEmailFound struct {
 	FailedEmails []string
@@ -132,6 +148,10 @@ func (err ErrGPGKeyNotExist) Error() string {
 	return fmt.Sprintf("public gpg key does not exist [id: %d]", err.ID)
 }
 
+func (err ErrGPGKeyNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrGPGKeyImportNotExist represents a "GPGKeyImportNotExist" kind of error.
 type ErrGPGKeyImportNotExist struct {
 	ID string
@@ -147,6 +167,10 @@ func (err ErrGPGKeyImportNotExist) Error() string {
 	return fmt.Sprintf("public gpg key import does not exist [id: %s]", err.ID)
 }
 
+func (err ErrGPGKeyImportNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrGPGKeyIDAlreadyUsed represents a "GPGKeyIDAlreadyUsed" kind of error.
 type ErrGPGKeyIDAlreadyUsed struct {
 	KeyID string
@@ -162,6 +186,10 @@ func (err ErrGPGKeyIDAlreadyUsed) Error() string {
 	return fmt.Sprintf("public key already exists [key_id: %s]", err.KeyID)
 }
 
+func (err ErrGPGKeyIDAlreadyUsed) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrGPGKeyAccessDenied represents a "GPGKeyAccessDenied" kind of Error.
 type ErrGPGKeyAccessDenied struct {
 	UserID int64
@@ -180,6 +208,10 @@ func (err ErrGPGKeyAccessDenied) Error() string {
 		err.UserID, err.KeyID)
 }
 
+func (err ErrGPGKeyAccessDenied) Unwrap() error {
+	return util.ErrPermissionDenied
+}
+
 // ErrKeyAccessDenied represents a "KeyAccessDenied" kind of error.
 type ErrKeyAccessDenied struct {
 	UserID int64
@@ -198,6 +230,10 @@ func (err ErrKeyAccessDenied) Error() string {
 		err.UserID, err.KeyID, err.Note)
 }
 
+func (err ErrKeyAccessDenied) Unwrap() error {
+	return util.ErrPermissionDenied
+}
+
 // ErrDeployKeyNotExist represents a "DeployKeyNotExist" kind of error.
 type ErrDeployKeyNotExist struct {
 	ID     int64
@@ -215,6 +251,10 @@ func (err ErrDeployKeyNotExist) Error() string {
 	return fmt.Sprintf("Deploy key does not exist [id: %d, key_id: %d, repo_id: %d]", err.ID, err.KeyID, err.RepoID)
 }
 
+func (err ErrDeployKeyNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrDeployKeyAlreadyExist represents a "DeployKeyAlreadyExist" kind of error.
 type ErrDeployKeyAlreadyExist struct {
 	KeyID  int64
@@ -231,6 +271,10 @@ func (err ErrDeployKeyAlreadyExist) Error() string {
 	return fmt.Sprintf("public key already exists [key_id: %d, repo_id: %d]", err.KeyID, err.RepoID)
 }
 
+func (err ErrDeployKeyAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrDeployKeyNameAlreadyUsed represents a "DeployKeyNameAlreadyUsed" kind of error.
 type ErrDeployKeyNameAlreadyUsed struct {
 	RepoID int64
@@ -247,6 +291,10 @@ func (err ErrDeployKeyNameAlreadyUsed) Error() string {
 	return fmt.Sprintf("public key with name already exists [repo_id: %d, name: %s]", err.RepoID, err.Name)
 }
 
+func (err ErrDeployKeyNameAlreadyUsed) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrSSHInvalidTokenSignature represents a "ErrSSHInvalidTokenSignature" kind of error.
 type ErrSSHInvalidTokenSignature struct {
 	Wrapped     error
@@ -262,3 +310,7 @@ func IsErrSSHInvalidTokenSignature(err error) bool {
 func (err ErrSSHInvalidTokenSignature) Error() string {
 	return "the provided signature does not sign the token with the provided key"
 }
+
+func (err ErrSSHInvalidTokenSignature) Unwrap() error {
+	return util.ErrInvalidArgument
+}
diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go
index abcd9e1ca6..9fdce24253 100644
--- a/models/auth/oauth2.go
+++ b/models/auth/oauth2.go
@@ -486,7 +486,7 @@ type ErrOAuthClientIDInvalid struct {
 	ClientID string
 }
 
-// IsErrOauthClientIDInvalid checks if an error is a ErrReviewNotExist.
+// IsErrOauthClientIDInvalid checks if an error is a ErrOAuthClientIDInvalid.
 func IsErrOauthClientIDInvalid(err error) bool {
 	_, ok := err.(ErrOAuthClientIDInvalid)
 	return ok
@@ -497,6 +497,11 @@ func (err ErrOAuthClientIDInvalid) Error() string {
 	return fmt.Sprintf("Client ID invalid [Client ID: %s]", err.ClientID)
 }
 
+// Unwrap unwraps this as a ErrNotExist err
+func (err ErrOAuthClientIDInvalid) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrOAuthApplicationNotFound will be thrown if id cannot be found
 type ErrOAuthApplicationNotFound struct {
 	ID int64
@@ -513,6 +518,11 @@ func (err ErrOAuthApplicationNotFound) Error() string {
 	return fmt.Sprintf("OAuth application not found [ID: %d]", err.ID)
 }
 
+// Unwrap unwraps this as a ErrNotExist err
+func (err ErrOAuthApplicationNotFound) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // GetActiveOAuth2ProviderSources returns all actived LoginOAuth2 sources
 func GetActiveOAuth2ProviderSources() ([]*Source, error) {
 	sources := make([]*Source, 0, 1)
diff --git a/models/auth/source.go b/models/auth/source.go
index 6f4f5addcb..f8be5398ae 100644
--- a/models/auth/source.go
+++ b/models/auth/source.go
@@ -12,6 +12,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/timeutil"
+	"code.gitea.io/gitea/modules/util"
 
 	"xorm.io/xorm"
 	"xorm.io/xorm/convert"
@@ -366,6 +367,11 @@ func (err ErrSourceNotExist) Error() string {
 	return fmt.Sprintf("login source does not exist [id: %d]", err.ID)
 }
 
+// Unwrap unwraps this as a ErrNotExist err
+func (err ErrSourceNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrSourceAlreadyExist represents a "SourceAlreadyExist" kind of error.
 type ErrSourceAlreadyExist struct {
 	Name string
@@ -381,6 +387,11 @@ func (err ErrSourceAlreadyExist) Error() string {
 	return fmt.Sprintf("login source already exists [name: %s]", err.Name)
 }
 
+// Unwrap unwraps this as a ErrExist err
+func (err ErrSourceAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrSourceInUse represents a "SourceInUse" kind of error.
 type ErrSourceInUse struct {
 	ID int64
diff --git a/models/auth/token.go b/models/auth/token.go
index 01654f2901..3afef832da 100644
--- a/models/auth/token.go
+++ b/models/auth/token.go
@@ -35,6 +35,10 @@ func (err ErrAccessTokenNotExist) Error() string {
 	return fmt.Sprintf("access token does not exist [sha: %s]", err.Token)
 }
 
+func (err ErrAccessTokenNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrAccessTokenEmpty represents a "AccessTokenEmpty" kind of error.
 type ErrAccessTokenEmpty struct{}
 
@@ -48,6 +52,10 @@ func (err ErrAccessTokenEmpty) Error() string {
 	return "access token is empty"
 }
 
+func (err ErrAccessTokenEmpty) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 var successfulAccessTokenCache *lru.Cache
 
 // AccessToken represents a personal access token.
diff --git a/models/auth/twofactor.go b/models/auth/twofactor.go
index c5bd972f91..736d4c340c 100644
--- a/models/auth/twofactor.go
+++ b/models/auth/twofactor.go
@@ -41,6 +41,11 @@ func (err ErrTwoFactorNotEnrolled) Error() string {
 	return fmt.Sprintf("user not enrolled in 2FA [uid: %d]", err.UID)
 }
 
+// Unwrap unwraps this as a ErrNotExist err
+func (err ErrTwoFactorNotEnrolled) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // TwoFactor represents a two-factor authentication token.
 type TwoFactor struct {
 	ID               int64 `xorm:"pk autoincr"`
diff --git a/models/auth/webauthn.go b/models/auth/webauthn.go
index d3062342f5..1575b6cbab 100644
--- a/models/auth/webauthn.go
+++ b/models/auth/webauthn.go
@@ -11,6 +11,7 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/modules/timeutil"
+	"code.gitea.io/gitea/modules/util"
 
 	"github.com/duo-labs/webauthn/webauthn"
 	"xorm.io/xorm"
@@ -29,6 +30,11 @@ func (err ErrWebAuthnCredentialNotExist) Error() string {
 	return fmt.Sprintf("WebAuthn credential does not exist [credential_id: %x]", err.CredentialID)
 }
 
+// Unwrap unwraps this as a ErrNotExist err
+func (err ErrWebAuthnCredentialNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // IsErrWebAuthnCredentialNotExist checks if an error is a ErrWebAuthnCredentialNotExist.
 func IsErrWebAuthnCredentialNotExist(err error) bool {
 	_, ok := err.(ErrWebAuthnCredentialNotExist)
diff --git a/models/db/engine.go b/models/db/engine.go
index 2c329300e3..f0c9ec46e9 100755
--- a/models/db/engine.go
+++ b/models/db/engine.go
@@ -225,7 +225,7 @@ func NamesToBean(names ...string) ([]interface{}, error) {
 	for _, name := range names {
 		bean, ok := beanMap[strings.ToLower(strings.TrimSpace(name))]
 		if !ok {
-			return nil, fmt.Errorf("No table found that matches: %s", name)
+			return nil, fmt.Errorf("no table found that matches: %s", name)
 		}
 		if !gotBean[bean] {
 			beans = append(beans, bean)
diff --git a/models/db/error.go b/models/db/error.go
index 6557229943..9577fa55db 100644
--- a/models/db/error.go
+++ b/models/db/error.go
@@ -6,6 +6,8 @@ package db
 
 import (
 	"fmt"
+
+	"code.gitea.io/gitea/modules/util"
 )
 
 // ErrCancelled represents an error due to context cancellation
@@ -45,7 +47,8 @@ func (err ErrSSHDisabled) Error() string {
 
 // ErrNotExist represents a non-exist error.
 type ErrNotExist struct {
-	ID int64
+	Resource string
+	ID       int64
 }
 
 // IsErrNotExist checks if an error is an ErrNotExist
@@ -55,5 +58,18 @@ func IsErrNotExist(err error) bool {
 }
 
 func (err ErrNotExist) Error() string {
-	return fmt.Sprintf("record does not exist [id: %d]", err.ID)
+	name := "record"
+	if err.Resource != "" {
+		name = err.Resource
+	}
+
+	if err.ID != 0 {
+		return fmt.Sprintf("%s does not exist [id: %d]", name, err.ID)
+	}
+	return fmt.Sprintf("%s does not exist", name)
+}
+
+// Unwrap unwraps this as a ErrNotExist err
+func (err ErrNotExist) Unwrap() error {
+	return util.ErrNotExist
 }
diff --git a/models/db/name.go b/models/db/name.go
index 9c9d18f184..a05d1a789b 100644
--- a/models/db/name.go
+++ b/models/db/name.go
@@ -5,16 +5,17 @@
 package db
 
 import (
-	"errors"
 	"fmt"
 	"regexp"
 	"strings"
 	"unicode/utf8"
+
+	"code.gitea.io/gitea/modules/util"
 )
 
 var (
 	// ErrNameEmpty name is empty error
-	ErrNameEmpty = errors.New("Name is empty")
+	ErrNameEmpty = util.SilentWrap{Message: "name is empty", Err: util.ErrInvalidArgument}
 
 	// AlphaDashDotPattern characters prohibited in a user name (anything except A-Za-z0-9_.-)
 	AlphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
@@ -35,6 +36,11 @@ func (err ErrNameReserved) Error() string {
 	return fmt.Sprintf("name is reserved [name: %s]", err.Name)
 }
 
+// Unwrap unwraps this as a ErrInvalid err
+func (err ErrNameReserved) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // ErrNamePatternNotAllowed represents a "pattern not allowed" error.
 type ErrNamePatternNotAllowed struct {
 	Pattern string
@@ -50,6 +56,11 @@ func (err ErrNamePatternNotAllowed) Error() string {
 	return fmt.Sprintf("name pattern is not allowed [pattern: %s]", err.Pattern)
 }
 
+// Unwrap unwraps this as a ErrInvalid err
+func (err ErrNamePatternNotAllowed) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // ErrNameCharsNotAllowed represents a "character not allowed in name" error.
 type ErrNameCharsNotAllowed struct {
 	Name string
@@ -62,7 +73,12 @@ func IsErrNameCharsNotAllowed(err error) bool {
 }
 
 func (err ErrNameCharsNotAllowed) Error() string {
-	return fmt.Sprintf("User name is invalid [%s]: must be valid alpha or numeric or dash(-_) or dot characters", err.Name)
+	return fmt.Sprintf("name is invalid [%s]: must be valid alpha or numeric or dash(-_) or dot characters", err.Name)
+}
+
+// Unwrap unwraps this as a ErrInvalid err
+func (err ErrNameCharsNotAllowed) Unwrap() error {
+	return util.ErrInvalidArgument
 }
 
 // IsUsableName checks if name is reserved or pattern of name is not allowed
diff --git a/models/error.go b/models/error.go
index 873ed0ceaa..f4c4bc8f67 100644
--- a/models/error.go
+++ b/models/error.go
@@ -10,6 +10,7 @@ import (
 
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/util"
 )
 
 // ErrUserOwnRepos represents a "UserOwnRepos" kind of error.
@@ -63,8 +64,8 @@ type ErrNoPendingRepoTransfer struct {
 	RepoID int64
 }
 
-func (e ErrNoPendingRepoTransfer) Error() string {
-	return fmt.Sprintf("repository doesn't have a pending transfer [repo_id: %d]", e.RepoID)
+func (err ErrNoPendingRepoTransfer) Error() string {
+	return fmt.Sprintf("repository doesn't have a pending transfer [repo_id: %d]", err.RepoID)
 }
 
 // IsErrNoPendingTransfer is an error type when a repository has no pending
@@ -74,6 +75,10 @@ func IsErrNoPendingTransfer(err error) bool {
 	return ok
 }
 
+func (err ErrNoPendingRepoTransfer) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrRepoTransferInProgress represents the state of a repository that has an
 // ongoing transfer
 type ErrRepoTransferInProgress struct {
@@ -91,6 +96,10 @@ func (err ErrRepoTransferInProgress) Error() string {
 	return fmt.Sprintf("repository is already being transferred [uname: %s, name: %s]", err.Uname, err.Name)
 }
 
+func (err ErrRepoTransferInProgress) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrInvalidCloneAddr represents a "InvalidCloneAddr" kind of error.
 type ErrInvalidCloneAddr struct {
 	Host               string
@@ -124,6 +133,10 @@ func (err *ErrInvalidCloneAddr) Error() string {
 	return fmt.Sprintf("migration/cloning from '%s' is not allowed", err.Host)
 }
 
+func (err *ErrInvalidCloneAddr) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // ErrUpdateTaskNotExist represents a "UpdateTaskNotExist" kind of error.
 type ErrUpdateTaskNotExist struct {
 	UUID string
@@ -139,6 +152,10 @@ func (err ErrUpdateTaskNotExist) Error() string {
 	return fmt.Sprintf("update task does not exist [uuid: %s]", err.UUID)
 }
 
+func (err ErrUpdateTaskNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrInvalidTagName represents a "InvalidTagName" kind of error.
 type ErrInvalidTagName struct {
 	TagName string
@@ -154,6 +171,10 @@ func (err ErrInvalidTagName) Error() string {
 	return fmt.Sprintf("release tag name is not valid [tag_name: %s]", err.TagName)
 }
 
+func (err ErrInvalidTagName) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // ErrProtectedTagName represents a "ProtectedTagName" kind of error.
 type ErrProtectedTagName struct {
 	TagName string
@@ -169,6 +190,10 @@ func (err ErrProtectedTagName) Error() string {
 	return fmt.Sprintf("release tag name is protected [tag_name: %s]", err.TagName)
 }
 
+func (err ErrProtectedTagName) Unwrap() error {
+	return util.ErrPermissionDenied
+}
+
 // ErrRepoFileAlreadyExists represents a "RepoFileAlreadyExist" kind of error.
 type ErrRepoFileAlreadyExists struct {
 	Path string
@@ -184,6 +209,10 @@ func (err ErrRepoFileAlreadyExists) Error() string {
 	return fmt.Sprintf("repository file already exists [path: %s]", err.Path)
 }
 
+func (err ErrRepoFileAlreadyExists) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrRepoFileDoesNotExist represents a "RepoFileDoesNotExist" kind of error.
 type ErrRepoFileDoesNotExist struct {
 	Path string
@@ -200,6 +229,10 @@ func (err ErrRepoFileDoesNotExist) Error() string {
 	return fmt.Sprintf("repository file does not exist [path: %s]", err.Path)
 }
 
+func (err ErrRepoFileDoesNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrFilenameInvalid represents a "FilenameInvalid" kind of error.
 type ErrFilenameInvalid struct {
 	Path string
@@ -215,6 +248,10 @@ func (err ErrFilenameInvalid) Error() string {
 	return fmt.Sprintf("path contains a malformed path component [path: %s]", err.Path)
 }
 
+func (err ErrFilenameInvalid) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // ErrUserCannotCommit represents "UserCannotCommit" kind of error.
 type ErrUserCannotCommit struct {
 	UserName string
@@ -230,6 +267,10 @@ func (err ErrUserCannotCommit) Error() string {
 	return fmt.Sprintf("user cannot commit to repo [user: %s]", err.UserName)
 }
 
+func (err ErrUserCannotCommit) Unwrap() error {
+	return util.ErrPermissionDenied
+}
+
 // ErrFilePathInvalid represents a "FilePathInvalid" kind of error.
 type ErrFilePathInvalid struct {
 	Message string
@@ -251,6 +292,10 @@ func (err ErrFilePathInvalid) Error() string {
 	return fmt.Sprintf("path is invalid [path: %s]", err.Path)
 }
 
+func (err ErrFilePathInvalid) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // ErrFilePathProtected represents a "FilePathProtected" kind of error.
 type ErrFilePathProtected struct {
 	Message string
@@ -270,6 +315,10 @@ func (err ErrFilePathProtected) Error() string {
 	return fmt.Sprintf("path is protected and can not be changed [path: %s]", err.Path)
 }
 
+func (err ErrFilePathProtected) Unwrap() error {
+	return util.ErrPermissionDenied
+}
+
 // __________                             .__
 // \______   \____________    ____   ____ |  |__
 //  |    |  _/\_  __ \__  \  /    \_/ ___\|  |  \
@@ -292,6 +341,10 @@ func (err ErrBranchDoesNotExist) Error() string {
 	return fmt.Sprintf("branch does not exist [name: %s]", err.BranchName)
 }
 
+func (err ErrBranchDoesNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrBranchAlreadyExists represents an error that branch with such name already exists.
 type ErrBranchAlreadyExists struct {
 	BranchName string
@@ -307,6 +360,10 @@ func (err ErrBranchAlreadyExists) Error() string {
 	return fmt.Sprintf("branch already exists [name: %s]", err.BranchName)
 }
 
+func (err ErrBranchAlreadyExists) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrBranchNameConflict represents an error that branch name conflicts with other branch.
 type ErrBranchNameConflict struct {
 	BranchName string
@@ -322,6 +379,10 @@ func (err ErrBranchNameConflict) Error() string {
 	return fmt.Sprintf("branch conflicts with existing branch [name: %s]", err.BranchName)
 }
 
+func (err ErrBranchNameConflict) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrBranchesEqual represents an error that branch name conflicts with other branch.
 type ErrBranchesEqual struct {
 	BaseBranchName string
@@ -338,6 +399,10 @@ func (err ErrBranchesEqual) Error() string {
 	return fmt.Sprintf("branches are equal [head: %sm base: %s]", err.HeadBranchName, err.BaseBranchName)
 }
 
+func (err ErrBranchesEqual) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // ErrDisallowedToMerge represents an error that a branch is protected and the current user is not allowed to modify it.
 type ErrDisallowedToMerge struct {
 	Reason string
@@ -353,6 +418,10 @@ func (err ErrDisallowedToMerge) Error() string {
 	return fmt.Sprintf("not allowed to merge [reason: %s]", err.Reason)
 }
 
+func (err ErrDisallowedToMerge) Unwrap() error {
+	return util.ErrPermissionDenied
+}
+
 // ErrTagAlreadyExists represents an error that tag with such name already exists.
 type ErrTagAlreadyExists struct {
 	TagName string
@@ -368,6 +437,10 @@ func (err ErrTagAlreadyExists) Error() string {
 	return fmt.Sprintf("tag already exists [name: %s]", err.TagName)
 }
 
+func (err ErrTagAlreadyExists) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrSHADoesNotMatch represents a "SHADoesNotMatch" kind of error.
 type ErrSHADoesNotMatch struct {
 	Path       string
@@ -400,6 +473,10 @@ func (err ErrSHANotFound) Error() string {
 	return fmt.Sprintf("sha not found [%s]", err.SHA)
 }
 
+func (err ErrSHANotFound) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrCommitIDDoesNotMatch represents a "CommitIDDoesNotMatch" kind of error.
 type ErrCommitIDDoesNotMatch struct {
 	GivenCommitID   string
@@ -446,6 +523,10 @@ func (err ErrInvalidMergeStyle) Error() string {
 		err.ID, err.Style)
 }
 
+func (err ErrInvalidMergeStyle) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // ErrMergeConflicts represents an error if merging fails with a conflict
 type ErrMergeConflicts struct {
 	Style  repo_model.MergeStyle
diff --git a/models/foreignreference/error.go b/models/foreignreference/error.go
index d783a08730..a1db773cd2 100644
--- a/models/foreignreference/error.go
+++ b/models/foreignreference/error.go
@@ -6,6 +6,8 @@ package foreignreference
 
 import (
 	"fmt"
+
+	"code.gitea.io/gitea/modules/util"
 )
 
 // ErrLocalIndexNotExist represents a "LocalIndexNotExist" kind of error.
@@ -25,6 +27,10 @@ func (err ErrLocalIndexNotExist) Error() string {
 	return fmt.Sprintf("repository %d has no LocalIndex for ForeignIndex %d of type %s", err.RepoID, err.ForeignIndex, err.Type)
 }
 
+func (err ErrLocalIndexNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrForeignIndexNotExist represents a "ForeignIndexNotExist" kind of error.
 type ErrForeignIndexNotExist struct {
 	RepoID     int64
@@ -41,3 +47,7 @@ func IsErrForeignIndexNotExist(err error) bool {
 func (err ErrForeignIndexNotExist) Error() string {
 	return fmt.Sprintf("repository %d has no ForeignIndex for LocalIndex %d of type %s", err.RepoID, err.LocalIndex, err.Type)
 }
+
+func (err ErrForeignIndexNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
diff --git a/models/git/lfs.go b/models/git/lfs.go
index 179da3120a..1dab31d5f9 100644
--- a/models/git/lfs.go
+++ b/models/git/lfs.go
@@ -6,7 +6,6 @@ package git
 
 import (
 	"context"
-	"errors"
 	"fmt"
 
 	"code.gitea.io/gitea/models/db"
@@ -17,6 +16,7 @@ import (
 	"code.gitea.io/gitea/modules/lfs"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/timeutil"
+	"code.gitea.io/gitea/modules/util"
 
 	"xorm.io/builder"
 )
@@ -38,6 +38,10 @@ func (err ErrLFSLockNotExist) Error() string {
 	return fmt.Sprintf("lfs lock does not exist [id: %d, rid: %d, path: %s]", err.ID, err.RepoID, err.Path)
 }
 
+func (err ErrLFSLockNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrLFSUnauthorizedAction represents a "LFSUnauthorizedAction" kind of error.
 type ErrLFSUnauthorizedAction struct {
 	RepoID   int64
@@ -58,6 +62,10 @@ func (err ErrLFSUnauthorizedAction) Error() string {
 	return fmt.Sprintf("User %s doesn't have read access for lfs lock [rid: %d]", err.UserName, err.RepoID)
 }
 
+func (err ErrLFSUnauthorizedAction) Unwrap() error {
+	return util.ErrPermissionDenied
+}
+
 // ErrLFSLockAlreadyExist represents a "LFSLockAlreadyExist" kind of error.
 type ErrLFSLockAlreadyExist struct {
 	RepoID int64
@@ -74,6 +82,10 @@ func (err ErrLFSLockAlreadyExist) Error() string {
 	return fmt.Sprintf("lfs lock already exists [rid: %d, path: %s]", err.RepoID, err.Path)
 }
 
+func (err ErrLFSLockAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrLFSFileLocked represents a "LFSFileLocked" kind of error.
 type ErrLFSFileLocked struct {
 	RepoID   int64
@@ -91,6 +103,10 @@ func (err ErrLFSFileLocked) Error() string {
 	return fmt.Sprintf("File is lfs locked [repo: %d, locked by: %s, path: %s]", err.RepoID, err.UserName, err.Path)
 }
 
+func (err ErrLFSFileLocked) Unwrap() error {
+	return util.ErrPermissionDenied
+}
+
 // LFSMetaObject stores metadata for LFS tracked files.
 type LFSMetaObject struct {
 	ID           int64 `xorm:"pk autoincr"`
@@ -114,7 +130,7 @@ type LFSTokenResponse struct {
 
 // ErrLFSObjectNotExist is returned from lfs models functions in order
 // to differentiate between database and missing object errors.
-var ErrLFSObjectNotExist = errors.New("LFS Meta object does not exist")
+var ErrLFSObjectNotExist = db.ErrNotExist{Resource: "LFS Meta object"}
 
 // NewLFSMetaObject stores a given populated LFSMetaObject structure in the database
 // if it is not already present.
diff --git a/models/issues/comment.go b/models/issues/comment.go
index a71afda9e0..9ab6cab7d0 100644
--- a/models/issues/comment.go
+++ b/models/issues/comment.go
@@ -28,6 +28,7 @@ import (
 	"code.gitea.io/gitea/modules/references"
 	"code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/timeutil"
+	"code.gitea.io/gitea/modules/util"
 
 	"xorm.io/builder"
 	"xorm.io/xorm"
@@ -49,6 +50,10 @@ func (err ErrCommentNotExist) Error() string {
 	return fmt.Sprintf("comment does not exist [id: %d, issue_id: %d]", err.ID, err.IssueID)
 }
 
+func (err ErrCommentNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
 type CommentType int
 
diff --git a/models/issues/content_history.go b/models/issues/content_history.go
index 3e321784bd..f5cfa65b8f 100644
--- a/models/issues/content_history.go
+++ b/models/issues/content_history.go
@@ -12,6 +12,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/timeutil"
+	"code.gitea.io/gitea/modules/util"
 
 	"xorm.io/builder"
 )
@@ -201,6 +202,10 @@ func (err ErrIssueContentHistoryNotExist) Error() string {
 	return fmt.Sprintf("issue content history does not exist [id: %d]", err.ID)
 }
 
+func (err ErrIssueContentHistoryNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // GetIssueContentHistoryByID get issue content history
 func GetIssueContentHistoryByID(dbCtx context.Context, id int64) (*ContentHistory, error) {
 	h := &ContentHistory{}
diff --git a/models/issues/dependency.go b/models/issues/dependency.go
index d664c0758e..4754ed0f5f 100644
--- a/models/issues/dependency.go
+++ b/models/issues/dependency.go
@@ -11,6 +11,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/timeutil"
+	"code.gitea.io/gitea/modules/util"
 )
 
 // ErrDependencyExists represents a "DependencyAlreadyExists" kind of error.
@@ -29,6 +30,10 @@ func (err ErrDependencyExists) Error() string {
 	return fmt.Sprintf("issue dependency does already exist [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
 }
 
+func (err ErrDependencyExists) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrDependencyNotExists represents a "DependencyAlreadyExists" kind of error.
 type ErrDependencyNotExists struct {
 	IssueID      int64
@@ -45,6 +50,10 @@ func (err ErrDependencyNotExists) Error() string {
 	return fmt.Sprintf("issue dependency does not exist [issue id: %d, dependency id: %d]", err.IssueID, err.DependencyID)
 }
 
+func (err ErrDependencyNotExists) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrCircularDependency represents a "DependencyCircular" kind of error.
 type ErrCircularDependency struct {
 	IssueID      int64
@@ -91,6 +100,10 @@ func (err ErrUnknownDependencyType) Error() string {
 	return fmt.Sprintf("unknown dependency type [type: %d]", err.Type)
 }
 
+func (err ErrUnknownDependencyType) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // IssueDependency represents an issue dependency
 type IssueDependency struct {
 	ID           int64              `xorm:"pk autoincr"`
diff --git a/models/issues/issue.go b/models/issues/issue.go
index 786c969522..e56e43bd1a 100644
--- a/models/issues/issue.go
+++ b/models/issues/issue.go
@@ -52,6 +52,10 @@ func (err ErrIssueNotExist) Error() string {
 	return fmt.Sprintf("issue does not exist [id: %d, repo_id: %d, index: %d]", err.ID, err.RepoID, err.Index)
 }
 
+func (err ErrIssueNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrIssueIsClosed represents a "IssueIsClosed" kind of error.
 type ErrIssueIsClosed struct {
 	ID     int64
diff --git a/models/issues/label.go b/models/issues/label.go
index 667a608687..be97454e26 100644
--- a/models/issues/label.go
+++ b/models/issues/label.go
@@ -38,6 +38,10 @@ func (err ErrRepoLabelNotExist) Error() string {
 	return fmt.Sprintf("label does not exist [label_id: %d, repo_id: %d]", err.LabelID, err.RepoID)
 }
 
+func (err ErrRepoLabelNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrOrgLabelNotExist represents a "OrgLabelNotExist" kind of error.
 type ErrOrgLabelNotExist struct {
 	LabelID int64
@@ -54,6 +58,10 @@ func (err ErrOrgLabelNotExist) Error() string {
 	return fmt.Sprintf("label does not exist [label_id: %d, org_id: %d]", err.LabelID, err.OrgID)
 }
 
+func (err ErrOrgLabelNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrLabelNotExist represents a "LabelNotExist" kind of error.
 type ErrLabelNotExist struct {
 	LabelID int64
@@ -69,6 +77,10 @@ func (err ErrLabelNotExist) Error() string {
 	return fmt.Sprintf("label does not exist [label_id: %d]", err.LabelID)
 }
 
+func (err ErrLabelNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // LabelColorPattern is a regexp witch can validate LabelColor
 var LabelColorPattern = regexp.MustCompile("^#?(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{3})$")
 
diff --git a/models/issues/milestone.go b/models/issues/milestone.go
index 1021938b20..3ccade7411 100644
--- a/models/issues/milestone.go
+++ b/models/issues/milestone.go
@@ -15,6 +15,7 @@ import (
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/timeutil"
+	"code.gitea.io/gitea/modules/util"
 
 	"xorm.io/builder"
 )
@@ -39,6 +40,10 @@ func (err ErrMilestoneNotExist) Error() string {
 	return fmt.Sprintf("milestone does not exist [id: %d, repo_id: %d]", err.ID, err.RepoID)
 }
 
+func (err ErrMilestoneNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // Milestone represents a milestone of repository.
 type Milestone struct {
 	ID              int64                  `xorm:"pk autoincr"`
diff --git a/models/issues/pull.go b/models/issues/pull.go
index 69259c269f..18b67eb305 100644
--- a/models/issues/pull.go
+++ b/models/issues/pull.go
@@ -46,6 +46,10 @@ func (err ErrPullRequestNotExist) Error() string {
 		err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBranch, err.BaseBranch)
 }
 
+func (err ErrPullRequestNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrPullRequestAlreadyExists represents a "PullRequestAlreadyExists"-error
 type ErrPullRequestAlreadyExists struct {
 	ID         int64
@@ -68,6 +72,10 @@ func (err ErrPullRequestAlreadyExists) Error() string {
 		err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBranch, err.BaseBranch)
 }
 
+func (err ErrPullRequestAlreadyExists) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrPullRequestHeadRepoMissing represents a "ErrPullRequestHeadRepoMissing" error
 type ErrPullRequestHeadRepoMissing struct {
 	ID         int64
diff --git a/models/issues/reaction.go b/models/issues/reaction.go
index ccda10be2c..02cffad3ba 100644
--- a/models/issues/reaction.go
+++ b/models/issues/reaction.go
@@ -15,6 +15,7 @@ import (
 	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/timeutil"
+	"code.gitea.io/gitea/modules/util"
 
 	"xorm.io/builder"
 )
@@ -34,6 +35,10 @@ func (err ErrForbiddenIssueReaction) Error() string {
 	return fmt.Sprintf("'%s' is not an allowed reaction", err.Reaction)
 }
 
+func (err ErrForbiddenIssueReaction) Unwrap() error {
+	return util.ErrPermissionDenied
+}
+
 // ErrReactionAlreadyExist is used when a existing reaction was try to created
 type ErrReactionAlreadyExist struct {
 	Reaction string
@@ -49,6 +54,10 @@ func (err ErrReactionAlreadyExist) Error() string {
 	return fmt.Sprintf("reaction '%s' already exists", err.Reaction)
 }
 
+func (err ErrReactionAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // Reaction represents a reactions on issues and comments.
 type Reaction struct {
 	ID               int64              `xorm:"pk autoincr"`
diff --git a/models/issues/review.go b/models/issues/review.go
index 5835900801..26fcea9eef 100644
--- a/models/issues/review.go
+++ b/models/issues/review.go
@@ -39,6 +39,10 @@ func (err ErrReviewNotExist) Error() string {
 	return fmt.Sprintf("review does not exist [id: %d]", err.ID)
 }
 
+func (err ErrReviewNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrNotValidReviewRequest an not allowed review request modify
 type ErrNotValidReviewRequest struct {
 	Reason string
@@ -59,6 +63,10 @@ func (err ErrNotValidReviewRequest) Error() string {
 		err.RepoID)
 }
 
+func (err ErrNotValidReviewRequest) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // ReviewType defines the sort of feedback a review gives
 type ReviewType int
 
diff --git a/models/issues/stopwatch.go b/models/issues/stopwatch.go
index 0a7ad41f9c..a87fbfafa2 100644
--- a/models/issues/stopwatch.go
+++ b/models/issues/stopwatch.go
@@ -25,6 +25,10 @@ func (err ErrIssueStopwatchNotExist) Error() string {
 	return fmt.Sprintf("issue stopwatch doesn't exist[uid: %d, issue_id: %d", err.UserID, err.IssueID)
 }
 
+func (err ErrIssueStopwatchNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrIssueStopwatchAlreadyExist represents an error that stopwatch is already exist
 type ErrIssueStopwatchAlreadyExist struct {
 	UserID  int64
@@ -35,6 +39,10 @@ func (err ErrIssueStopwatchAlreadyExist) Error() string {
 	return fmt.Sprintf("issue stopwatch already exists[uid: %d, issue_id: %d", err.UserID, err.IssueID)
 }
 
+func (err ErrIssueStopwatchAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // Stopwatch represents a stopwatch for time tracking.
 type Stopwatch struct {
 	ID          int64              `xorm:"pk autoincr"`
diff --git a/models/issues/tracked_time.go b/models/issues/tracked_time.go
index 9f8767362f..ca21eb5149 100644
--- a/models/issues/tracked_time.go
+++ b/models/issues/tracked_time.go
@@ -236,7 +236,7 @@ func DeleteIssueUserTimes(issue *Issue, user *user_model.User) error {
 		return err
 	}
 	if removedTime == 0 {
-		return db.ErrNotExist{}
+		return db.ErrNotExist{Resource: "tracked_time"}
 	}
 
 	if err := issue.LoadRepo(ctx); err != nil {
@@ -296,7 +296,7 @@ func deleteTimes(ctx context.Context, opts FindTrackedTimesOptions) (removedTime
 
 func deleteTime(ctx context.Context, t *TrackedTime) error {
 	if t.Deleted {
-		return db.ErrNotExist{ID: t.ID}
+		return db.ErrNotExist{Resource: "tracked_time", ID: t.ID}
 	}
 	t.Deleted = true
 	_, err := db.GetEngine(ctx).ID(t.ID).Cols("deleted").Update(t)
@@ -310,7 +310,7 @@ func GetTrackedTimeByID(id int64) (*TrackedTime, error) {
 	if err != nil {
 		return nil, err
 	} else if !has {
-		return nil, db.ErrNotExist{ID: id}
+		return nil, db.ErrNotExist{Resource: "tracked_time", ID: id}
 	}
 	return time, nil
 }
diff --git a/models/organization/org.go b/models/organization/org.go
index 044ea06563..fbbf6d04fa 100644
--- a/models/organization/org.go
+++ b/models/organization/org.go
@@ -18,6 +18,7 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/util"
 
 	"xorm.io/builder"
 )
@@ -45,6 +46,10 @@ func (err ErrOrgNotExist) Error() string {
 	return fmt.Sprintf("org does not exist [id: %d, name: %s]", err.ID, err.Name)
 }
 
+func (err ErrOrgNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrLastOrgOwner represents a "LastOrgOwner" kind of error.
 type ErrLastOrgOwner struct {
 	UID int64
@@ -73,6 +78,10 @@ func (err ErrUserNotAllowedCreateOrg) Error() string {
 	return "user is not allowed to create organizations"
 }
 
+func (err ErrUserNotAllowedCreateOrg) Unwrap() error {
+	return util.ErrPermissionDenied
+}
+
 // Organization represents an organization
 type Organization user_model.User
 
diff --git a/models/organization/team.go b/models/organization/team.go
index bd80b1a8c7..83e5bd6fe1 100644
--- a/models/organization/team.go
+++ b/models/organization/team.go
@@ -16,6 +16,7 @@ import (
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/util"
 
 	"xorm.io/builder"
 )
@@ -43,6 +44,10 @@ func (err ErrTeamAlreadyExist) Error() string {
 	return fmt.Sprintf("team already exists [org_id: %d, name: %s]", err.OrgID, err.Name)
 }
 
+func (err ErrTeamAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrTeamNotExist represents a "TeamNotExist" error
 type ErrTeamNotExist struct {
 	OrgID  int64
@@ -60,6 +65,10 @@ func (err ErrTeamNotExist) Error() string {
 	return fmt.Sprintf("team does not exist [org_id %d, team_id %d, name: %s]", err.OrgID, err.TeamID, err.Name)
 }
 
+func (err ErrTeamNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // OwnerTeamName return the owner team name
 const OwnerTeamName = "Owners"
 
diff --git a/models/project/project.go b/models/project/project.go
index 86a77947d8..af2c8ac2af 100644
--- a/models/project/project.go
+++ b/models/project/project.go
@@ -55,6 +55,10 @@ func (err ErrProjectNotExist) Error() string {
 	return fmt.Sprintf("projects does not exist [id: %d]", err.ID)
 }
 
+func (err ErrProjectNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrProjectBoardNotExist represents a "ProjectBoardNotExist" kind of error.
 type ErrProjectBoardNotExist struct {
 	BoardID int64
@@ -70,6 +74,10 @@ func (err ErrProjectBoardNotExist) Error() string {
 	return fmt.Sprintf("project board does not exist [id: %d]", err.BoardID)
 }
 
+func (err ErrProjectBoardNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // Project represents a project board
 type Project struct {
 	ID          int64  `xorm:"pk autoincr"`
diff --git a/models/pull/automerge.go b/models/pull/automerge.go
index d0aca2e85f..16ab5af093 100644
--- a/models/pull/automerge.go
+++ b/models/pull/automerge.go
@@ -90,7 +90,7 @@ func DeleteScheduledAutoMerge(ctx context.Context, pullID int64) error {
 	if err != nil {
 		return err
 	} else if !exist {
-		return db.ErrNotExist{ID: pullID}
+		return db.ErrNotExist{Resource: "auto_merge", ID: pullID}
 	}
 
 	_, err = db.GetEngine(ctx).ID(scheduledPRM.ID).Delete(&AutoMerge{})
diff --git a/models/repo/attachment.go b/models/repo/attachment.go
index afec78a425..5d4e11ae72 100644
--- a/models/repo/attachment.go
+++ b/models/repo/attachment.go
@@ -14,6 +14,7 @@ import (
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/storage"
 	"code.gitea.io/gitea/modules/timeutil"
+	"code.gitea.io/gitea/modules/util"
 )
 
 // Attachment represent a attachment of issue/comment/release.
@@ -83,6 +84,10 @@ func (err ErrAttachmentNotExist) Error() string {
 	return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID)
 }
 
+func (err ErrAttachmentNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // GetAttachmentByID returns attachment by given id
 func GetAttachmentByID(ctx context.Context, id int64) (*Attachment, error) {
 	attach := &Attachment{}
diff --git a/models/repo/redirect.go b/models/repo/redirect.go
index 88fad6f3e3..f28220c2af 100644
--- a/models/repo/redirect.go
+++ b/models/repo/redirect.go
@@ -10,6 +10,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/util"
 )
 
 // ErrRedirectNotExist represents a "RedirectNotExist" kind of error.
@@ -28,6 +29,10 @@ func (err ErrRedirectNotExist) Error() string {
 	return fmt.Sprintf("repository redirect does not exist [uid: %d, name: %s]", err.OwnerID, err.RepoName)
 }
 
+func (err ErrRedirectNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // Redirect represents that a repo name should be redirected to another
 type Redirect struct {
 	ID             int64  `xorm:"pk autoincr"`
diff --git a/models/repo/release.go b/models/repo/release.go
index 2b484c9b84..2e7bc6d322 100644
--- a/models/repo/release.go
+++ b/models/repo/release.go
@@ -37,6 +37,10 @@ func (err ErrReleaseAlreadyExist) Error() string {
 	return fmt.Sprintf("release tag already exist [tag_name: %s]", err.TagName)
 }
 
+func (err ErrReleaseAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrReleaseNotExist represents a "ReleaseNotExist" kind of error.
 type ErrReleaseNotExist struct {
 	ID      int64
@@ -53,6 +57,10 @@ func (err ErrReleaseNotExist) Error() string {
 	return fmt.Sprintf("release tag does not exist [id: %d, tag_name: %s]", err.ID, err.TagName)
 }
 
+func (err ErrReleaseNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // Release represents a release of repository.
 type Release struct {
 	ID               int64            `xorm:"pk autoincr"`
diff --git a/models/repo/repo.go b/models/repo/repo.go
index 4fd97dcd1b..ce698baaef 100644
--- a/models/repo/repo.go
+++ b/models/repo/repo.go
@@ -43,6 +43,10 @@ func (err ErrUserDoesNotHaveAccessToRepo) Error() string {
 	return fmt.Sprintf("user doesn't have access to repo [user_id: %d, repo_name: %s]", err.UserID, err.RepoName)
 }
 
+func (err ErrUserDoesNotHaveAccessToRepo) Unwrap() error {
+	return util.ErrPermissionDenied
+}
+
 var (
 	reservedRepoNames    = []string{".", "..", "-"}
 	reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss", "*.atom"}
@@ -643,6 +647,11 @@ func (err ErrRepoNotExist) Error() string {
 		err.ID, err.UID, err.OwnerName, err.Name)
 }
 
+// Unwrap unwraps this error as a ErrNotExist error
+func (err ErrRepoNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // GetRepositoryByOwnerAndNameCtx returns the repository by given owner name and repo name
 func GetRepositoryByOwnerAndNameCtx(ctx context.Context, ownerName, repoName string) (*Repository, error) {
 	var repo Repository
diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go
index da3e19dece..dd85ca9186 100644
--- a/models/repo/repo_unit.go
+++ b/models/repo/repo_unit.go
@@ -13,6 +13,7 @@ import (
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/timeutil"
+	"code.gitea.io/gitea/modules/util"
 
 	"xorm.io/xorm"
 	"xorm.io/xorm/convert"
@@ -33,6 +34,10 @@ func (err ErrUnitTypeNotExist) Error() string {
 	return fmt.Sprintf("Unit type does not exist: %s", err.UT.String())
 }
 
+func (err ErrUnitTypeNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // RepoUnit describes all units of a repository
 type RepoUnit struct { //revive:disable-line:exported
 	ID          int64
diff --git a/models/repo/topic.go b/models/repo/topic.go
index 7ba9a49e89..33bbb05af9 100644
--- a/models/repo/topic.go
+++ b/models/repo/topic.go
@@ -13,6 +13,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/timeutil"
+	"code.gitea.io/gitea/modules/util"
 
 	"xorm.io/builder"
 )
@@ -55,6 +56,10 @@ func (err ErrTopicNotExist) Error() string {
 	return fmt.Sprintf("topic is not exist [name: %s]", err.Name)
 }
 
+func (err ErrTopicNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ValidateTopic checks a topic by length and match pattern rules
 func ValidateTopic(topic string) bool {
 	return len(topic) <= 35 && topicPattern.MatchString(topic)
diff --git a/models/repo/update.go b/models/repo/update.go
index 07776ebc01..64a225d2f5 100644
--- a/models/repo/update.go
+++ b/models/repo/update.go
@@ -63,6 +63,10 @@ func (err ErrReachLimitOfRepo) Error() string {
 	return fmt.Sprintf("user has reached maximum limit of repositories [limit: %d]", err.Limit)
 }
 
+func (err ErrReachLimitOfRepo) Unwrap() error {
+	return util.ErrPermissionDenied
+}
+
 // ErrRepoAlreadyExist represents a "RepoAlreadyExist" kind of error.
 type ErrRepoAlreadyExist struct {
 	Uname string
@@ -79,6 +83,10 @@ func (err ErrRepoAlreadyExist) Error() string {
 	return fmt.Sprintf("repository already exists [uname: %s, name: %s]", err.Uname, err.Name)
 }
 
+func (err ErrRepoAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrRepoFilesAlreadyExist represents a "RepoFilesAlreadyExist" kind of error.
 type ErrRepoFilesAlreadyExist struct {
 	Uname string
@@ -95,6 +103,10 @@ func (err ErrRepoFilesAlreadyExist) Error() string {
 	return fmt.Sprintf("repository files already exist [uname: %s, name: %s]", err.Uname, err.Name)
 }
 
+func (err ErrRepoFilesAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // CheckCreateRepository check if could created a repository
 func CheckCreateRepository(doer, u *user_model.User, name string, overwriteOrAdopt bool) error {
 	if !doer.CanCreateRepo() {
diff --git a/models/repo/upload.go b/models/repo/upload.go
index 24544910b1..e3ce7e458f 100644
--- a/models/repo/upload.go
+++ b/models/repo/upload.go
@@ -36,6 +36,10 @@ func (err ErrUploadNotExist) Error() string {
 	return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID)
 }
 
+func (err ErrUploadNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // Upload represent a uploaded file to a repo to be deleted when moved
 type Upload struct {
 	ID   int64  `xorm:"pk autoincr"`
diff --git a/models/repo/wiki.go b/models/repo/wiki.go
index 72ec7c394b..c8886eaa34 100644
--- a/models/repo/wiki.go
+++ b/models/repo/wiki.go
@@ -30,6 +30,10 @@ func (err ErrWikiAlreadyExist) Error() string {
 	return fmt.Sprintf("wiki page already exists [title: %s]", err.Title)
 }
 
+func (err ErrWikiAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrWikiReservedName represents a reserved name error.
 type ErrWikiReservedName struct {
 	Title string
@@ -45,6 +49,10 @@ func (err ErrWikiReservedName) Error() string {
 	return fmt.Sprintf("wiki title is reserved: %s", err.Title)
 }
 
+func (err ErrWikiReservedName) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // ErrWikiInvalidFileName represents an invalid wiki file name.
 type ErrWikiInvalidFileName struct {
 	FileName string
@@ -60,6 +68,10 @@ func (err ErrWikiInvalidFileName) Error() string {
 	return fmt.Sprintf("Invalid wiki filename: %s", err.FileName)
 }
 
+func (err ErrWikiInvalidFileName) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // WikiCloneLink returns clone URLs of repository wiki.
 func (repo *Repository) WikiCloneLink() *CloneLink {
 	return repo.cloneLink(true)
diff --git a/models/user/email_address.go b/models/user/email_address.go
index d87b945706..964e7ae08c 100644
--- a/models/user/email_address.go
+++ b/models/user/email_address.go
@@ -40,6 +40,10 @@ func (err ErrEmailCharIsNotSupported) Error() string {
 	return fmt.Sprintf("e-mail address contains unsupported character [email: %s]", err.Email)
 }
 
+func (err ErrEmailCharIsNotSupported) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // ErrEmailInvalid represents an error where the email address does not comply with RFC 5322
 // or has a leading '-' character
 type ErrEmailInvalid struct {
@@ -56,6 +60,10 @@ func (err ErrEmailInvalid) Error() string {
 	return fmt.Sprintf("e-mail invalid [email: %s]", err.Email)
 }
 
+func (err ErrEmailInvalid) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // ErrEmailAlreadyUsed represents a "EmailAlreadyUsed" kind of error.
 type ErrEmailAlreadyUsed struct {
 	Email string
@@ -71,6 +79,10 @@ func (err ErrEmailAlreadyUsed) Error() string {
 	return fmt.Sprintf("e-mail already in use [email: %s]", err.Email)
 }
 
+func (err ErrEmailAlreadyUsed) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrEmailAddressNotExist email address not exist
 type ErrEmailAddressNotExist struct {
 	Email string
@@ -86,6 +98,10 @@ func (err ErrEmailAddressNotExist) Error() string {
 	return fmt.Sprintf("Email address does not exist [email: %s]", err.Email)
 }
 
+func (err ErrEmailAddressNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrPrimaryEmailCannotDelete primary email address cannot be deleted
 type ErrPrimaryEmailCannotDelete struct {
 	Email string
@@ -101,6 +117,10 @@ func (err ErrPrimaryEmailCannotDelete) Error() string {
 	return fmt.Sprintf("Primary email address cannot be deleted [email: %s]", err.Email)
 }
 
+func (err ErrPrimaryEmailCannotDelete) Unwrap() error {
+	return util.ErrInvalidArgument
+}
+
 // EmailAddress is the list of all email addresses of a user. It also contains the
 // primary email address which is saved in user table.
 type EmailAddress struct {
diff --git a/models/user/error.go b/models/user/error.go
index 25e0d8ea8a..3fe4ee6657 100644
--- a/models/user/error.go
+++ b/models/user/error.go
@@ -6,6 +6,8 @@ package user
 
 import (
 	"fmt"
+
+	"code.gitea.io/gitea/modules/util"
 )
 
 //  ____ ___
@@ -30,6 +32,11 @@ func (err ErrUserAlreadyExist) Error() string {
 	return fmt.Sprintf("user already exists [name: %s]", err.Name)
 }
 
+// Unwrap unwraps this error as a ErrExist error
+func (err ErrUserAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrUserNotExist represents a "UserNotExist" kind of error.
 type ErrUserNotExist struct {
 	UID   int64
@@ -47,6 +54,11 @@ func (err ErrUserNotExist) Error() string {
 	return fmt.Sprintf("user does not exist [uid: %d, name: %s, keyid: %d]", err.UID, err.Name, err.KeyID)
 }
 
+// Unwrap unwraps this error as a ErrNotExist error
+func (err ErrUserNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrUserProhibitLogin represents a "ErrUserProhibitLogin" kind of error.
 type ErrUserProhibitLogin struct {
 	UID  int64
@@ -63,6 +75,11 @@ func (err ErrUserProhibitLogin) Error() string {
 	return fmt.Sprintf("user is not allowed login [uid: %d, name: %s]", err.UID, err.Name)
 }
 
+// Unwrap unwraps this error as a ErrPermission error
+func (err ErrUserProhibitLogin) Unwrap() error {
+	return util.ErrPermissionDenied
+}
+
 // ErrUserInactive represents a "ErrUserInactive" kind of error.
 type ErrUserInactive struct {
 	UID  int64
@@ -78,3 +95,8 @@ func IsErrUserInactive(err error) bool {
 func (err ErrUserInactive) Error() string {
 	return fmt.Sprintf("user is inactive [uid: %d, name: %s]", err.UID, err.Name)
 }
+
+// Unwrap unwraps this error as a ErrPermission error
+func (err ErrUserInactive) Unwrap() error {
+	return util.ErrPermissionDenied
+}
diff --git a/models/user/external_login_user.go b/models/user/external_login_user.go
index 422823b89c..496717c57b 100644
--- a/models/user/external_login_user.go
+++ b/models/user/external_login_user.go
@@ -10,6 +10,7 @@ import (
 	"time"
 
 	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/util"
 
 	"xorm.io/builder"
 )
@@ -31,6 +32,10 @@ func (err ErrExternalLoginUserAlreadyExist) Error() string {
 	return fmt.Sprintf("external login user already exists [externalID: %s, userID: %d, loginSourceID: %d]", err.ExternalID, err.UserID, err.LoginSourceID)
 }
 
+func (err ErrExternalLoginUserAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ErrExternalLoginUserNotExist represents a "ExternalLoginUserNotExist" kind of error.
 type ErrExternalLoginUserNotExist struct {
 	UserID        int64
@@ -47,6 +52,10 @@ func (err ErrExternalLoginUserNotExist) Error() string {
 	return fmt.Sprintf("external login user link does not exists [userID: %d, loginSourceID: %d]", err.UserID, err.LoginSourceID)
 }
 
+func (err ErrExternalLoginUserNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ExternalLoginUser makes the connecting between some existing user and additional external login sources
 type ExternalLoginUser struct {
 	ExternalID        string                 `xorm:"pk NOT NULL"`
diff --git a/models/user/openid.go b/models/user/openid.go
index 8ef0ce5ed7..f8e8a787e6 100644
--- a/models/user/openid.go
+++ b/models/user/openid.go
@@ -10,6 +10,7 @@ import (
 	"fmt"
 
 	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/util"
 )
 
 // ErrOpenIDNotExist openid is not known
@@ -65,6 +66,10 @@ func (err ErrOpenIDAlreadyUsed) Error() string {
 	return fmt.Sprintf("OpenID already in use [oid: %s]", err.OpenID)
 }
 
+func (err ErrOpenIDAlreadyUsed) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // AddUserOpenID adds an pre-verified/normalized OpenID URI to given user.
 // NOTE: make sure openid.URI is normalized already
 func AddUserOpenID(ctx context.Context, openid *UserOpenID) error {
diff --git a/models/user/redirect.go b/models/user/redirect.go
index 49370218db..af8d6439ad 100644
--- a/models/user/redirect.go
+++ b/models/user/redirect.go
@@ -10,6 +10,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/util"
 )
 
 // ErrUserRedirectNotExist represents a "UserRedirectNotExist" kind of error.
@@ -27,6 +28,10 @@ func (err ErrUserRedirectNotExist) Error() string {
 	return fmt.Sprintf("user redirect does not exist [name: %s]", err.Name)
 }
 
+func (err ErrUserRedirectNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // Redirect represents that a user name should be redirected to another
 type Redirect struct {
 	ID             int64  `xorm:"pk autoincr"`
diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go
index ac09220630..83200a3d1c 100644
--- a/models/webhook/webhook.go
+++ b/models/webhook/webhook.go
@@ -41,6 +41,10 @@ func (err ErrWebhookNotExist) Error() string {
 	return fmt.Sprintf("webhook does not exist [id: %d]", err.ID)
 }
 
+func (err ErrWebhookNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrHookTaskNotExist represents a "HookTaskNotExist" kind of error.
 type ErrHookTaskNotExist struct {
 	HookID int64
@@ -57,6 +61,10 @@ func (err ErrHookTaskNotExist) Error() string {
 	return fmt.Sprintf("hook task does not exist [hook: %d, uuid: %s]", err.HookID, err.UUID)
 }
 
+func (err ErrHookTaskNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // HookContentType is the content type of a web hook
 type HookContentType int
 
diff --git a/modules/git/error.go b/modules/git/error.go
index 387dd724e5..40c4106414 100644
--- a/modules/git/error.go
+++ b/modules/git/error.go
@@ -8,6 +8,8 @@ import (
 	"fmt"
 	"strings"
 	"time"
+
+	"code.gitea.io/gitea/modules/util"
 )
 
 // ErrExecTimeout error when exec timed out
@@ -41,6 +43,10 @@ func (err ErrNotExist) Error() string {
 	return fmt.Sprintf("object does not exist [id: %s, rel_path: %s]", err.ID, err.RelPath)
 }
 
+func (err ErrNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrBadLink entry.FollowLink error
 type ErrBadLink struct {
 	Name    string
@@ -87,6 +93,10 @@ func (err ErrBranchNotExist) Error() string {
 	return fmt.Sprintf("branch does not exist [name: %s]", err.Name)
 }
 
+func (err ErrBranchNotExist) Unwrap() error {
+	return util.ErrNotExist
+}
+
 // ErrPushOutOfDate represents an error if merging fails due to unrelated histories
 type ErrPushOutOfDate struct {
 	StdOut string
diff --git a/modules/translation/i18n/errors.go b/modules/translation/i18n/errors.go
index b485badd1d..a81b0bc1ac 100644
--- a/modules/translation/i18n/errors.go
+++ b/modules/translation/i18n/errors.go
@@ -4,9 +4,11 @@
 
 package i18n
 
-import "errors"
+import (
+	"code.gitea.io/gitea/modules/util"
+)
 
 var (
-	ErrLocaleAlreadyExist = errors.New("lang already exists")
-	ErrUncertainArguments = errors.New("arguments to i18n should not contain uncertain slices")
+	ErrLocaleAlreadyExist = util.SilentWrap{Message: "lang already exists", Err: util.ErrAlreadyExist}
+	ErrUncertainArguments = util.SilentWrap{Message: "arguments to i18n should not contain uncertain slices", Err: util.ErrInvalidArgument}
 )
diff --git a/modules/util/error.go b/modules/util/error.go
new file mode 100644
index 0000000000..08e491dbaf
--- /dev/null
+++ b/modules/util/error.go
@@ -0,0 +1,37 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package util
+
+import (
+	"errors"
+)
+
+// Common Errors forming the base of our error system
+//
+// Many Errors returned by Gitea can be tested against these errors
+// using errors.Is.
+var (
+	ErrInvalidArgument  = errors.New("invalid argument")
+	ErrPermissionDenied = errors.New("permission denied")
+	ErrAlreadyExist     = errors.New("resource already exists")
+	ErrNotExist         = errors.New("resource does not exist")
+)
+
+// SilentWrap provides a simple wrapper for a wrapped error where the wrapped error message plays no part in the error message
+// Especially useful for "untyped" errors created with "errors.New(…)" that can be classified as 'invalid argument', 'permission denied', 'exists already', or 'does not exist'
+type SilentWrap struct {
+	Message string
+	Err     error
+}
+
+// Error returns the message
+func (w SilentWrap) Error() string {
+	return w.Message
+}
+
+// Unwrap returns the underlying error
+func (w SilentWrap) Unwrap() error {
+	return w.Err
+}
diff --git a/services/repository/fork.go b/services/repository/fork.go
index 96c391e715..32a516b79f 100644
--- a/services/repository/fork.go
+++ b/services/repository/fork.go
@@ -39,6 +39,10 @@ func (err ErrForkAlreadyExist) Error() string {
 	return fmt.Sprintf("repository is already forked by user [uname: %s, repo path: %s, fork path: %s]", err.Uname, err.RepoName, err.ForkName)
 }
 
+func (err ErrForkAlreadyExist) Unwrap() error {
+	return util.ErrAlreadyExist
+}
+
 // ForkRepoOptions contains the fork repository options
 type ForkRepoOptions struct {
 	BaseRepo    *repo_model.Repository