diff --git a/src/main/resources/update/gitbucket-core_4.39.xml b/src/main/resources/update/gitbucket-core_4.39.xml
new file mode 100644
index 0000000..9691ce1
--- /dev/null
+++ b/src/main/resources/update/gitbucket-core_4.39.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<changeSet>
+  <addColumn tableName="CUSTOM_FIELD">
+    <column name="CONSTRAINTS" type="varchar(200)" nullable="true"/>
+  </addColumn>
+</changeSet>
diff --git a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
index 08bdbf6..74f7159 100644
--- a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
+++ b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
@@ -115,5 +115,6 @@
       new Version("4.38.1"),
       new Version("4.38.2"),
       new Version("4.38.3"),
-      new Version("4.38.4")
+      new Version("4.38.4"),
+      new Version("4.39.0", new LiquibaseMigration("update/gitbucket-core_4.39.xml")),
     )
diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala
index a949ba6..2a09fd1 100644
--- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala
+++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala
@@ -126,6 +126,7 @@
   case class CustomFieldForm(
     fieldName: String,
     fieldType: String,
+    constraints: Option[String],
     enableForIssues: Boolean,
     enableForPullRequests: Boolean
   )
@@ -133,6 +134,7 @@
   val customFieldForm = mapping(
     "fieldName" -> trim(label("Field name", text(required, maxlength(100)))),
     "fieldType" -> trim(label("Field type", text(required))),
+    "constraints" -> trim(label("Constraints", optional(text()))),
     "enableForIssues" -> trim(label("Enable for issues", boolean(required))),
     "enableForPullRequests" -> trim(label("Enable for pull requests", boolean(required))),
   )(CustomFieldForm.apply)
@@ -511,6 +513,7 @@
       repository.name,
       form.fieldName,
       form.fieldType,
+      if (form.fieldType == "enum") form.constraints else None,
       form.enableForIssues,
       form.enableForPullRequests
     )
@@ -533,6 +536,7 @@
         params("fieldId").toInt,
         form.fieldName,
         form.fieldType,
+        if (form.fieldType == "enum") form.constraints else None,
         form.enableForIssues,
         form.enableForPullRequests
       )
diff --git a/src/main/scala/gitbucket/core/model/CustomField.scala b/src/main/scala/gitbucket/core/model/CustomField.scala
index 4d5a4f1..0eaac53 100644
--- a/src/main/scala/gitbucket/core/model/CustomField.scala
+++ b/src/main/scala/gitbucket/core/model/CustomField.scala
@@ -5,6 +5,7 @@
 import gitbucket.core.util.StringUtil
 import gitbucket.core.view.helpers
 import org.scalatra.i18n.Messages
+import play.twirl.api.Html
 
 trait CustomFieldComponent extends TemplateComponent { self: Profile =>
   import profile.api._
@@ -15,10 +16,11 @@
     val fieldId = column[Int]("FIELD_ID", O AutoInc)
     val fieldName = column[String]("FIELD_NAME")
     val fieldType = column[String]("FIELD_TYPE")
+    val constraints = column[Option[String]]("CONSTRAINTS")
     val enableForIssues = column[Boolean]("ENABLE_FOR_ISSUES")
     val enableForPullRequests = column[Boolean]("ENABLE_FOR_PULL_REQUESTS")
     def * =
-      (userName, repositoryName, fieldId, fieldName, fieldType, enableForIssues, enableForPullRequests)
+      (userName, repositoryName, fieldId, fieldName, fieldType, constraints, enableForIssues, enableForPullRequests)
         .<>(CustomField.tupled, CustomField.unapply)
 
     def byPrimaryKey(userName: String, repositoryName: String, fieldId: Int) =
@@ -31,17 +33,28 @@
   repositoryName: String,
   fieldId: Int = 0,
   fieldName: String,
-  fieldType: String, // long, double, string, or date
+  fieldType: String, // long, double, string, date, or enum
+  constraints: Option[String],
   enableForIssues: Boolean,
   enableForPullRequests: Boolean
 )
 
 trait CustomFieldBehavior {
-  def createHtml(repository: RepositoryInfo, fieldId: Int)(implicit conext: Context): String
-  def fieldHtml(repository: RepositoryInfo, issueId: Int, fieldId: Int, value: String, editable: Boolean)(
+  def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])(
     implicit context: Context
   ): String
-  def validate(name: String, value: String, messages: Messages): Option[String]
+  def fieldHtml(
+    repository: RepositoryInfo,
+    issueId: Int,
+    fieldId: Int,
+    fieldName: String,
+    constraints: Option[String],
+    value: String,
+    editable: Boolean
+  )(
+    implicit context: Context
+  ): String
+  def validate(name: String, constraints: Option[String], value: String, messages: Messages): Option[String]
 }
 
 object CustomFieldBehavior {
@@ -49,7 +62,7 @@
     if (value.isEmpty) None
     else {
       CustomFieldBehavior(field.fieldType).flatMap { behavior =>
-        behavior.validate(field.fieldName, value, messages)
+        behavior.validate(field.fieldName, field.constraints, value, messages)
       }
     }
   }
@@ -60,12 +73,18 @@
       case "double" => Some(DoubleFieldBehavior)
       case "string" => Some(StringFieldBehavior)
       case "date"   => Some(DateFieldBehavior)
+      case "enum"   => Some(EnumFieldBehavior)
       case _        => None
     }
   }
 
   case object LongFieldBehavior extends TextFieldBehavior {
-    override def validate(name: String, value: String, messages: Messages): Option[String] = {
+    override def validate(
+      name: String,
+      constraints: Option[String],
+      value: String,
+      messages: Messages
+    ): Option[String] = {
       try {
         value.toLong
         None
@@ -75,7 +94,12 @@
     }
   }
   case object DoubleFieldBehavior extends TextFieldBehavior {
-    override def validate(name: String, value: String, messages: Messages): Option[String] = {
+    override def validate(
+      name: String,
+      constraints: Option[String],
+      value: String,
+      messages: Messages
+    ): Option[String] = {
       try {
         value.toDouble
         None
@@ -89,7 +113,12 @@
     private val pattern = "yyyy-MM-dd"
     override protected val fieldType: String = "date"
 
-    override def validate(name: String, value: String, messages: Messages): Option[String] = {
+    override def validate(
+      name: String,
+      constraints: Option[String],
+      value: String,
+      messages: Messages
+    ): Option[String] = {
       try {
         new java.text.SimpleDateFormat(pattern).parse(value)
         None
@@ -100,10 +129,142 @@
     }
   }
 
+  case object EnumFieldBehavior extends CustomFieldBehavior {
+    override def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])(
+      implicit context: Context
+    ): String = {
+      createPulldownHtml(repository, fieldId, fieldName, constraints, None, None)
+    }
+
+    override def fieldHtml(
+      repository: RepositoryInfo,
+      issueId: Int,
+      fieldId: Int,
+      fieldName: String,
+      constraints: Option[String],
+      value: String,
+      editable: Boolean
+    )(implicit context: Context): String = {
+      if (!editable) {
+        val sb = new StringBuilder
+        sb.append("""</div>""")
+        sb.append("""<div>""")
+        if (value == "") {
+          sb.append(s"""<span id="label-custom-field-$fieldId"><span class="muted small">No ${StringUtil.escapeHtml(
+            fieldName
+          )}</span></span>""")
+        } else {
+          sb.append(s"""<span id="label-custom-field-$fieldId"><span class="muted small">${StringUtil
+            .escapeHtml(value)}</span></span>""")
+        }
+        sb.toString()
+      } else {
+        createPulldownHtml(repository, fieldId, fieldName, constraints, Some(issueId), Some(value))
+      }
+    }
+
+    private def createPulldownHtml(
+      repository: RepositoryInfo,
+      fieldId: Int,
+      fieldName: String,
+      constraints: Option[String],
+      issueId: Option[Int],
+      value: Option[String]
+    )(implicit context: Context): String = {
+      val sb = new StringBuilder
+      sb.append("""<div class="pull-right">""")
+      sb.append(
+        gitbucket.core.helper.html
+          .dropdown("Edit", right = true, filter = (fieldName, s"Filter $fieldName")) {
+            val options = new StringBuilder()
+            options.append(
+              s"""<li><a href="javascript:void(0);" class="custom-field-option-$fieldId" data-value=""><i class="octicon octicon-x"></i> Clear ${StringUtil
+                .escapeHtml(fieldName)}</a></li>"""
+            )
+            constraints.foreach {
+              x =>
+                x.split(",").map(_.trim).foreach {
+                  item =>
+                    options.append(s"""<li>
+                 |  <a href="javascript:void(0);" class="custom-field-option-$fieldId" data-value="${StringUtil
+                                        .escapeHtml(item)}">
+                 |    ${gitbucket.core.helper.html.checkicon(value.contains(item))}
+                 |    ${StringUtil.escapeHtml(item)}
+                 |  </a>
+                 |</li>
+                 |""".stripMargin)
+                }
+            }
+            Html(options.toString())
+          }
+          .toString()
+      )
+      sb.append("""</div>""")
+      sb.append("""</div>""")
+      sb.append("""<div>""")
+      value match {
+        case None =>
+          sb.append(s"""<span id="label-custom-field-$fieldId"><span class="muted small">No ${StringUtil.escapeHtml(
+            fieldName
+          )}</span></span>""")
+        case Some(value) =>
+          sb.append(s"""<span id="label-custom-field-$fieldId"><span class="muted small">${StringUtil
+            .escapeHtml(value)}</span></span>""")
+      }
+      if (value.isEmpty || issueId.isEmpty) {
+        sb.append(s"""<input type="hidden" id="custom-field-$fieldId" name="custom-field-$fieldId" value=""/>""")
+        sb.append(s"""<script>
+             |$$('a.custom-field-option-$fieldId').click(function(){
+             |  const value = $$(this).data('value');
+             |  $$('a.custom-field-option-$fieldId i.octicon-check').removeClass('octicon-check');
+             |  $$('#custom-field-$fieldId').val(value);
+             |  if (value == '') {
+             |    $$('#label-custom-field-$fieldId').html($$('<span class="muted small">').text('No ${StringUtil
+                       .escapeHtml(fieldName)}'));
+             |  } else {
+             |    $$('#label-custom-field-$fieldId').html($$('<span class="muted small">').text(value));
+             |    $$('a.custom-field-option-$fieldId[data-value=' + value + '] i').addClass('octicon-check');
+             |  }
+             |});
+             |</script>""".stripMargin)
+      } else {
+        sb.append(s"""<script>
+             |$$('a.custom-field-option-$fieldId').click(function(){
+             |  const value = $$(this).data('value');
+             |  $$.post('${helpers.url(repository)}/issues/${issueId.get}/customfield/$fieldId',
+             |    { value: value },
+             |    function(data){
+             |      $$('a.custom-field-option-$fieldId i.octicon-check').removeClass('octicon-check');
+             |      if (value == '') {
+             |        $$('#label-custom-field-$fieldId').html($$('<span class="muted small">').text('No ${StringUtil
+                       .escapeHtml(fieldName)}'));
+             |      } else {
+             |        $$('#label-custom-field-$fieldId').html($$('<span class="muted small">').text(value));
+             |        $$('a.custom-field-option-$fieldId[data-value=' + value + '] i').addClass('octicon-check');
+             |      }
+             |    }
+             |  );
+             |});
+             |</script>
+             |""".stripMargin)
+      }
+      sb.toString()
+    }
+
+    override def validate(
+      name: String,
+      constraints: Option[String],
+      value: String,
+      messages: Messages
+    ): Option[String] = None
+  }
+
   trait TextFieldBehavior extends CustomFieldBehavior {
     protected val fieldType = "text"
 
-    def createHtml(repository: RepositoryInfo, fieldId: Int)(implicit context: Context): String = {
+    override def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])(
+      implicit context: Context
+    ): String = {
       val sb = new StringBuilder
       sb.append(
         s"""<input type="$fieldType" class="form-control input-sm" id="custom-field-$fieldId" name="custom-field-$fieldId" data-field-id="$fieldId" style="width: 120px;"/>"""
@@ -111,8 +272,7 @@
       sb.append(s"""<script>
                    |$$('#custom-field-$fieldId').focusout(function(){
                    |  const $$this = $$(this);
-                   |  const fieldId = $$this.data('field-id');
-                   |  $$.post('${helpers.url(repository)}/issues/customfield_validation/' + fieldId,
+                   |  $$.post('${helpers.url(repository)}/issues/customfield_validation/$fieldId',
                    |    { value: $$this.val() },
                    |    function(data){
                    |      if (data != '') {
@@ -128,7 +288,15 @@
       sb.toString()
     }
 
-    def fieldHtml(repository: RepositoryInfo, issueId: Int, fieldId: Int, value: String, editable: Boolean)(
+    override def fieldHtml(
+      repository: RepositoryInfo,
+      issueId: Int,
+      fieldId: Int,
+      fieldName: String,
+      constraints: Option[String],
+      value: String,
+      editable: Boolean
+    )(
       implicit context: Context
     ): String = {
       val sb = new StringBuilder
@@ -149,15 +317,14 @@
             |
             |$$('#custom-field-$fieldId-editor').focusout(function(){
             |  const $$this = $$(this);
-            |  const fieldId = $$this.data('field-id');
-            |  $$.post('${helpers.url(repository)}/issues/customfield_validation/' + fieldId,
+            |  $$.post('${helpers.url(repository)}/issues/customfield_validation/$fieldId',
             |    { value: $$this.val() },
             |    function(data){
             |      if (data != '') {
             |        $$('#custom-field-$fieldId-error').text(data);
             |      } else {
             |        $$('#custom-field-$fieldId-error').text('');
-            |        $$.post('${helpers.url(repository)}/issues/$issueId/customfield/' + fieldId,
+            |        $$.post('${helpers.url(repository)}/issues/$issueId/customfield/$fieldId',
             |          { value: $$this.val() },
             |          function(data){
             |            $$this.hide();
@@ -186,6 +353,11 @@
       sb.toString()
     }
 
-    def validate(name: String, value: String, messages: Messages): Option[String] = None
+    override def validate(
+      name: String,
+      constraints: Option[String],
+      value: String,
+      messages: Messages
+    ): Option[String] = None
   }
 }
diff --git a/src/main/scala/gitbucket/core/service/CustomFieldsService.scala b/src/main/scala/gitbucket/core/service/CustomFieldsService.scala
index cc5ddfa..0f5d41d 100644
--- a/src/main/scala/gitbucket/core/service/CustomFieldsService.scala
+++ b/src/main/scala/gitbucket/core/service/CustomFieldsService.scala
@@ -28,6 +28,7 @@
     repository: String,
     fieldName: String,
     fieldType: String,
+    constraints: Option[String],
     enableForIssues: Boolean,
     enableForPullRequests: Boolean
   )(implicit s: Session): Int = {
@@ -36,6 +37,7 @@
       repositoryName = repository,
       fieldName = fieldName,
       fieldType = fieldType,
+      constraints = constraints,
       enableForIssues = enableForIssues,
       enableForPullRequests = enableForPullRequests
     )
@@ -47,6 +49,7 @@
     fieldId: Int,
     fieldName: String,
     fieldType: String,
+    constraints: Option[String],
     enableForIssues: Boolean,
     enableForPullRequests: Boolean
   )(
@@ -54,8 +57,8 @@
   ): Unit =
     CustomFields
       .filter(_.byPrimaryKey(owner, repository, fieldId))
-      .map(t => (t.fieldName, t.fieldType, t.enableForIssues, t.enableForPullRequests))
-      .update((fieldName, fieldType, enableForIssues, enableForPullRequests))
+      .map(t => (t.fieldName, t.fieldType, t.constraints, t.enableForIssues, t.enableForPullRequests))
+      .update((fieldName, fieldType, constraints, enableForIssues, enableForPullRequests))
 
   def deleteCustomField(owner: String, repository: String, fieldId: Int)(implicit s: Session): Unit = {
     IssueCustomFields
diff --git a/src/main/scala/gitbucket/core/service/IssuesService.scala b/src/main/scala/gitbucket/core/service/IssuesService.scala
index bb5dec9..d6aa5a5 100644
--- a/src/main/scala/gitbucket/core/service/IssuesService.scala
+++ b/src/main/scala/gitbucket/core/service/IssuesService.scala
@@ -964,39 +964,39 @@
 
     def nonEmpty: Boolean = !isEmpty
 
-    def toFilterString: String =
-      (
-        List(
-          Some(s"is:${state}"),
-          author.map(author => s"author:${author}"),
-          assigned.map(assignee => s"assignee:${assignee}"),
-          mentioned.map(mentioned => s"mentions:${mentioned}")
-        ).flatten ++
-          labels.map(label => s"label:${label}") ++
-          List(
-            milestone.map {
-              case Some(x) => s"milestone:${x}"
-              case None    => "no:milestone"
-            },
-            priority.map {
-              case Some(x) => s"priority:${x}"
-              case None    => "no:priority"
-            },
-            (sort, direction) match {
-              case ("created", "desc")  => None
-              case ("created", "asc")   => Some("sort:created-asc")
-              case ("comments", "desc") => Some("sort:comments-desc")
-              case ("comments", "asc")  => Some("sort:comments-asc")
-              case ("updated", "desc")  => Some("sort:updated-desc")
-              case ("updated", "asc")   => Some("sort:updated-asc")
-              case ("priority", "desc") => Some("sort:priority-desc")
-              case ("priority", "asc")  => Some("sort:priority-asc")
-              case x                    => throw new MatchError(x)
-            },
-            visibility.map(visibility => s"visibility:${visibility}")
-          ).flatten ++
-          groups.map(group => s"group:${group}")
-      ).mkString(" ")
+//    def toFilterString: String =
+//      (
+//        List(
+//          Some(s"is:${state}"),
+//          author.map(author => s"author:${author}"),
+//          assigned.map(assignee => s"assignee:${assignee}"),
+//          mentioned.map(mentioned => s"mentions:${mentioned}")
+//        ).flatten ++
+//          labels.map(label => s"label:${label}") ++
+//          List(
+//            milestone.map {
+//              case Some(x) => s"milestone:${x}"
+//              case None    => "no:milestone"
+//            },
+//            priority.map {
+//              case Some(x) => s"priority:${x}"
+//              case None    => "no:priority"
+//            },
+//            (sort, direction) match {
+//              case ("created", "desc")  => None
+//              case ("created", "asc")   => Some("sort:created-asc")
+//              case ("comments", "desc") => Some("sort:comments-desc")
+//              case ("comments", "asc")  => Some("sort:comments-asc")
+//              case ("updated", "desc")  => Some("sort:updated-desc")
+//              case ("updated", "asc")   => Some("sort:updated-asc")
+//              case ("priority", "desc") => Some("sort:priority-desc")
+//              case ("priority", "asc")  => Some("sort:priority-asc")
+//              case x                    => throw new MatchError(x)
+//            },
+//            visibility.map(visibility => s"visibility:${visibility}")
+//          ).flatten ++
+//          groups.map(group => s"group:${group}")
+//      ).mkString(" ")
 
     def toURL: String =
       "?" + List(
diff --git a/src/main/twirl/gitbucket/core/issues/issueinfo.scala.html b/src/main/twirl/gitbucket/core/issues/issueinfo.scala.html
index 8502c95..bf19bca 100644
--- a/src/main/twirl/gitbucket/core/issues/issueinfo.scala.html
+++ b/src/main/twirl/gitbucket/core/issues/issueinfo.scala.html
@@ -140,8 +140,8 @@
   }
 </div>
 <span id="label-assigned">
-  @issueAssignees.map { asignee =>
-    <div>@helpers.avatarLink(asignee.assigneeUserName, 20) @helpers.user(asignee.assigneeUserName, styleClass="username strong small")</div>
+  @issueAssignees.map { assignee =>
+    <div>@helpers.avatarLink(assignee.assigneeUserName, 20) @helpers.user(assignee.assigneeUserName, styleClass="username strong small")</div>
   }
   @if(issueAssignees.isEmpty) {
     <span class="muted small">No one assigned</span>
@@ -158,10 +158,10 @@
     <div class="pull-right">
       @gitbucket.core.model.CustomFieldBehavior(field.fieldType).map { behavior =>
         @if(issue.nonEmpty) {
-          @Html(behavior.fieldHtml(repository, issue.get.issueId, field.fieldId, value.map(_.value).getOrElse(""), isManageable))
+          @Html(behavior.fieldHtml(repository, issue.get.issueId, field.fieldId, field.fieldName, field.constraints, value.map(_.value).getOrElse(""), isManageable))
         }
         @if(issue.isEmpty) {
-          @Html(behavior.createHtml(repository, field.fieldId))
+          @Html(behavior.createHtml(repository, field.fieldId, field.fieldName, field.constraints))
         }
       }
     </div>
diff --git a/src/main/twirl/gitbucket/core/settings/issuesfield.scala.html b/src/main/twirl/gitbucket/core/settings/issuesfield.scala.html
index 1daf1f4..d572782 100644
--- a/src/main/twirl/gitbucket/core/settings/issuesfield.scala.html
+++ b/src/main/twirl/gitbucket/core/settings/issuesfield.scala.html
@@ -7,6 +7,9 @@
       </div>
       <div class="col-md-4">
         @customField.fieldType
+        @customField.constraints.map { constraints =>
+          (@constraints)
+        }
       </div>
       <div class="col-md-2">
         @if(customField.enableForIssues) {
diff --git a/src/main/twirl/gitbucket/core/settings/issuesfieldform.scala.html b/src/main/twirl/gitbucket/core/settings/issuesfieldform.scala.html
index 6824ad6..7927c49 100644
--- a/src/main/twirl/gitbucket/core/settings/issuesfieldform.scala.html
+++ b/src/main/twirl/gitbucket/core/settings/issuesfieldform.scala.html
@@ -10,7 +10,9 @@
         <option value="double" @if(field.map(_.fieldType == "double").getOrElse(false)){selected}>Double</option>
         <option value="string" @if(field.map(_.fieldType == "string").getOrElse(false)){selected}>String</option>
         <option value="date" @if(field.map(_.fieldType == "date").getOrElse(false)){selected}>Date</option>
+        <option value="enum" @if(field.map(_.fieldType == "enum").getOrElse(false)){selected}>Enum</option>
       </select>
+      <input type="text" id="constraints-@fieldId" style="width: 300px; @if(!field.exists(_.fieldType == "enum")){display: none;}" class="form-control input-sm" value="@field.map(_.constraints)" placeholder="Comma-separated enum values">
       <label for="enableForIssues-@fieldId" class="normal" style="margin-left: 4px;">
         <input type="checkbox" id="enableForIssues-@fieldId" @if(field.map(_.enableForIssues).getOrElse(false)){checked}> Issues
       </label>
@@ -30,6 +32,7 @@
       $.post('@helpers.url(repository)/settings/issues/fields/@{if(fieldId == "new") "new" else s"$fieldId/edit"}', {
         'fieldName' : $('#fieldName-@fieldId').val(),
         'fieldType': $('#fieldType-@fieldId option:selected').val(),
+        'constraints': $('#constraints-@fieldId').val(),
         'enableForIssues': $('#enableForIssues-@fieldId').prop('checked'),
         'enableForPullRequests': $('#enableForPullRequests-@fieldId').prop('checked')
       }, function(data, status){
@@ -61,6 +64,14 @@
         $('#field-@fieldId').show();
       }
     });
+
+    $('#fieldType-@fieldId').change(function(){
+      if($(this).val() == 'enum') {
+        $('#constraints-@fieldId').show();
+      } else {
+        $('#constraints-@fieldId').hide();
+      }
+    });
   });
   </script>
 }