diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index bb75ed1..c8dd257 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -551,6 +551,15 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_wal_records</structname><indexterm><primary>pg_stat_wal_records</primary></indexterm></entry>
+      <entry>One row per WAL record type, showing how many WAL records of
+       each type have been generated. See
+       <link linkend="monitoring-pg-stat-wal-records-view">
+       <structname>pg_stat_wal_records</structname></link> for details.
+      </entry>
+     </row>
+
      <!-- all "stat" for schema objects, by "importance" -->
 
      <row>
@@ -3689,6 +3698,92 @@ description | Waiting for a newly initialized WAL file to reach durable storage
 
 </sect2>
 
+ <sect2 id="monitoring-pg-stat-wal-records-view">
+  <title><structname>pg_stat_wal_records</structname></title>
+
+  <indexterm>
+   <primary>pg_stat_wal_records</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_stat_wal_records</structname> view contains one row
+   for each WAL record type that has been generated since the last statistics
+   reset.  It provides a breakdown of WAL generation by resource manager and
+   record type, which can be used to identify which operations are
+   responsible for WAL volume.  Record types with a count of zero are omitted.
+  </para>
+
+  <para>
+   Unlike <structname>pg_stat_wal</structname>, which provides aggregate
+   totals, this view shows the composition of WAL traffic.  Unlike
+   <function>pg_get_wal_stats()</function> from
+   <xref linkend="pgwalinspect"/>, this view tracks live cumulative
+   counters without needing to read WAL files from disk.
+  </para>
+
+  <table id="pg-stat-wal-records-view" xreflabel="pg_stat_wal_records">
+   <title><structname>pg_stat_wal_records</structname> View</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>resource_manager</structfield> <type>text</type>
+      </para>
+      <para>
+       Name of the WAL resource manager (e.g.
+       <literal>Heap</literal>, <literal>Btree</literal>,
+       <literal>Transaction</literal>, <literal>XLOG</literal>).
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>record_type</structfield> <type>text</type>
+      </para>
+      <para>
+       Name of the WAL record type within the resource manager (e.g.
+       <literal>INSERT</literal>, <literal>HOT_UPDATE</literal>,
+       <literal>COMMIT</literal>, <literal>INSERT_LEAF</literal>).
+       These names are provided by each resource manager's
+       <function>rm_identify</function> callback.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of WAL records of this type generated since the last
+       statistics reset.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>stats_reset</structfield> <type>timestamp with time zone</type>
+      </para>
+      <para>
+       Time at which WAL record statistics were last reset.
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+
+ </sect2>
+
  <sect2 id="monitoring-pg-stat-database-view">
   <title><structname>pg_stat_database</structname></title>
 
@@ -5500,6 +5595,12 @@ description | Waiting for a newly initialized WAL file to reach durable storage
           <structname>pg_stat_wal</structname> view.
          </para>
         </listitem>
+        <listitem>
+         <para>
+          <literal>wal_records</literal>: Reset all the counters shown in the
+          <structname>pg_stat_wal_records</structname> view.
+         </para>
+        </listitem>
         <listitem>
          <para>
           <literal>NULL</literal> or not specified: All the counters from the
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index e4a819e..f2556f5 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -36,6 +36,7 @@
 #include "executor/instrument.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
+#include "pgstat.h"
 #include "replication/origin.h"
 #include "storage/bufmgr.h"
 #include "storage/proc.h"
@@ -531,6 +532,8 @@ XLogInsert(RmgrId rmid, uint8 info)
 								  fpi_bytes, topxid_included);
 	} while (!XLogRecPtrIsValid(EndPos));
 
+	pgstat_count_wal_record(rmid, info);
+
 	XLogResetInsertion();
 
 	return EndPos;
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e540180..a8b7d5c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1274,6 +1274,14 @@ CREATE VIEW pg_stat_wal AS
         w.stats_reset
     FROM pg_stat_get_wal() w;
 
+CREATE VIEW pg_stat_wal_records AS
+    SELECT
+        w.resource_manager,
+        w.record_type,
+        w.count,
+        w.stats_reset
+    FROM pg_stat_get_wal_records() w;
+
 CREATE VIEW pg_stat_progress_analyze AS
     SELECT
         S.pid AS pid, S.datid AS datid, D.datname AS datname,
diff --git a/src/backend/utils/activity/meson.build b/src/backend/utils/activity/meson.build
index 1aa7ece..23c6c6c 100644
--- a/src/backend/utils/activity/meson.build
+++ b/src/backend/utils/activity/meson.build
@@ -18,6 +18,7 @@ backend_sources += files(
   'pgstat_slru.c',
   'pgstat_subscription.c',
   'pgstat_wal.c',
+  'pgstat_walrecords.c',
   'pgstat_xact.c',
 )
 
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index eb8ccba..dde41b8 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -500,6 +500,23 @@ static const PgStat_KindInfo pgstat_kind_builtin_infos[PGSTAT_KIND_BUILTIN_SIZE]
 		.reset_all_cb = pgstat_wal_reset_all_cb,
 		.snapshot_cb = pgstat_wal_snapshot_cb,
 	},
+
+	[PGSTAT_KIND_WAL_RECORDS] = {
+		.name = "wal_records",
+
+		.fixed_amount = true,
+		.write_to_file = true,
+
+		.snapshot_ctl_off = offsetof(PgStat_Snapshot, wal_records),
+		.shared_ctl_off = offsetof(PgStat_ShmemControl, wal_records),
+		.shared_data_off = offsetof(PgStatShared_WalRecords, stats),
+		.shared_data_len = sizeof(((PgStatShared_WalRecords *) 0)->stats),
+
+		.flush_static_cb = pgstat_walrecords_flush_cb,
+		.init_shmem_cb = pgstat_walrecords_init_shmem_cb,
+		.reset_all_cb = pgstat_walrecords_reset_all_cb,
+		.snapshot_cb = pgstat_walrecords_snapshot_cb,
+	},
 };
 
 /*
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 9185a8e..49951d3 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -16,6 +16,7 @@
 
 #include "access/htup_details.h"
 #include "access/xlog.h"
+#include "access/xlog_internal.h"
 #include "access/xlogprefetcher.h"
 #include "catalog/catalog.h"
 #include "catalog/pg_authid.h"
@@ -1741,6 +1742,75 @@ pg_stat_get_wal(PG_FUNCTION_ARGS)
 									wal_stats->stat_reset_timestamp));
 }
 
+/*
+ * Returns per-resource-manager WAL record type statistics.
+ */
+Datum
+pg_stat_get_wal_records(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_WAL_RECORDS_COLS	4
+	ReturnSetInfo *rsinfo;
+	PgStat_WalRecordStats *stats;
+
+	InitMaterializedSRF(fcinfo, 0);
+	rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	stats = pgstat_fetch_stat_wal_records();
+
+	for (int rmid = 0; rmid < PGSTAT_NUM_WAL_RMGRS; rmid++)
+	{
+		const char *rm_name;
+
+		/* Skip resource managers that don't exist */
+		if (!RmgrIdExists((RmgrId) rmid))
+			continue;
+
+		rm_name = RmgrTable[rmid].rm_name;
+
+		for (int info_idx = 0; info_idx < PGSTAT_NUM_WAL_REC_TYPES; info_idx++)
+		{
+			Datum		values[PG_STAT_GET_WAL_RECORDS_COLS] = {0};
+			bool		nulls[PG_STAT_GET_WAL_RECORDS_COLS] = {0};
+			const char *record_type = NULL;
+			char		record_type_buf[32];
+
+			/* Skip entries with no recorded activity */
+			if (stats->counts[rmid][info_idx] == 0)
+				continue;
+
+			/* Resource manager name */
+			values[0] = CStringGetTextDatum(rm_name);
+
+			/* Record type name from rm_identify, with numeric fallback */
+			if (RmgrTable[rmid].rm_identify)
+				record_type = RmgrTable[rmid].rm_identify((uint8) (info_idx << 4));
+
+			if (record_type)
+				values[1] = CStringGetTextDatum(record_type);
+			else
+			{
+				snprintf(record_type_buf, sizeof(record_type_buf),
+						 "0x%02X", info_idx << 4);
+				values[1] = CStringGetTextDatum(record_type_buf);
+			}
+
+			/* Count */
+			values[2] = Int64GetDatum(stats->counts[rmid][info_idx]);
+
+			/* Stats reset timestamp */
+			if (stats->stat_reset_timestamp != 0)
+				values[3] = TimestampTzGetDatum(stats->stat_reset_timestamp);
+			else
+				nulls[3] = true;
+
+			tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
+								values, nulls);
+		}
+	}
+
+	return (Datum) 0;
+}
+
 Datum
 pg_stat_get_lock(PG_FUNCTION_ARGS)
 {
@@ -1965,6 +2035,7 @@ pg_stat_reset_shared(PG_FUNCTION_ARGS)
 		XLogPrefetchResetStats();
 		pgstat_reset_of_kind(PGSTAT_KIND_SLRU);
 		pgstat_reset_of_kind(PGSTAT_KIND_WAL);
+		pgstat_reset_of_kind(PGSTAT_KIND_WAL_RECORDS);
 
 		PG_RETURN_VOID();
 	}
@@ -1987,11 +2058,13 @@ pg_stat_reset_shared(PG_FUNCTION_ARGS)
 		pgstat_reset_of_kind(PGSTAT_KIND_SLRU);
 	else if (strcmp(target, "wal") == 0)
 		pgstat_reset_of_kind(PGSTAT_KIND_WAL);
+	else if (strcmp(target, "wal_records") == 0)
+		pgstat_reset_of_kind(PGSTAT_KIND_WAL_RECORDS);
 	else
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("unrecognized reset target: \"%s\"", target),
-				 errhint("Target must be \"archiver\", \"bgwriter\", \"checkpointer\", \"io\", \"recovery_prefetch\", \"slru\", or \"wal\".")));
+				 errhint("Target must be \"archiver\", \"bgwriter\", \"checkpointer\", \"io\", \"recovery_prefetch\", \"slru\", \"wal\", or \"wal_records\".")));
 
 	PG_RETURN_VOID();
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0118e97..ecc9fb2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6079,6 +6079,15 @@
   proargmodes => '{i,o,o,o,o,o,o}',
   proargnames => '{backend_pid,wal_records,wal_fpi,wal_bytes,wal_fpi_bytes,wal_buffers_full,stats_reset}',
   prosrc => 'pg_stat_get_backend_wal' },
+{ oid => '6',
+  descr => 'statistics: per-resource-manager WAL record type counts',
+  proname => 'pg_stat_get_wal_records', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => '',
+  proallargtypes => '{text,text,int8,timestamptz}',
+  proargmodes => '{o,o,o,o}',
+  proargnames => '{resource_manager,record_type,count,stats_reset}',
+  prosrc => 'pg_stat_get_wal_records' },
 { oid => '6248', descr => 'statistics: information about WAL prefetching',
   proname => 'pg_stat_get_recovery_prefetch', prorows => '1', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 8e3549c..15a1d63 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -514,6 +514,23 @@ typedef struct PgStat_WalStats
 	TimestampTz stat_reset_timestamp;
 } PgStat_WalStats;
 
+/*
+ * Number of WAL resource managers (must match RM_N_IDS from rmgr.h) and
+ * record info types per resource manager (top 4 bits of info byte).
+ */
+#define PGSTAT_NUM_WAL_RMGRS		256
+#define PGSTAT_NUM_WAL_REC_TYPES	16
+
+/* -------
+ * PgStat_WalRecordStats		Per-resource-manager WAL record type counts
+ * -------
+ */
+typedef struct PgStat_WalRecordStats
+{
+	PgStat_Counter counts[PGSTAT_NUM_WAL_RMGRS][PGSTAT_NUM_WAL_REC_TYPES];
+	TimestampTz stat_reset_timestamp;
+} PgStat_WalRecordStats;
+
 /* -------
  * PgStat_Backend		Backend statistics
  * -------
@@ -834,6 +851,24 @@ extern void pgstat_report_wal(bool force);
 extern PgStat_WalStats *pgstat_fetch_stat_wal(void);
 
 
+/*
+ * Functions in pgstat_walrecords.c
+ */
+
+extern PgStat_WalRecordStats *pgstat_fetch_stat_wal_records(void);
+
+/*
+ * Variables in pgstat_walrecords.c
+ */
+
+extern PGDLLIMPORT PgStat_Counter pgstat_pending_wal_records[PGSTAT_NUM_WAL_RMGRS][PGSTAT_NUM_WAL_REC_TYPES];
+
+#define pgstat_count_wal_record(rmid, info) \
+	do { \
+		pgstat_pending_wal_records[(rmid)][((info) >> 4) & 0x0F]++; \
+	} while (0)
+
+
 /*
  * Variables in pgstat.c
  */
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 9770442..060a44a 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -488,6 +488,13 @@ typedef struct PgStatShared_Wal
 	PgStat_WalStats stats;
 } PgStatShared_Wal;
 
+typedef struct PgStatShared_WalRecords
+{
+	/* lock protects ->stats */
+	LWLock		lock;
+	PgStat_WalRecordStats stats;
+} PgStatShared_WalRecords;
+
 
 
 /* ----------
@@ -583,6 +590,7 @@ typedef struct PgStat_ShmemControl
 	PgStatShared_Lock lock;
 	PgStatShared_SLRU slru;
 	PgStatShared_Wal wal;
+	PgStatShared_WalRecords wal_records;
 
 	/*
 	 * Custom stats data with fixed-numbered objects, indexed by (PgStat_Kind
@@ -619,6 +627,8 @@ typedef struct PgStat_Snapshot
 
 	PgStat_WalStats wal;
 
+	PgStat_WalRecordStats wal_records;
+
 	/*
 	 * Data in snapshot for custom fixed-numbered statistics, indexed by
 	 * (PgStat_Kind - PGSTAT_KIND_CUSTOM_MIN).  Each entry is allocated in
@@ -847,6 +857,16 @@ extern void pgstat_wal_reset_all_cb(TimestampTz ts);
 extern void pgstat_wal_snapshot_cb(void);
 
 
+/*
+ * Functions in pgstat_walrecords.c
+ */
+
+extern bool pgstat_walrecords_flush_cb(bool nowait);
+extern void pgstat_walrecords_init_shmem_cb(void *stats);
+extern void pgstat_walrecords_reset_all_cb(TimestampTz ts);
+extern void pgstat_walrecords_snapshot_cb(void);
+
+
 /*
  * Functions in pgstat_subscription.c
  */
diff --git a/src/include/utils/pgstat_kind.h b/src/include/utils/pgstat_kind.h
index 2d78a02..85fc070 100644
--- a/src/include/utils/pgstat_kind.h
+++ b/src/include/utils/pgstat_kind.h
@@ -39,9 +39,10 @@
 #define PGSTAT_KIND_LOCK	11
 #define PGSTAT_KIND_SLRU	12
 #define PGSTAT_KIND_WAL	13
+#define PGSTAT_KIND_WAL_RECORDS	14
 
 #define PGSTAT_KIND_BUILTIN_MIN PGSTAT_KIND_DATABASE
-#define PGSTAT_KIND_BUILTIN_MAX PGSTAT_KIND_WAL
+#define PGSTAT_KIND_BUILTIN_MAX PGSTAT_KIND_WAL_RECORDS
 #define PGSTAT_KIND_BUILTIN_SIZE (PGSTAT_KIND_BUILTIN_MAX + 1)
 
 /* Custom stats kinds */
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index ea7f784..279d7be 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -982,6 +982,38 @@ SELECT wal_bytes > :wal_bytes_before FROM pg_stat_wal;
  t
 (1 row)
 
+-- Test pg_stat_wal_records
+-- WAL-generating activity above should have produced records
+SELECT count(*) > 0 AS has_records FROM pg_stat_wal_records;
+ has_records 
+-------------
+ t
+(1 row)
+
+SELECT count(DISTINCT resource_manager) > 0 AS has_rmgrs FROM pg_stat_wal_records;
+ has_rmgrs 
+-----------
+ t
+(1 row)
+
+-- Every row should have non-null resource_manager and record_type
+SELECT count(*) = 0 AS no_nulls
+  FROM pg_stat_wal_records
+  WHERE resource_manager IS NULL OR record_type IS NULL;
+ no_nulls 
+----------
+ t
+(1 row)
+
+-- Counts should all be positive
+SELECT count(*) = 0 AS all_positive
+  FROM pg_stat_wal_records
+  WHERE count <= 0;
+ all_positive 
+--------------
+ t
+(1 row)
+
 SELECT pg_stat_force_next_flush();
  pg_stat_force_next_flush 
 --------------------------
@@ -1127,10 +1159,25 @@ SELECT stats_reset > :'wal_reset_ts'::timestamptz FROM pg_stat_wal;
  t
 (1 row)
 
+-- Test that reset_shared with wal_records specified as the stats type works
+SELECT stats_reset AS wal_records_reset_ts FROM pg_stat_wal_records LIMIT 1 \gset
+SELECT pg_stat_reset_shared('wal_records');
+ pg_stat_reset_shared 
+----------------------
+ 
+(1 row)
+
+SELECT count(*) AS wal_records_post_reset FROM pg_stat_wal_records \gset
+SELECT :wal_records_post_reset = 0 AS reset_cleared_counts;
+ reset_cleared_counts 
+----------------------
+ t
+(1 row)
+
 -- Test error case for reset_shared with unknown stats type
 SELECT pg_stat_reset_shared('unknown');
 ERROR:  unrecognized reset target: "unknown"
-HINT:  Target must be "archiver", "bgwriter", "checkpointer", "io", "recovery_prefetch", "slru", or "wal".
+HINT:  Target must be "archiver", "bgwriter", "checkpointer", "io", "recovery_prefetch", "slru", "wal", or "wal_records".
 -- Test that reset works for pg_stat_database and pg_stat_database_conflicts
 -- Since pg_stat_database stats_reset starts out as NULL, reset it once first so that we
 -- have a baseline for comparison. The same for pg_stat_database_conflicts as it shares
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 65d8968..a3378ef 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -464,6 +464,19 @@ CHECKPOINT (FLUSH_UNLOGGED);
 SELECT num_requested > :rqst_ckpts_before FROM pg_stat_checkpointer;
 SELECT wal_bytes > :wal_bytes_before FROM pg_stat_wal;
 
+-- Test pg_stat_wal_records
+-- WAL-generating activity above should have produced records
+SELECT count(*) > 0 AS has_records FROM pg_stat_wal_records;
+SELECT count(DISTINCT resource_manager) > 0 AS has_rmgrs FROM pg_stat_wal_records;
+-- Every row should have non-null resource_manager and record_type
+SELECT count(*) = 0 AS no_nulls
+  FROM pg_stat_wal_records
+  WHERE resource_manager IS NULL OR record_type IS NULL;
+-- Counts should all be positive
+SELECT count(*) = 0 AS all_positive
+  FROM pg_stat_wal_records
+  WHERE count <= 0;
+
 SELECT pg_stat_force_next_flush();
 SELECT wal_bytes > :backend_wal_bytes_before FROM pg_stat_get_backend_wal(pg_backend_pid());
 
@@ -520,6 +533,12 @@ SELECT stats_reset AS wal_reset_ts FROM pg_stat_wal \gset
 SELECT pg_stat_reset_shared('wal');
 SELECT stats_reset > :'wal_reset_ts'::timestamptz FROM pg_stat_wal;
 
+-- Test that reset_shared with wal_records specified as the stats type works
+SELECT stats_reset AS wal_records_reset_ts FROM pg_stat_wal_records LIMIT 1 \gset
+SELECT pg_stat_reset_shared('wal_records');
+SELECT count(*) AS wal_records_post_reset FROM pg_stat_wal_records \gset
+SELECT :wal_records_post_reset = 0 AS reset_cleared_counts;
+
 -- Test error case for reset_shared with unknown stats type
 SELECT pg_stat_reset_shared('unknown');
 
