diff --git a/models/fixtures/label.yml b/models/fixtures/label.yml
index ab4d5ef944..2242b90dcd 100644
--- a/models/fixtures/label.yml
+++ b/models/fixtures/label.yml
@@ -7,6 +7,7 @@
   exclusive: false
   num_issues: 2
   num_closed_issues: 0
+  archived_unix: 0
 
 -
   id: 2
@@ -17,6 +18,7 @@
   exclusive: false
   num_issues: 1
   num_closed_issues: 1
+  archived_unix: 0
 
 -
   id: 3
@@ -27,6 +29,7 @@
   exclusive: false
   num_issues: 0
   num_closed_issues: 0
+  archived_unix: 0
 
 -
   id: 4
@@ -37,6 +40,7 @@
   exclusive: false
   num_issues: 1
   num_closed_issues: 0
+  archived_unix: 0
 
 -
   id: 5
@@ -47,6 +51,7 @@
   exclusive: false
   num_issues: 0
   num_closed_issues: 0
+  archived_unix: 0
 
 -
   id: 6
@@ -57,6 +62,7 @@
   exclusive: false
   num_issues: 0
   num_closed_issues: 0
+  archived_unix: 0
 
 -
   id: 7
@@ -67,6 +73,7 @@
   exclusive: true
   num_issues: 0
   num_closed_issues: 0
+  archived_unix: 0
 
 -
   id: 8
@@ -77,6 +84,7 @@
   exclusive: true
   num_issues: 0
   num_closed_issues: 0
+  archived_unix: 0
 
 -
   id: 9
@@ -87,3 +95,4 @@
   exclusive: true
   num_issues: 0
   num_closed_issues: 0
+  archived_unix: 0
diff --git a/models/issues/label.go b/models/issues/label.go
index 57a2e67f8c..70906efb47 100644
--- a/models/issues/label.go
+++ b/models/issues/label.go
@@ -97,6 +97,8 @@ type Label struct {
 	QueryString       string `xorm:"-"`
 	IsSelected        bool   `xorm:"-"`
 	IsExcluded        bool   `xorm:"-"`
+
+	ArchivedUnix timeutil.TimeStamp `xorm:"DEFAULT NULL"`
 }
 
 func init() {
@@ -109,6 +111,15 @@ func (l *Label) CalOpenIssues() {
 	l.NumOpenIssues = l.NumIssues - l.NumClosedIssues
 }
 
+// SetArchived set the label as archived
+func (l *Label) SetArchived(isArchived bool) {
+	if isArchived && l.ArchivedUnix.IsZero() {
+		l.ArchivedUnix = timeutil.TimeStampNow()
+	} else {
+		l.ArchivedUnix = timeutil.TimeStamp(0)
+	}
+}
+
 // CalOpenOrgIssues calculates the open issues of a label for a specific repo
 func (l *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) {
 	counts, _ := CountIssuesByRepo(ctx, &IssuesOptions{
@@ -153,6 +164,11 @@ func (l *Label) BelongsToOrg() bool {
 	return l.OrgID > 0
 }
 
+// IsArchived returns true if label is an archived
+func (l *Label) IsArchived() bool {
+	return l.ArchivedUnix > 0
+}
+
 // BelongsToRepo returns true if label is a repository label
 func (l *Label) BelongsToRepo() bool {
 	return l.RepoID > 0
@@ -211,7 +227,7 @@ func UpdateLabel(l *Label) error {
 	}
 	l.Color = color
 
-	return updateLabelCols(db.DefaultContext, l, "name", "description", "color", "exclusive")
+	return updateLabelCols(db.DefaultContext, l, "name", "description", "color", "exclusive", "archived_unix")
 }
 
 // DeleteLabel delete a label
diff --git a/models/issues/label_test.go b/models/issues/label_test.go
index 1bc5a1a935..3f0e980b31 100644
--- a/models/issues/label_test.go
+++ b/models/issues/label_test.go
@@ -11,6 +11,7 @@ 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/timeutil"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -259,11 +260,12 @@ func TestUpdateLabel(t *testing.T) {
 	label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
 	// make sure update wont overwrite it
 	update := &issues_model.Label{
-		ID:          label.ID,
-		Color:       "#ffff00",
-		Name:        "newLabelName",
-		Description: label.Description,
-		Exclusive:   false,
+		ID:           label.ID,
+		Color:        "#ffff00",
+		Name:         "newLabelName",
+		Description:  label.Description,
+		Exclusive:    false,
+		ArchivedUnix: timeutil.TimeStamp(0),
 	}
 	label.Color = update.Color
 	label.Name = update.Name
@@ -273,6 +275,7 @@ func TestUpdateLabel(t *testing.T) {
 	assert.EqualValues(t, label.Color, newLabel.Color)
 	assert.EqualValues(t, label.Name, newLabel.Name)
 	assert.EqualValues(t, label.Description, newLabel.Description)
+	assert.EqualValues(t, newLabel.ArchivedUnix, 0)
 	unittest.CheckConsistencyFor(t, &issues_model.Label{}, &repo_model.Repository{})
 }
 
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 55107439b0..7a126593d1 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -522,6 +522,8 @@ var migrations = []Migration{
 	NewMigration("Drop deleted branch table", v1_21.DropDeletedBranchTable),
 	// v270 -> v271
 	NewMigration("Fix PackageProperty typo", v1_21.FixPackagePropertyTypo),
+	// v271 -> v272
+	NewMigration("Allow archiving labels", v1_21.AddArchivedUnixColumInLabelTable),
 }
 
 // GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v1_21/v271.go b/models/migrations/v1_21/v271.go
new file mode 100644
index 0000000000..098f6499d5
--- /dev/null
+++ b/models/migrations/v1_21/v271.go
@@ -0,0 +1,16 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+import (
+	"code.gitea.io/gitea/modules/timeutil"
+
+	"xorm.io/xorm"
+)
+
+func AddArchivedUnixColumInLabelTable(x *xorm.Engine) error {
+	type Label struct {
+		ArchivedUnix timeutil.TimeStamp `xorm:"DEFAULT NULL"`
+	}
+	return x.Sync(new(Label))
+}
diff --git a/modules/structs/issue_label.go b/modules/structs/issue_label.go
index 2610d3e93f..bf68726d79 100644
--- a/modules/structs/issue_label.go
+++ b/modules/structs/issue_label.go
@@ -11,6 +11,8 @@ type Label struct {
 	Name string `json:"name"`
 	// example: false
 	Exclusive bool `json:"exclusive"`
+	// example: false
+	IsArchived bool `json:"is_archived"`
 	// example: 00aabb
 	Color       string `json:"color"`
 	Description string `json:"description"`
@@ -27,6 +29,8 @@ type CreateLabelOption struct {
 	// example: #00aabb
 	Color       string `json:"color" binding:"Required"`
 	Description string `json:"description"`
+	// example: false
+	IsArchived bool `json:"is_archived"`
 }
 
 // EditLabelOption options for editing a label
@@ -37,6 +41,8 @@ type EditLabelOption struct {
 	// example: #00aabb
 	Color       *string `json:"color"`
 	Description *string `json:"description"`
+	// example: false
+	IsArchived *bool `json:"is_archived"`
 }
 
 // IssueLabelsOption a collection of labels
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 30fa899c9d..daf22d9fea 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1491,6 +1491,8 @@ issues.label_title = Name
 issues.label_description = Description
 issues.label_color = Color
 issues.label_exclusive = Exclusive
+issues.label_archive = Archive Label
+issues.label_archive_tooltip= Archived labels are excluded from the label search when applying labels to an issue. Existing labels on issues remain unaffected, allowing you to retire obsolete labels without losing information.
 issues.label_exclusive_desc = Name the label <code>scope/item</code> to make it mutually exclusive with other <code>scope/</code> labels.
 issues.label_exclusive_warning = Any conflicting scoped labels will be removed when editing the labels of an issue or pull request.
 issues.label_count = %d labels
diff --git a/routers/api/v1/org/label.go b/routers/api/v1/org/label.go
index 183c1e6cc8..9ef28d4db9 100644
--- a/routers/api/v1/org/label.go
+++ b/routers/api/v1/org/label.go
@@ -209,6 +209,7 @@ func EditLabel(ctx *context.APIContext) {
 	if form.Description != nil {
 		l.Description = *form.Description
 	}
+	l.SetArchived(form.IsArchived != nil && *form.IsArchived)
 	if err := issues_model.UpdateLabel(l); err != nil {
 		ctx.Error(http.StatusInternalServerError, "UpdateLabel", err)
 		return
diff --git a/routers/api/v1/repo/label.go b/routers/api/v1/repo/label.go
index 6cb231f596..fc9a16b58a 100644
--- a/routers/api/v1/repo/label.go
+++ b/routers/api/v1/repo/label.go
@@ -151,7 +151,6 @@ func CreateLabel(ctx *context.APIContext) {
 		return
 	}
 	form.Color = color
-
 	l := &issues_model.Label{
 		Name:        form.Name,
 		Exclusive:   form.Exclusive,
@@ -159,6 +158,7 @@ func CreateLabel(ctx *context.APIContext) {
 		RepoID:      ctx.Repo.Repository.ID,
 		Description: form.Description,
 	}
+	l.SetArchived(form.IsArchived)
 	if err := issues_model.NewLabel(ctx, l); err != nil {
 		ctx.Error(http.StatusInternalServerError, "NewLabel", err)
 		return
@@ -231,6 +231,7 @@ func EditLabel(ctx *context.APIContext) {
 	if form.Description != nil {
 		l.Description = *form.Description
 	}
+	l.SetArchived(form.IsArchived != nil && *form.IsArchived)
 	if err := issues_model.UpdateLabel(l); err != nil {
 		ctx.Error(http.StatusInternalServerError, "UpdateLabel", err)
 		return
diff --git a/routers/web/org/org_labels.go b/routers/web/org/org_labels.go
index a9f9e963d4..2c7725e38d 100644
--- a/routers/web/org/org_labels.go
+++ b/routers/web/org/org_labels.go
@@ -75,6 +75,7 @@ func UpdateLabel(ctx *context.Context) {
 	l.Exclusive = form.Exclusive
 	l.Description = form.Description
 	l.Color = form.Color
+	l.SetArchived(form.IsArchived)
 	if err := issues_model.UpdateLabel(l); err != nil {
 		ctx.ServerError("UpdateLabel", err)
 		return
diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go
index 5d326bab58..257610d3af 100644
--- a/routers/web/repo/issue_label.go
+++ b/routers/web/repo/issue_label.go
@@ -14,6 +14,7 @@ import (
 	"code.gitea.io/gitea/modules/label"
 	"code.gitea.io/gitea/modules/log"
 	repo_module "code.gitea.io/gitea/modules/repository"
+	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/forms"
 	issue_service "code.gitea.io/gitea/services/issue"
@@ -111,11 +112,12 @@ func NewLabel(ctx *context.Context) {
 	}
 
 	l := &issues_model.Label{
-		RepoID:      ctx.Repo.Repository.ID,
-		Name:        form.Title,
-		Exclusive:   form.Exclusive,
-		Description: form.Description,
-		Color:       form.Color,
+		RepoID:       ctx.Repo.Repository.ID,
+		Name:         form.Title,
+		Exclusive:    form.Exclusive,
+		Description:  form.Description,
+		Color:        form.Color,
+		ArchivedUnix: timeutil.TimeStamp(0),
 	}
 	if err := issues_model.NewLabel(ctx, l); err != nil {
 		ctx.ServerError("NewLabel", err)
@@ -137,11 +139,12 @@ func UpdateLabel(ctx *context.Context) {
 		}
 		return
 	}
-
 	l.Name = form.Title
 	l.Exclusive = form.Exclusive
 	l.Description = form.Description
 	l.Color = form.Color
+
+	l.SetArchived(form.IsArchived)
 	if err := issues_model.UpdateLabel(l); err != nil {
 		ctx.ServerError("UpdateLabel", err)
 		return
diff --git a/routers/web/repo/issue_label_test.go b/routers/web/repo/issue_label_test.go
index 4c9a359438..e29582f968 100644
--- a/routers/web/repo/issue_label_test.go
+++ b/routers/web/repo/issue_label_test.go
@@ -97,9 +97,10 @@ func TestUpdateLabel(t *testing.T) {
 	test.LoadUser(t, ctx, 2)
 	test.LoadRepo(t, ctx, 1)
 	web.SetForm(ctx, &forms.CreateLabelForm{
-		ID:    2,
-		Title: "newnameforlabel",
-		Color: "#abcdef",
+		ID:         2,
+		Title:      "newnameforlabel",
+		Color:      "#abcdef",
+		IsArchived: true,
 	})
 	UpdateLabel(ctx)
 	assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status())
diff --git a/services/convert/issue.go b/services/convert/issue.go
index d81840f025..33fad31d48 100644
--- a/services/convert/issue.go
+++ b/services/convert/issue.go
@@ -208,6 +208,7 @@ func ToLabel(label *issues_model.Label, repo *repo_model.Repository, org *user_m
 		Exclusive:   label.Exclusive,
 		Color:       strings.TrimLeft(label.Color, "#"),
 		Description: label.Description,
+		IsArchived:  label.IsArchived(),
 	}
 
 	// calculate URL
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index 8c763e17cb..b36c8cc9b6 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -569,6 +569,7 @@ type CreateLabelForm struct {
 	ID          int64
 	Title       string `binding:"Required;MaxSize(50)" locale:"repo.issues.label_title"`
 	Exclusive   bool   `form:"exclusive"`
+	IsArchived  bool   `form:"is_archived"`
 	Description string `binding:"MaxSize(200)" locale:"repo.issues.label_description"`
 	Color       string `binding:"Required;MaxSize(7)" locale:"repo.issues.label_color"`
 }
diff --git a/templates/repo/issue/labels/edit_delete_label.tmpl b/templates/repo/issue/labels/edit_delete_label.tmpl
index b4eb6be7fc..d64782090c 100644
--- a/templates/repo/issue/labels/edit_delete_label.tmpl
+++ b/templates/repo/issue/labels/edit_delete_label.tmpl
@@ -33,6 +33,16 @@
 				<div class="desc gt-ml-2 gt-mt-3 gt-hidden label-exclusive-warning">
 					{{svg "octicon-alert"}} {{.locale.Tr "repo.issues.label_exclusive_warning" | Safe}}
 				</div>
+				<br>
+			</div>
+			<div class="field label-is-archived-input-field">
+				<div class="ui checkbox">
+					<input class="label-is-archived-input" name="is_archived" type="checkbox">
+					<label>{{.locale.Tr "repo.issues.label_archive"}}</label>
+				</div>
+				<i class="gt-ml-2" data-tooltip-content={{.locale.Tr "repo.issues.label_archive_tooltip"}}>
+					{{svg "octicon-info"}}
+				</i>
 			</div>
 			<div class="field">
 				<label for="description">{{.locale.Tr "repo.issues.label_description"}}</label>
diff --git a/templates/repo/issue/labels/label_list.tmpl b/templates/repo/issue/labels/label_list.tmpl
index 9eee95be92..c15833d952 100644
--- a/templates/repo/issue/labels/label_list.tmpl
+++ b/templates/repo/issue/labels/label_list.tmpl
@@ -44,10 +44,10 @@
 			</div>
 			<div class="label-operation">
 				{{if and (not $.PageIsOrgSettingsLabels) (not $.Repository.IsArchived) (or $.CanWriteIssues $.CanWritePulls)}}
-					<a class="edit-label-button" href="#" data-id="{{.ID}}" data-title="{{.Name}}" {{if .Exclusive}}data-exclusive{{end}} data-num-issues="{{.NumIssues}}" data-description="{{.Description}}" data-color={{.Color}}>{{svg "octicon-pencil"}} {{$.locale.Tr "repo.issues.label_edit"}}</a>
+					<a class="edit-label-button" href="#" data-id="{{.ID}}" data-title="{{.Name}}" {{if .Exclusive}}data-exclusive{{end}} {{if gt .ArchivedUnix 0}}data-is-archived{{end}} data-num-issues="{{.NumIssues}}" data-description="{{.Description}}" data-color={{.Color}}>{{svg "octicon-pencil"}} {{$.locale.Tr "repo.issues.label_edit"}}</a>
 					<a class="delete-button" href="#" data-url="{{$.Link}}/delete" data-id="{{.ID}}">{{svg "octicon-trash"}} {{$.locale.Tr "repo.issues.label_delete"}}</a>
 				{{else if $.PageIsOrgSettingsLabels}}
-					<a class="edit-label-button" href="#" data-id="{{.ID}}" data-title="{{.Name}}" {{if .Exclusive}}data-exclusive{{end}} data-num-issues="{{.NumIssues}}" data-description="{{.Description}}" data-color={{.Color}}>{{svg "octicon-pencil"}} {{$.locale.Tr "repo.issues.label_edit"}}</a>
+					<a class="edit-label-button" href="#" data-id="{{.ID}}" data-title="{{.Name}}" {{if .Exclusive}}data-exclusive{{end}} {{if gt .ArchivedUnix 0}}data-is-archived{{end}} data-num-issues="{{.NumIssues}}" data-description="{{.Description}}" data-color={{.Color}}>{{svg "octicon-pencil"}} {{$.locale.Tr "repo.issues.label_edit"}}</a>
 					<a class="delete-button" href="#" data-url="{{$.Link}}/delete" data-id="{{.ID}}">{{svg "octicon-trash"}} {{$.locale.Tr "repo.issues.label_delete"}}</a>
 				{{end}}
 			</div>
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 8cf5332baf..a5bea8a4cb 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -17057,6 +17057,11 @@
           "x-go-name": "Exclusive",
           "example": false
         },
+        "is_archived": {
+          "type": "boolean",
+          "x-go-name": "IsArchived",
+          "example": false
+        },
         "name": {
           "type": "string",
           "x-go-name": "Name"
@@ -18001,6 +18006,11 @@
           "x-go-name": "Exclusive",
           "example": false
         },
+        "is_archived": {
+          "type": "boolean",
+          "x-go-name": "IsArchived",
+          "example": false
+        },
         "name": {
           "type": "string",
           "x-go-name": "Name"
@@ -19479,6 +19489,11 @@
           "format": "int64",
           "x-go-name": "ID"
         },
+        "is_archived": {
+          "type": "boolean",
+          "x-go-name": "IsArchived",
+          "example": false
+        },
         "name": {
           "type": "string",
           "x-go-name": "Name"
diff --git a/web_src/js/features/comp/LabelEdit.js b/web_src/js/features/comp/LabelEdit.js
index 18676d25e6..2a22190e10 100644
--- a/web_src/js/features/comp/LabelEdit.js
+++ b/web_src/js/features/comp/LabelEdit.js
@@ -36,7 +36,7 @@ export function initCompLabelEdit(selector) {
     $('.new-label.modal').modal({
       onApprove() {
         $('.new-label.form').trigger('submit');
-      }
+      },
     }).modal('show');
     return false;
   });
@@ -49,6 +49,9 @@ export function initCompLabelEdit(selector) {
     const nameInput = $('.edit-label .label-name-input');
     nameInput.val($(this).data('title'));
 
+    const isArchivedCheckbox = $('.edit-label .label-is-archived-input');
+    isArchivedCheckbox.prop('checked', this.hasAttribute('data-is-archived'));
+
     const exclusiveCheckbox = $('.edit-label .label-exclusive-input');
     exclusiveCheckbox.prop('checked', this.hasAttribute('data-exclusive'));
     // Warn when label was previously not exclusive and used in issues
@@ -64,7 +67,7 @@ export function initCompLabelEdit(selector) {
     $('.edit-label.modal').modal({
       onApprove() {
         $('.edit-label.form').trigger('submit');
-      }
+      },
     }).modal('show');
     return false;
   });