diff --git a/api/k8sdeps/kunstruct/hasher.go b/api/k8sdeps/kunstruct/hasher.go
index 35bf5eb220..1b263fede0 100644
--- a/api/k8sdeps/kunstruct/hasher.go
+++ b/api/k8sdeps/kunstruct/hasher.go
@@ -5,7 +5,6 @@ package kunstruct
 
 import (
 	"encoding/json"
-	"fmt"
 
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -21,7 +20,7 @@ func NewKustHash() *kustHash {
 	return &kustHash{}
 }
 
-// Hash returns a hash of either a ConfigMap or a Secret
+// Hash returns a hash of the given object
 func (h *kustHash) Hash(m ifc.Kunstructured) (string, error) {
 	u := unstructured.Unstructured{
 		Object: m.Map(),
@@ -36,15 +35,12 @@ func (h *kustHash) Hash(m ifc.Kunstructured) (string, error) {
 		return configMapHash(cm)
 	case "Secret":
 		sec, err := unstructuredToSecret(u)
-
 		if err != nil {
 			return "", err
 		}
 		return secretHash(sec)
 	default:
-		return "", fmt.Errorf(
-			"type %s is not supported for hashing in %v",
-			kind, m.Map())
+		return unstructuredHash(&u)
 	}
 }
 
@@ -76,6 +72,21 @@ func secretHash(sec *corev1.Secret) (string, error) {
 	return h, nil
 }
 
+// unstructuredHash creates a hash for an arbitrary type.
+// All fields of the object are taken into account when generating the hash.
+// This is a fallback for when a specalised hash for the type is unavailable.
+func unstructuredHash(u *unstructured.Unstructured) (string, error) {
+	encoded, err := json.Marshal(u.Object)
+	if err != nil {
+		return "", err
+	}
+	h, err := hasher.Encode(hasher.Hash(string(encoded)))
+	if err != nil {
+		return "", err
+	}
+	return h, nil
+}
+
 // encodeConfigMap encodes a ConfigMap.
 // Data, Kind, and Name are taken into account.
 // BinaryData is included if it's not empty to avoid useless key in output.
diff --git a/api/k8sdeps/kunstruct/hasher_test.go b/api/k8sdeps/kunstruct/hasher_test.go
index 0c43ec4d95..be84ad3828 100644
--- a/api/k8sdeps/kunstruct/hasher_test.go
+++ b/api/k8sdeps/kunstruct/hasher_test.go
@@ -9,6 +9,7 @@ import (
 	"testing"
 
 	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 )
 
 func TestConfigMapHash(t *testing.T) {
@@ -75,6 +76,39 @@ func TestSecretHash(t *testing.T) {
 	}
 }
 
+func TestUnstructuredHash(t *testing.T) {
+	cases := []struct {
+		desc         string
+		unstructured *unstructured.Unstructured
+		hash         string
+		err          string
+	}{
+		{"minimal", &unstructured.Unstructured{
+			Object: map[string]interface{}{
+				"apiVersion": "test/v1",
+				"kind":       "TestResource",
+				"metadata":   map[string]string{"name": "my-resource"}},
+		}, "2tt46d7f79", ""},
+		{"with spec", &unstructured.Unstructured{
+			Object: map[string]interface{}{
+				"apiVersion": "test/v1",
+				"kind":       "TestResource",
+				"metadata":   map[string]string{"name": "my-resource"},
+				"spec":       map[string]interface{}{"foo": 1, "bar": "abc"}},
+		}, "6gc62g4m6k", ""},
+	}
+
+	for _, c := range cases {
+		h, err := unstructuredHash(c.unstructured)
+		if SkipRest(t, c.desc, err, c.err) {
+			continue
+		}
+		if c.hash != h {
+			t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
+		}
+	}
+}
+
 func TestEncodeConfigMap(t *testing.T) {
 	cases := []struct {
 		desc   string
diff --git a/site/content/en/guides/plugins/_index.md b/site/content/en/guides/plugins/_index.md
index d3d6b970e3..095361844b 100644
--- a/site/content/en/guides/plugins/_index.md
+++ b/site/content/en/guides/plugins/_index.md
@@ -253,7 +253,11 @@ A generator exec plugin can adjust the generator options for the resources it em
 
 Resources can be marked as needing to be processed by the internal hash transformer by including the `needs-hash` annotation. When set valid values for the annotation are `"true"` and `"false"` which respectively enable or disable hash suffixing for the resource. Omitting the annotation is equivalent to setting the value `"false"`.
 
-If this annotation is set on a resource not supported by the hash transformer the build will fail.
+Hashes are determined as follows:
+
+* For `ConfigMap` resources, hashes are based on the values of the `name`, `data`, and `binaryData` fields.
+* For `Secret` resources, hashes are based on the values of the `name`, `type`, `data`, and `stringData` fields.
+* For any other object type, hashes are based on the entire object content (i.e. all fields).
 
 Example: