diff --git a/pkg/storage/internalstorage/features.go b/pkg/storage/internalstorage/features.go
new file mode 100644
index 0000000000000000000000000000000000000000..b59ec093d0c8575b2e381be883984d4b21f04a50
--- /dev/null
+++ b/pkg/storage/internalstorage/features.go
@@ -0,0 +1,25 @@
+package internalstorage
+
+import (
+	"k8s.io/apimachinery/pkg/util/runtime"
+	utilfeature "k8s.io/apiserver/pkg/util/feature"
+	"k8s.io/component-base/featuregate"
+)
+
+const (
+	// AllowRawSQLQuery is a feature gate for the apiserver to allow querying by the raw sql.
+	//
+	// owner: @cleverhu
+	// alpha: v0.3.0
+	AllowRawSQLQuery featuregate.Feature = "AllowRawSQLQuery"
+)
+
+func init() {
+	runtime.Must(utilfeature.DefaultMutableFeatureGate.Add(defaultInternalStorageFeatureGates))
+}
+
+// defaultInternalStorageFeatureGates consists of all known custom internalstorage feature keys.
+// To add a new feature, define a key for it above and add it here.
+var defaultInternalStorageFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
+	AllowRawSQLQuery: {Default: false, PreRelease: featuregate.Alpha},
+}
diff --git a/pkg/storage/internalstorage/util.go b/pkg/storage/internalstorage/util.go
index bef36ad9f8c5197f41593ea2b687770cea9a2c97..9d9faf2d105a1ddde5ac28ad2f9055a065970b67 100644
--- a/pkg/storage/internalstorage/util.go
+++ b/pkg/storage/internalstorage/util.go
@@ -14,10 +14,15 @@ import (
 	"k8s.io/apimachinery/pkg/selection"
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/apimachinery/pkg/util/validation/field"
+	utilfeature "k8s.io/apiserver/pkg/util/feature"
 
 	internal "github.com/clusterpedia-io/api/clusterpedia"
 )
 
+const (
+	URLQueryWhereSQL = "whereSQL"
+)
+
 var (
 	supportedOrderByFields = sets.NewString("cluster", "namespace", "name", "created_at", "resource_version")
 )
@@ -55,6 +60,15 @@ func applyListOptionsToQuery(query *gorm.DB, opts *internal.ListOptions, applyFn
 		query = query.Where("created_at < ?", opts.Before.Time.UTC())
 	}
 
+	if utilfeature.DefaultMutableFeatureGate.Enabled(AllowRawSQLQuery) {
+		if len(opts.URLQuery[URLQueryWhereSQL]) > 0 {
+			// TODO: prevent SQL injection.
+			// If a string of numbers is passed in from SQL, the query will be taken as ID by default.
+			// If the SQL contains English letter, it will be passed in as column.
+			query = query.Where(opts.URLQuery[URLQueryWhereSQL][0])
+		}
+	}
+
 	if opts.LabelSelector != nil {
 		if requirements, selectable := opts.LabelSelector.Requirements(); selectable {
 			for _, requirement := range requirements {
@@ -137,7 +151,7 @@ func applyListOptionsToQuery(query *gorm.DB, opts *internal.ListOptions, applyFn
 	for _, orderby := range opts.OrderBy {
 		field := orderby.Field
 		if supportedOrderByFields.Has(field) {
-			if field == "resource_version"{
+			if field == "resource_version" {
 				field = "CAST(resource_version as decimal)"
 			}
 			column := clause.OrderByColumn{
diff --git a/staging/src/github.com/clusterpedia-io/api/clusterpedia/types.go b/staging/src/github.com/clusterpedia-io/api/clusterpedia/types.go
index d4f7b059e7c2128f11c15cec5731a029810509d1..e6a8faa779c6f1e5d4e272b4ce214f65d65f045e 100644
--- a/staging/src/github.com/clusterpedia-io/api/clusterpedia/types.go
+++ b/staging/src/github.com/clusterpedia-io/api/clusterpedia/types.go
@@ -68,7 +68,7 @@ type ListOptions struct {
 	ExtraLabelSelector labels.Selector
 
 	// +k8s:conversion-fn:drop
-	ExtraQuery url.Values
+	URLQuery url.Values
 
 	// RelatedResources []schema.GroupVersionKind
 }
diff --git a/staging/src/github.com/clusterpedia-io/api/clusterpedia/v1beta1/conversion.go b/staging/src/github.com/clusterpedia-io/api/clusterpedia/v1beta1/conversion.go
index 59b6a58a6a6e8f180772b590f99d05968de5e077..460fc62ac994b16e5e018d1edfc3bbe88d5b3d49 100644
--- a/staging/src/github.com/clusterpedia-io/api/clusterpedia/v1beta1/conversion.go
+++ b/staging/src/github.com/clusterpedia-io/api/clusterpedia/v1beta1/conversion.go
@@ -183,6 +183,10 @@ func Convert_v1beta1_ListOptions_To_clusterpedia_ListOptions(in *ListOptions, ou
 	if out.Before.Before(out.Since) {
 		return fmt.Errorf("Invalid Query, Since is after Before")
 	}
+	if len(in.urlQuery) > 0 {
+		// Out URLQuery will not be modified, so deepcopy is not used here.
+		out.URLQuery = in.urlQuery
+	}
 	return nil
 }
 
@@ -229,6 +233,8 @@ func Convert_url_Values_To_v1beta1_ListOptions(in *url.Values, out *ListOptions,
 	if err := metav1.Convert_url_Values_To_v1_ListOptions(in, &out.ListOptions, s); err != nil {
 		return err
 	}
+	// Save the native query parameters for use by listoptions.
+	out.urlQuery = *in
 
 	return autoConvert_url_Values_To_v1beta1_ListOptions(in, out, s)
 }
diff --git a/staging/src/github.com/clusterpedia-io/api/clusterpedia/v1beta1/types.go b/staging/src/github.com/clusterpedia-io/api/clusterpedia/v1beta1/types.go
index 6c667e0a42d68e34d0c108ed6b06298437712ad7..28b5e5ff03cca3644fb5aebf9b5041559d245f67 100644
--- a/staging/src/github.com/clusterpedia-io/api/clusterpedia/v1beta1/types.go
+++ b/staging/src/github.com/clusterpedia-io/api/clusterpedia/v1beta1/types.go
@@ -1,6 +1,8 @@
 package v1beta1
 
 import (
+	"net/url"
+
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 )
@@ -46,6 +48,8 @@ type ListOptions struct {
 
 	// +optional
 	WithRemainingCount *bool `json:"withRemainingCount,omitempty"`
+
+	urlQuery url.Values
 }
 
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
diff --git a/staging/src/github.com/clusterpedia-io/api/clusterpedia/v1beta1/zz_generated.conversion.go b/staging/src/github.com/clusterpedia-io/api/clusterpedia/v1beta1/zz_generated.conversion.go
index 02851d6a978ad5fe3eaabed0e58f4f6869e2d4bb..a306b897cb555b2df974592bcc6dccde785bf10e 100644
--- a/staging/src/github.com/clusterpedia-io/api/clusterpedia/v1beta1/zz_generated.conversion.go
+++ b/staging/src/github.com/clusterpedia-io/api/clusterpedia/v1beta1/zz_generated.conversion.go
@@ -201,6 +201,7 @@ func autoConvert_v1beta1_ListOptions_To_clusterpedia_ListOptions(in *ListOptions
 	out.OwnerSeniority = in.OwnerSeniority
 	out.WithContinue = (*bool)(unsafe.Pointer(in.WithContinue))
 	out.WithRemainingCount = (*bool)(unsafe.Pointer(in.WithRemainingCount))
+	// WARNING: in.urlQuery requires manual conversion: does not exist in peer-type
 	return nil
 }
 
@@ -227,7 +228,7 @@ func autoConvert_clusterpedia_ListOptions_To_v1beta1_ListOptions(in *clusterpedi
 	out.WithRemainingCount = (*bool)(unsafe.Pointer(in.WithRemainingCount))
 	// WARNING: in.EnhancedFieldSelector requires manual conversion: does not exist in peer-type
 	// WARNING: in.ExtraLabelSelector requires manual conversion: does not exist in peer-type
-	// WARNING: in.ExtraQuery requires manual conversion: does not exist in peer-type
+	// WARNING: in.URLQuery requires manual conversion: does not exist in peer-type
 	return nil
 }
 
@@ -318,5 +319,7 @@ func autoConvert_url_Values_To_v1beta1_ListOptions(in *url.Values, out *ListOpti
 	} else {
 		out.WithRemainingCount = nil
 	}
+	// WARNING: Field urlQuery does not have json tag, skipping.
+
 	return nil
 }
diff --git a/staging/src/github.com/clusterpedia-io/api/clusterpedia/v1beta1/zz_generated.deepcopy.go b/staging/src/github.com/clusterpedia-io/api/clusterpedia/v1beta1/zz_generated.deepcopy.go
index 100ba415c72999891b6c6a568c48c2bb9c70ae05..ae0ca2e62bfe77e0a4352d997c54f223b3036dd2 100644
--- a/staging/src/github.com/clusterpedia-io/api/clusterpedia/v1beta1/zz_generated.deepcopy.go
+++ b/staging/src/github.com/clusterpedia-io/api/clusterpedia/v1beta1/zz_generated.deepcopy.go
@@ -6,6 +6,8 @@
 package v1beta1
 
 import (
+	url "net/url"
+
 	runtime "k8s.io/apimachinery/pkg/runtime"
 )
 
@@ -110,6 +112,21 @@ func (in *ListOptions) DeepCopyInto(out *ListOptions) {
 		*out = new(bool)
 		**out = **in
 	}
+	if in.urlQuery != nil {
+		in, out := &in.urlQuery, &out.urlQuery
+		*out = make(url.Values, len(*in))
+		for key, val := range *in {
+			var outVal []string
+			if val == nil {
+				(*out)[key] = nil
+			} else {
+				in, out := &val, &outVal
+				*out = make([]string, len(*in))
+				copy(*out, *in)
+			}
+			(*out)[key] = outVal
+		}
+	}
 	return
 }
 
diff --git a/staging/src/github.com/clusterpedia-io/api/clusterpedia/zz_generated.deepcopy.go b/staging/src/github.com/clusterpedia-io/api/clusterpedia/zz_generated.deepcopy.go
index f44a9a1e7ce21b5eea35ebabadbce724789b10a5..cceef58d923527d9a7c5905216eb4e83afb9b669 100644
--- a/staging/src/github.com/clusterpedia-io/api/clusterpedia/zz_generated.deepcopy.go
+++ b/staging/src/github.com/clusterpedia-io/api/clusterpedia/zz_generated.deepcopy.go
@@ -149,8 +149,8 @@ func (in *ListOptions) DeepCopyInto(out *ListOptions) {
 	if in.ExtraLabelSelector != nil {
 		out.ExtraLabelSelector = in.ExtraLabelSelector.DeepCopySelector()
 	}
-	if in.ExtraQuery != nil {
-		in, out := &in.ExtraQuery, &out.ExtraQuery
+	if in.URLQuery != nil {
+		in, out := &in.URLQuery, &out.URLQuery
 		*out = make(url.Values, len(*in))
 		for key, val := range *in {
 			var outVal []string