diff --git a/upb/message/accessors.h b/upb/message/accessors.h
index f812e2da47..723d10d871 100644
--- a/upb/message/accessors.h
+++ b/upb/message/accessors.h
@@ -68,11 +68,9 @@ UPB_API_INLINE bool upb_Message_SetExtension(upb_Message* msg,
                                              const upb_MiniTableExtension* e,
                                              const void* val, upb_Arena* a);
 
-UPB_API_INLINE uint32_t upb_Message_WhichOneofFieldNumber(
-    const upb_Message* message, const upb_MiniTableField* oneof_field) {
-  UPB_ASSUME(upb_MiniTableField_IsInOneof(oneof_field));
-  return UPB_PRIVATE(_upb_Message_GetOneofCase)(message, oneof_field);
-}
+UPB_API_INLINE void upb_Message_ClearOneof(upb_Message* msg,
+                                           const upb_MiniTable* m,
+                                           const upb_MiniTableField* f);
 
 // NOTE: The default_val is only used for fields that support presence.
 // For repeated/map fields, the resulting upb_Array*/upb_Map* can be NULL if a
diff --git a/upb/message/accessors_test.cc b/upb/message/accessors_test.cc
index 5b0dd3638c..524e5fb655 100644
--- a/upb/message/accessors_test.cc
+++ b/upb/message/accessors_test.cc
@@ -477,5 +477,28 @@ TEST(GeneratedCode, EnumClosedCheck) {
   EXPECT_TRUE(upb_MiniTableField_IsClosedEnum(closedEnumField));
   upb_Arena_Free(arena);
 }
+TEST(GeneratedCode, OneofClear) {
+  upb_Arena* arena = upb_Arena_New();
+
+  protobuf_test_messages_proto2_TestAllTypesProto2* msg =
+      protobuf_test_messages_proto2_TestAllTypesProto2_new(arena);
+
+  const upb_MiniTable* table =
+      &protobuf_0test_0messages__proto2__TestAllTypesProto2_msg_init;
+
+  // oneof_uint32
+  const upb_MiniTableField* oneofField =
+      upb_MiniTable_FindFieldByNumber(table, 111);
+  EXPECT_TRUE(upb_MiniTableField_IsInOneof(oneofField));
+  protobuf_test_messages_proto2_TestAllTypesProto2_set_oneof_uint32(msg, 522);
+  EXPECT_TRUE(
+      protobuf_test_messages_proto2_TestAllTypesProto2_has_oneof_uint32(msg));
+
+  upb_Message_ClearOneof((upb_Message*)msg, table, oneofField);
+  EXPECT_FALSE(
+      protobuf_test_messages_proto2_TestAllTypesProto2_has_oneof_uint32(msg));
+
+  upb_Arena_Free(arena);
+}
 
 }  // namespace
diff --git a/upb/message/internal/accessors.h b/upb/message/internal/accessors.h
index bfae12e601..1c5a397487 100644
--- a/upb/message/internal/accessors.h
+++ b/upb/message/internal/accessors.h
@@ -335,6 +335,27 @@ UPB_API_INLINE void upb_Message_ClearExtension(
   }
 }
 
+UPB_API_INLINE uint32_t upb_Message_WhichOneofFieldNumber(
+    const struct upb_Message* message, const upb_MiniTableField* oneof_field) {
+  UPB_ASSUME(upb_MiniTableField_IsInOneof(oneof_field));
+  return UPB_PRIVATE(_upb_Message_GetOneofCase)(message, oneof_field);
+}
+
+UPB_API_INLINE void upb_Message_ClearOneof(struct upb_Message* msg,
+                                           const upb_MiniTable* m,
+                                           const upb_MiniTableField* f) {
+  UPB_ASSERT(!upb_Message_IsFrozen(msg));
+  uint32_t field_number = upb_Message_WhichOneofFieldNumber(msg, f);
+  if (field_number == 0) {
+    // No field in the oneof is set.
+    return;
+  }
+
+  const upb_MiniTableField* field =
+      upb_MiniTable_FindFieldByNumber(m, field_number);
+  upb_Message_ClearBaseField(msg, field);
+}
+
 UPB_INLINE void _upb_Message_AssertMapIsUntagged(
     const struct upb_Message* msg, const upb_MiniTableField* field) {
   UPB_UNUSED(msg);