diff --git a/prometheus/promsafe/labels_provider.go b/prometheus/promsafe/labels_provider.go
new file mode 100644
index 000000000..0d73d6835
--- /dev/null
+++ b/prometheus/promsafe/labels_provider.go
@@ -0,0 +1,161 @@
+// Copyright 2024 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package promsafe
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+
+	"github.com/prometheus/client_golang/prometheus"
+)
+
+// LabelsProvider is an interface that allows to convert anything into prometheus.Labels
+// It allows to provide your own FAST implementation of Struct->prometheus.Labels conversion
+// without using reflection.
+type LabelsProvider interface {
+	ToPrometheusLabels() prometheus.Labels
+	ToLabelNames() []string
+}
+
+// LabelsProviderMarker is a marker interface for enforcing type-safety of StructLabelProvider.
+type LabelsProviderMarker interface {
+	labelsProviderMarker()
+}
+
+// StructLabelProvider should be embedded in any struct that serves as a label provider.
+type StructLabelProvider struct{}
+
+var _ LabelsProviderMarker = (*StructLabelProvider)(nil)
+
+func (s StructLabelProvider) labelsProviderMarker() {
+	panic("LabelsProviderMarker interface method should never be called")
+}
+
+// NewEmptyLabels creates a new empty labels instance of type T
+// It's a bit tricky as we want to support both structs and pointers to structs
+// e.g. &MyLabels{StructLabelProvider} or MyLabels{StructLabelProvider}
+func NewEmptyLabels[T LabelsProviderMarker]() T {
+	var emptyLabels T
+
+	val := reflect.ValueOf(&emptyLabels).Elem()
+	if val.Kind() == reflect.Ptr {
+		ptrType := val.Type().Elem()
+		newValue := reflect.New(ptrType).Interface().(T)
+		return newValue
+	}
+
+	return emptyLabels
+}
+
+//
+// Helpers
+//
+
+// promsafeTag is the tag name used for promsafe labels inside structs.
+// The tag is optional, as if not present, field is used with snake_cased FieldName.
+// It's useful to use a tag when you want to override the default naming or exclude a field from the metric.
+var promsafeTag = "promsafe"
+
+// SetPromsafeTag sets the tag name used for promsafe labels inside structs.
+func SetPromsafeTag(tag string) {
+	promsafeTag = tag
+}
+
+// iterateStructFields iterates over struct fields, calling the given function for each field.
+func iterateStructFields(structValue any, fn func(labelName string, fieldValue reflect.Value)) {
+	val := reflect.Indirect(reflect.ValueOf(structValue))
+	typ := val.Type()
+
+	for i := 0; i < typ.NumField(); i++ {
+		field := typ.Field(i)
+		if field.Anonymous {
+			continue
+		}
+
+		// Handle tag logic centrally
+		var labelName string
+		if ourTag := field.Tag.Get(promsafeTag); ourTag == "-" {
+			continue // Skip field
+		} else if ourTag != "" {
+			labelName = ourTag
+		} else {
+			labelName = toSnakeCase(field.Name)
+		}
+
+		fn(labelName, val.Field(i))
+	}
+}
+
+// extractLabelsWithValues extracts labels names+values from a given LabelsProviderMarker (parent instance of a StructLabelProvider)
+func extractLabelsWithValues(labelProvider LabelsProviderMarker) prometheus.Labels {
+	if any(labelProvider) == nil {
+		return nil
+	}
+
+	if clp, ok := labelProvider.(LabelsProvider); ok {
+		return clp.ToPrometheusLabels()
+	}
+
+	// extracting labels from a struct
+	labels := prometheus.Labels{}
+	iterateStructFields(labelProvider, func(labelName string, fieldValue reflect.Value) {
+		labels[labelName] = stringifyLabelValue(fieldValue)
+	})
+	return labels
+}
+
+// extractLabelNames extracts labels names from a given LabelsProviderMarker (parent instance of aStructLabelProvider)
+func extractLabelNames(labelProvider LabelsProviderMarker) []string {
+	if any(labelProvider) == nil {
+		return nil
+	}
+
+	// If custom implementation is done, just do it
+	if lp, ok := labelProvider.(LabelsProvider); ok {
+		return lp.ToLabelNames()
+	}
+
+	// Fallback to slow implementation via reflect
+	// Important! We return label names in order of fields in the struct
+	labelNames := make([]string, 0)
+	iterateStructFields(labelProvider, func(labelName string, fieldValue reflect.Value) {
+		labelNames = append(labelNames, labelName)
+	})
+
+	return labelNames
+}
+
+// stringifyLabelValue makes up a valid string value from a given field's value
+// It's used ONLY in fallback reflect mode
+// Field value might be a pointer, that's why we do reflect.Indirect()
+// Note: in future we can handle default values here as well
+func stringifyLabelValue(v reflect.Value) string {
+	// TODO: we probably want to handle custom type processing here
+	//       e.g. sometimes booleans need to be "on"/"off" instead of "true"/"false"
+	return fmt.Sprintf("%v", reflect.Indirect(v).Interface())
+}
+
+// Convert struct field names to snake_case for Prometheus label compliance.
+func toSnakeCase(s string) string {
+	s = strings.TrimSpace(s)
+	var result []rune
+	for i, r := range s {
+		if i > 0 && r >= 'A' && r <= 'Z' {
+			result = append(result, '_')
+		}
+		result = append(result, r)
+	}
+	return strings.ToLower(string(result))
+}
diff --git a/prometheus/promsafe/labels_provider_test.go b/prometheus/promsafe/labels_provider_test.go
new file mode 100644
index 000000000..b455ecd7a
--- /dev/null
+++ b/prometheus/promsafe/labels_provider_test.go
@@ -0,0 +1,301 @@
+// Copyright 2024 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package promsafe
+
+import (
+	"reflect"
+	"strconv"
+	"testing"
+
+	"github.com/prometheus/client_golang/prometheus"
+)
+
+// CustomType that will be stringified via String() method
+type CustomType int
+
+const (
+	CustomConst0 CustomType = iota
+	CustomConst1
+	CustomConst2
+)
+
+func (ct CustomType) String() string {
+	switch ct {
+	case CustomConst1:
+		return "c1"
+	case CustomConst2:
+		return "c2"
+
+	default:
+		return "c0"
+	}
+}
+
+// CustomTypeInt will remain as simple int
+type CustomTypeInt int
+
+const (
+	CustomConstInt100 CustomTypeInt = 100
+	CustomConstInt200 CustomTypeInt = 200
+)
+
+type TestLabels struct {
+	StructLabelProvider
+	Field1 string
+	Field2 int
+	Field3 bool
+
+	Field4 CustomType
+	Field5 CustomTypeInt
+}
+
+type TestLabelsWithTags struct {
+	StructLabelProvider
+	FieldA string `promsafe:"custom_a"`
+	FieldB int    `promsafe:"-"`
+	FieldC bool   // no promsafe tag, so default to snake_case of field_c
+}
+
+type TestLabelsWithPointers struct {
+	StructLabelProvider
+	Field1 *string
+	Field2 *int
+	Field3 *bool
+}
+
+type TestLabelsFast struct {
+	StructLabelProvider
+	Field1 string
+	Field2 int
+	Field3 bool
+}
+
+func (t TestLabelsFast) ToPrometheusLabels() prometheus.Labels {
+	return prometheus.Labels{
+		"f1": t.Field1,
+		"f2": strconv.Itoa(t.Field2),
+		"f3": strconv.FormatBool(t.Field3),
+	}
+}
+
+func (t TestLabelsFast) ToLabelNames() []string {
+	return []string{"f1", "f2", "f3"}
+}
+
+func Test_extractLabelsWithValues(t *testing.T) {
+	tests := []struct {
+		name       string
+		input      LabelsProviderMarker
+		expected   prometheus.Labels
+		shouldFail bool
+	}{
+		{
+			name: "Basic struct without custom tags",
+			input: TestLabels{
+				Field1: "value1",
+				Field2: 123,
+				Field3: true,
+				Field4: CustomConst1,
+				Field5: CustomConstInt200,
+			},
+			expected: prometheus.Labels{
+				"field1": "value1",
+				"field2": "123",
+				"field3": "true",
+				"field4": "c1",
+				"field5": "200",
+			},
+		},
+		{
+			name: "Struct with custom tags and exclusions",
+			input: TestLabelsWithTags{
+				FieldA: "customValue",
+				FieldB: 456,
+				FieldC: false,
+			},
+			expected: prometheus.Labels{
+				"custom_a": "customValue",
+				"field_c":  "false",
+			},
+		},
+		{
+			name: "Struct with pointers",
+			input: TestLabelsWithPointers{
+				Field1: ptr("ptrValue"),
+				Field2: ptr(789),
+				Field3: ptr(true),
+			},
+			expected: prometheus.Labels{
+				"field1": "ptrValue",
+				"field2": "789",
+				"field3": "true",
+			},
+		},
+		{
+			name: "Struct fast (with declared methods)",
+			input: TestLabelsFast{
+				Field1: "hello",
+				Field2: 100,
+				Field3: true,
+			},
+			expected: prometheus.Labels{
+				"f1": "hello",
+				"f2": "100",
+				"f3": "true",
+			},
+		},
+		{
+			name:     "Nil will return empty result",
+			input:    nil,
+			expected: nil,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			defer func() {
+				if r := recover(); r != nil {
+					if !tt.shouldFail {
+						t.Errorf("unexpected panic: %v", r)
+					}
+				}
+			}()
+
+			// Call extractLabelsFromStruct
+			got := extractLabelsWithValues(tt.input)
+
+			// Compare results
+			if !reflect.DeepEqual(got, tt.expected) {
+				t.Errorf("extractLabelFromStruct(%v) = %v; want %v", tt.input, got, tt.expected)
+			}
+		})
+	}
+}
+
+func Test_extractLabelNames(t *testing.T) {
+	tests := []struct {
+		name       string
+		input      LabelsProviderMarker
+		expected   []string
+		shouldFail bool
+	}{
+		{
+			name: "Basic struct without custom tags",
+			input: TestLabels{
+				Field1: "value1",
+				Field2: 123,
+				Field3: true,
+				Field4: CustomConst1,
+				Field5: CustomConstInt100,
+			},
+			expected: []string{"field1", "field2", "field3", "field4", "field5"},
+		},
+		{
+			name: "Struct with custom tags and exclusions",
+			input: TestLabelsWithTags{
+				FieldA: "customValue",
+				FieldB: 456,
+				FieldC: false,
+			},
+			expected: []string{"custom_a", "field_c"},
+		},
+		{
+			name: "Struct with pointers",
+			input: TestLabelsWithPointers{
+				Field1: ptr("ptrValue"),
+				Field2: ptr(789),
+				Field3: ptr(true),
+			},
+			expected: []string{"field1", "field2", "field3"},
+		},
+		{
+			name: "Struct fast (with declared methods)",
+			input: TestLabelsFast{
+				Field1: "hello",
+				Field2: 100,
+				Field3: true,
+			},
+			expected: []string{"f1", "f2", "f3"},
+		},
+		{
+			name:     "Nil will return empty result",
+			input:    nil,
+			expected: nil,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			defer func() {
+				if r := recover(); r != nil {
+					if !tt.shouldFail {
+						t.Errorf("unexpected panic: %v", r)
+					}
+				}
+			}()
+
+			// Call extractLabelsFromStruct
+			got := extractLabelNames(tt.input)
+
+			// Compare results
+			if !reflect.DeepEqual(got, tt.expected) {
+				t.Errorf("extractLabelFromStruct(%v) = %v; want %v", tt.input, got, tt.expected)
+			}
+		})
+	}
+}
+
+func Test_NewEmptyLabels(t *testing.T) {
+	got1 := NewEmptyLabels[TestLabels]()
+	if !reflect.DeepEqual(got1, TestLabels{}) {
+		t.Errorf("NewEmptyLabels[%T] = %v; want %v", TestLabels{}, got1, TestLabels{})
+	}
+	got2 := NewEmptyLabels[TestLabelsWithTags]()
+	if !reflect.DeepEqual(got2, TestLabelsWithTags{}) {
+		t.Errorf("NewEmptyLabels[%T] = %v; want %v", TestLabelsWithTags{}, got1, TestLabelsWithTags{})
+	}
+	got3 := NewEmptyLabels[TestLabelsWithPointers]()
+	if !reflect.DeepEqual(got3, TestLabelsWithPointers{}) {
+		t.Errorf("NewEmptyLabels[%T] = %v; want %v", TestLabelsWithPointers{}, got1, TestLabelsWithPointers{})
+	}
+	got4 := NewEmptyLabels[*TestLabelsFast]()
+	if !reflect.DeepEqual(*got4, TestLabelsFast{}) {
+		t.Errorf("NewEmptyLabels[%T] = %v; want %v", TestLabelsFast{}, *got4, TestLabelsFast{})
+	}
+}
+
+func Test_SetPromsafeTag(t *testing.T) {
+	SetPromsafeTag("prom")
+	defer func() {
+		SetPromsafeTag("")
+	}()
+	if promsafeTag != "prom" {
+		t.Errorf("promsafeTag = %v; want %v", promsafeTag, "prom")
+	}
+
+	type CustomTestLabels struct {
+		StructLabelProvider
+		FieldX string `prom:"x"`
+	}
+
+	extractedLabelNames := extractLabelNames(CustomTestLabels{})
+	if !reflect.DeepEqual(extractedLabelNames, []string{"x"}) {
+		t.Errorf("Using custom promsafeTag: extractLabelNames(%v) = %v; want %v", CustomTestLabels{}, extractedLabelNames, []string{"x"})
+	}
+}
+
+// Helper functions to create pointers
+func ptr[T any](v T) *T {
+	return &v
+}
diff --git a/prometheus/promsafe/promauto_adapter/promauto_adapter.go b/prometheus/promsafe/promauto_adapter/promauto_adapter.go
new file mode 100644
index 000000000..9dce4ee62
--- /dev/null
+++ b/prometheus/promsafe/promauto_adapter/promauto_adapter.go
@@ -0,0 +1,63 @@
+// Copyright 2024 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package promauto_adapter provides compatibility adapter for migration of calls of promauto into promsafe
+package promauto_adapter
+
+import (
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promauto"
+	"github.com/prometheus/client_golang/prometheus/promsafe"
+)
+
+// NewCounterVec behaves as adapter-promauto.NewCounterVec but with type-safe labels
+func NewCounterVec[T promsafe.LabelsProviderMarker](opts prometheus.CounterOpts) *promsafe.CounterVec[T] {
+	//_ = promauto.NewCounterVec // keeping for reference
+
+	c := promsafe.NewCounterVec[T](opts)
+	if prometheus.DefaultRegisterer != nil {
+		prometheus.DefaultRegisterer.MustRegister(c.Unsafe())
+	}
+	return c
+}
+
+// Factory is a promauto-like factory that allows type-safe labels.
+type Factory[T promsafe.LabelsProviderMarker] struct {
+	r prometheus.Registerer
+}
+
+// With behaves same as adapter-promauto.With but with type-safe labels
+func With[T promsafe.LabelsProviderMarker](r prometheus.Registerer) Factory[T] {
+	return Factory[T]{r: r}
+}
+
+// NewCounterVec behaves like adapter-promauto.NewCounterVec but with type-safe labels
+func (f Factory[T]) NewCounterVec(opts prometheus.CounterOpts) *promsafe.CounterVec[T] {
+	c := NewCounterVec[T](opts)
+	if f.r != nil {
+		f.r.MustRegister(c.Unsafe())
+	}
+	return c
+}
+
+// NewCounter wraps promauto.NewCounter.
+// As it doesn't require any labels, it's already type-safe, and we keep it for consistency.
+func (f Factory[T]) NewCounter(opts prometheus.CounterOpts) prometheus.Counter {
+	return promauto.With(f.r).NewCounter(opts)
+}
+
+// NewCounterFunc wraps promauto.NewCounterFunc.
+// As it doesn't require any labels, it's already type-safe, and we keep it for consistency.
+func (f Factory[T]) NewCounterFunc(opts prometheus.CounterOpts, function func() float64) prometheus.CounterFunc {
+	return promauto.With(f.r).NewCounterFunc(opts, function)
+}
diff --git a/prometheus/promsafe/promauto_adapter/promauto_adapter_test.go b/prometheus/promsafe/promauto_adapter/promauto_adapter_test.go
new file mode 100644
index 000000000..3df362e9c
--- /dev/null
+++ b/prometheus/promsafe/promauto_adapter/promauto_adapter_test.go
@@ -0,0 +1,81 @@
+// Copyright 2024 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package promauto_adapter_test
+
+import (
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promsafe"
+
+	promauto "github.com/prometheus/client_golang/prometheus/promsafe/promauto_adapter"
+)
+
+type EventType int64
+
+const (
+	EventTypeUndefined EventType = iota
+	EventTypeUser
+	EventTypeSystem
+)
+
+// String returns stringified value of EventType enum
+func (et EventType) String() string {
+	switch et {
+	case EventTypeUser:
+		return "user"
+	case EventTypeSystem:
+		return "system"
+	default:
+		return "undefined"
+	}
+}
+
+func ExampleNewCounterVec_promauto_adapted() {
+	// Examples on how to migrate from promauto to promsafe gently
+	//
+	// Before:
+	//   import "github.com/prometheus/client_golang/prometheus/promauto"
+	// func main() {
+	//   myReg := prometheus.NewRegistry()
+	//   counterOpts := prometheus.CounterOpts{Name:"..."}
+	//   promauto.With(myReg).NewCounterVec(counterOpts, []string{"event_type", "source"})
+	// }
+	//
+	// After:
+	//
+	//   import (
+	//     promauto "github.com/prometheus/client_golang/prometheus/promsafe/promauto_adapter"
+	//   )
+	//   ...
+
+	myReg := prometheus.NewRegistry()
+	counterOpts := prometheus.CounterOpts{
+		Name: "items_counted_detailed_auto",
+	}
+
+	type MyLabels struct {
+		promsafe.StructLabelProvider
+		EventType EventType
+		Source    string
+	}
+	// if declare Mylabels as global type you can add .ToPrometheusLabels() method
+	// that will use fast labels conversion instead of automatic reflect-based
+
+	c := promauto.With[MyLabels](myReg).NewCounterVec(counterOpts)
+
+	c.With(MyLabels{
+		EventType: EventTypeUser, Source: "source1",
+	}).Inc()
+
+	// Output:
+}
diff --git a/prometheus/promsafe/safe.go b/prometheus/promsafe/safe.go
new file mode 100644
index 000000000..29ac28a2a
--- /dev/null
+++ b/prometheus/promsafe/safe.go
@@ -0,0 +1,174 @@
+// Copyright 2024 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package promsafe provides a layer of type-safety for label management in Prometheus metrics.
+//
+// Promsafe introduces type-safe labels, ensuring that the labels used with
+// Prometheus metrics are explicitly defined and validated at compile-time. This
+// eliminates common runtime errors caused by mislabeling, such as typos or
+// incorrect label orders.
+//
+// The following example demonstrates how to create and use a CounterVec with
+// type-safe labels (compared to how it's done in a regular way):
+//
+//	package main
+//
+//	import (
+//		"strconv"
+//
+//		"github.com/prometheus/client_golang/prometheus"
+//		"github.com/prometheus/client_golang/prometheus/promsafe"
+//	)
+//
+//	// Original unsafe way (no type safety)
+//
+//	func originalUnsafeWay() {
+//		counterVec := prometheus.NewCounterVec(
+//			prometheus.CounterOpts{
+//				Name: "http_requests_total",
+//				Help: "Total number of HTTP requests by status code and method.",
+//			},
+//			[]string{"code", "method"}, // Labels defined as raw strings
+//		)
+//
+//		// No compile-time checks; label order and types must be correct
+//		// You have to know which and how many labels are expected (in proper order)
+//		counterVec.WithLabelValues("200", "GET").Inc()
+//
+//		// or you can use map, that is even more fragile
+//		counterVec.With(prometheus.Labels{"code": "200", "method": "GET"}).Inc()
+//	}
+//
+//	// Safe way (Quick implementation, reflect-based under-the-hood)
+//
+//	type Labels1 struct {
+//		promsafe.StructLabelProvider
+//		Code   int
+//		Method string
+//	}
+//
+//	func safeReflectWay() {
+//		counterVec := promsafe.NewCounterVec[Labels1](prometheus.CounterOpts{
+//			Name: "http_requests_total_reflection",
+//			Help: "Total number of HTTP requests by status code and method (reflection-based).",
+//		})
+//
+//		// Compile-time safe and readable; Will be converted into properly ordered list: "200", "GET"
+//		counterVec.With(Labels1{Method: "GET", Code: 200}).Inc()
+//	}
+//
+//	// Safe way with manual implementation (no reflection overhead, as fast as original)
+//
+//	type Labels2 struct {
+//		promsafe.StructLabelProvider
+//		Code   int
+//		Method string
+//	}
+//
+//	func (c Labels2) ToPrometheusLabels() prometheus.Labels {
+//		return prometheus.Labels{
+//			"code":   strconv.Itoa(c.Code), // Convert int to string
+//			"method": c.Method,
+//		}
+//	}
+//
+//	func (c Labels2) ToLabelNames() []string {
+//		return []string{"code", "method"}
+//	}
+//
+//	func safeManualWay() {
+//		counterVec := promsafe.NewCounterVec[Labels2](prometheus.CounterOpts{
+//			Name: "http_requests_total_custom",
+//			Help: "Total number of HTTP requests by status code and method (manual implementation).",
+//		})
+//		counterVec.With(Labels2{Code: 404, Method: "POST"}).Inc()
+//	}
+//
+// Package promsafe also provides compatibility adapter for integration with Prometheus's
+// `promauto` package, ensuring seamless adoption while preserving type-safety.
+// Methods that cannot guarantee type safety, such as those using raw `[]string`
+// label values, are explicitly deprecated and will raise runtime errors.
+//
+// A separate package allows conservative users to entirely ignore it. And
+// whoever wants to use it will do so explicitly, with an opportunity to read
+// this warning.
+//
+// Enjoy promsafe as it's safe!
+package promsafe
+
+import (
+	"github.com/prometheus/client_golang/prometheus"
+)
+
+// NewCounterVec creates a new CounterVec with type-safe labels.
+func NewCounterVec[T LabelsProviderMarker](opts prometheus.CounterOpts) *CounterVec[T] {
+	emptyLabels := NewEmptyLabels[T]()
+	inner := prometheus.NewCounterVec(opts, extractLabelNames(emptyLabels))
+
+	return &CounterVec[T]{inner: inner}
+}
+
+// CounterVec is a wrapper around prometheus.CounterVec that allows type-safe labels.
+type CounterVec[T LabelsProviderMarker] struct {
+	inner *prometheus.CounterVec
+}
+
+// GetMetricWith behaves like prometheus.CounterVec.GetMetricWith but with type-safe labels.
+func (c *CounterVec[T]) GetMetricWith(labels T) (prometheus.Counter, error) {
+	return c.inner.GetMetricWith(extractLabelsWithValues(labels))
+}
+
+// With behaves like prometheus.CounterVec.With but with type-safe labels.
+func (c *CounterVec[T]) With(labels T) prometheus.Counter {
+	return c.inner.With(extractLabelsWithValues(labels))
+}
+
+// CurryWith behaves like prometheus.CounterVec.CurryWith but with type-safe labels.
+// It still returns a CounterVec, but it's inner prometheus.CounterVec is curried.
+func (c *CounterVec[T]) CurryWith(labels T) (*CounterVec[T], error) {
+	curriedInner, err := c.inner.CurryWith(extractLabelsWithValues(labels))
+	if err != nil {
+		return nil, err
+	}
+	c.inner = curriedInner
+	return c, nil
+}
+
+// MustCurryWith behaves like prometheus.CounterVec.MustCurryWith but with type-safe labels.
+// It still returns a CounterVec, but it's inner prometheus.CounterVec is curried.
+func (c *CounterVec[T]) MustCurryWith(labels T) *CounterVec[T] {
+	c.inner = c.inner.MustCurryWith(extractLabelsWithValues(labels))
+	return c
+}
+
+// Unsafe returns the underlying prometheus.CounterVec
+// it's used to call any other method of prometheus.CounterVec that doesn't require type-safe labels
+func (c *CounterVec[T]) Unsafe() *prometheus.CounterVec {
+	return c.inner
+}
+
+// NewCounter simply creates a new prometheus.Counter.
+// As it doesn't have any labels, it's already type-safe.
+// We keep this method just for consistency and interface fulfillment.
+func NewCounter(opts prometheus.CounterOpts) prometheus.Counter {
+	return prometheus.NewCounter(opts)
+}
+
+// NewCounterFunc wraps a new prometheus.CounterFunc.
+// As it doesn't have any labels, it's already type-safe.
+// We keep this method just for consistency and interface fulfillment.
+func NewCounterFunc(opts prometheus.CounterOpts, function func() float64) prometheus.CounterFunc {
+	return prometheus.NewCounterFunc(opts, function)
+}
+
+// TODO: other methods (Gauge, Histogram, Summary, etc.)
diff --git a/prometheus/promsafe/safe_test.go b/prometheus/promsafe/safe_test.go
new file mode 100644
index 000000000..0b2eabb37
--- /dev/null
+++ b/prometheus/promsafe/safe_test.go
@@ -0,0 +1,162 @@
+// Copyright 2024 The Prometheus Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package promsafe_test
+
+import (
+	"fmt"
+	"log"
+	"strconv"
+	"testing"
+
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promsafe"
+)
+
+// These are Examples that can be treated as basic smoke tests
+
+func ExampleNewCounterVec_multiple_labels_manual() {
+	// Manually registering with multiple labels
+
+	type MyCounterLabels struct {
+		promsafe.StructLabelProvider
+		EventType string
+		Success   bool
+		Position  uint8 // yes, it's a number, but be careful with high-cardinality labels
+
+		ShouldNotBeUsed string `promsafe:"-"`
+	}
+
+	c := promsafe.NewCounterVec[MyCounterLabels](prometheus.CounterOpts{
+		Name: "items_counted_detailed",
+	})
+
+	// Manually register the counter
+	if err := prometheus.Register(c.Unsafe()); err != nil {
+		log.Fatal("could not register: ", err.Error())
+	}
+
+	// and now, because of generics we can call Inc() with filled struct of labels:
+	counter := c.With(MyCounterLabels{
+		EventType: "reservation", Success: true, Position: 1,
+	})
+	counter.Inc()
+
+	// Output:
+}
+
+// FastMyLabels is a struct that will have a custom method that converts to prometheus.Labels
+type FastMyLabels struct {
+	promsafe.StructLabelProvider
+	EventType string
+	Source    string
+}
+
+// ToPrometheusLabels does a superfast conversion to labels. So no reflection is involved.
+func (f FastMyLabels) ToPrometheusLabels() prometheus.Labels {
+	return prometheus.Labels{"event_type": f.EventType, "source": f.Source}
+}
+
+// ToLabelNames does a superfast label names list. So no reflection is involved.
+func (f FastMyLabels) ToLabelNames() []string {
+	return []string{"event_type", "source"}
+}
+
+func ExampleNewCounterVec_fast_safe_labels_provider() {
+	// Note: fast labels provider has a drawback: they can't be declared as inline structs
+	//       as we need methods
+
+	c := promsafe.NewCounterVec[FastMyLabels](prometheus.CounterOpts{
+		Name: "items_counted_detailed_fast",
+	})
+
+	// Manually register the counter
+	if err := prometheus.Register(c.Unsafe()); err != nil {
+		log.Fatal("could not register: ", err.Error())
+	}
+
+	counter := c.With(FastMyLabels{
+		EventType: "reservation", Source: "source1",
+	})
+	counter.Inc()
+
+	// Output:
+}
+
+// ====================
+// Benchmark Tests
+// ====================
+
+type TestLabels struct {
+	promsafe.StructLabelProvider
+	Label1 string
+	Label2 int
+	Label3 bool
+}
+
+type TestLabelsFast struct {
+	promsafe.StructLabelProvider
+	Label1 string
+	Label2 int
+	Label3 bool
+}
+
+func (t TestLabelsFast) ToPrometheusLabels() prometheus.Labels {
+	return prometheus.Labels{
+		"label1": t.Label1,
+		"label2": strconv.Itoa(t.Label2),
+		"label3": strconv.FormatBool(t.Label3),
+	}
+}
+
+func (t TestLabelsFast) ToLabelNames() []string {
+	return []string{"label1", "label2", "label3"}
+}
+
+func BenchmarkCompareCreatingMetric(b *testing.B) {
+	// Note: on stage of creation metrics, Unique metric names are not required,
+	//       but let it be for consistency
+
+	b.Run("Prometheus NewCounterVec", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			uniqueMetricName := fmt.Sprintf("test_counter_prometheus_%d_%d", b.N, i)
+
+			_ = prometheus.NewCounterVec(prometheus.CounterOpts{
+				Name: uniqueMetricName,
+				Help: "A test counter created just using prometheus.NewCounterVec",
+			}, []string{"label1", "label2", "label3"})
+		}
+	})
+
+	b.Run("Promsafe (reflect) NewCounterVec", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			uniqueMetricName := fmt.Sprintf("test_counter_promsafe_%d_%d", b.N, i)
+
+			_ = promsafe.NewCounterVec[TestLabels](prometheus.CounterOpts{
+				Name: uniqueMetricName,
+				Help: "A test counter created using promauto.NewCounterVec",
+			})
+		}
+	})
+
+	b.Run("Promsafe (fast) NewCounterVec", func(b *testing.B) {
+		for i := 0; i < b.N; i++ {
+			uniqueMetricName := fmt.Sprintf("test_counter_promsafe_fast_%d_%d", b.N, i)
+
+			_ = promsafe.NewCounterVec[TestLabelsFast](prometheus.CounterOpts{
+				Name: uniqueMetricName,
+				Help: "A test counter created using promauto.NewCounterVec",
+			})
+		}
+	})
+}