diff --git a/reftable/reftable-table.h b/reftable/reftable-table.h
index a78db9eea7e..4d61ce6cf77 100644
--- a/reftable/reftable-table.h
+++ b/reftable/reftable-table.h
@@ -10,6 +10,7 @@
 #define REFTABLE_TABLE_H
 
 #include "reftable-iterator.h"
+#include "reftable-block.h"
 #include "reftable-blocksource.h"
 
 /*
@@ -99,4 +100,19 @@ uint64_t reftable_table_min_update_index(struct reftable_table *t);
 /* print blocks onto stdout for debugging. */
 int reftable_table_print_blocks(const char *tablename);
 
+/*
+ * An iterator that iterates through the blocks contained in a given table.
+ */
+struct reftable_table_iterator {
+	void *iter_arg;
+};
+
+int reftable_table_iterator_init(struct reftable_table_iterator *it,
+				 struct reftable_table *t);
+
+void reftable_table_iterator_release(struct reftable_table_iterator *it);
+
+int reftable_table_iterator_next(struct reftable_table_iterator *it,
+				 const struct reftable_block **out);
+
 #endif
diff --git a/reftable/table.c b/reftable/table.c
index 50ffad7edcb..a2a0c7a1d2f 100644
--- a/reftable/table.c
+++ b/reftable/table.c
@@ -804,3 +804,50 @@ int reftable_table_print_blocks(const char *tablename)
 	table_iter_close(&ti);
 	return err;
 }
+
+int reftable_table_iterator_init(struct reftable_table_iterator *it,
+				 struct reftable_table *t)
+{
+	struct table_iter *ti;
+	int err;
+
+	REFTABLE_ALLOC_ARRAY(ti, 1);
+	if (!ti)
+		return REFTABLE_OUT_OF_MEMORY_ERROR;
+
+	err = table_iter_init(ti, t);
+	if (err < 0)
+		goto out;
+
+	it->iter_arg = ti;
+	err = 0;
+
+out:
+	if (err < 0)
+		reftable_free(ti);
+	return err;
+}
+
+void reftable_table_iterator_release(struct reftable_table_iterator *it)
+{
+	if (!it->iter_arg)
+		return;
+	table_iter_close(it->iter_arg);
+	reftable_free(it->iter_arg);
+	it->iter_arg = NULL;
+}
+
+int reftable_table_iterator_next(struct reftable_table_iterator *it,
+				 const struct reftable_block **out)
+{
+	struct table_iter *ti = it->iter_arg;
+	int err;
+
+	err = table_iter_next_block(ti);
+	if (err)
+		return err;
+
+	*out = &ti->block;
+
+	return 0;
+}
diff --git a/t/unit-tests/t-reftable-table.c b/t/unit-tests/t-reftable-table.c
index 77c59dbf46d..ba39cdf9a76 100644
--- a/t/unit-tests/t-reftable-table.c
+++ b/t/unit-tests/t-reftable-table.c
@@ -1,7 +1,10 @@
 #include "test-lib.h"
 #include "lib-reftable.h"
 #include "reftable/blocksource.h"
+#include "reftable/constants.h"
+#include "reftable/iter.h"
 #include "reftable/table.h"
+#include "strbuf.h"
 
 static int t_table_seek_once(void)
 {
@@ -88,9 +91,116 @@ static int t_table_reseek(void)
 	return 0;
 }
 
+static int t_table_block_iterator(void)
+{
+	struct reftable_block_source source = { 0 };
+	struct reftable_table_iterator it = { 0 };
+	struct reftable_ref_record *records;
+	const struct reftable_block *block;
+	struct reftable_table *table;
+	struct reftable_buf buf = REFTABLE_BUF_INIT;
+	struct {
+		uint8_t block_type;
+		uint16_t header_off;
+		uint16_t restart_count;
+		uint16_t record_count;
+	} expected_blocks[] = {
+		{
+			.block_type = BLOCK_TYPE_REF,
+			.header_off = 24,
+			.restart_count = 10,
+			.record_count = 158,
+		},
+		{
+			.block_type = BLOCK_TYPE_REF,
+			.restart_count = 10,
+			.record_count = 159,
+		},
+		{
+			.block_type = BLOCK_TYPE_REF,
+			.restart_count = 10,
+			.record_count = 159,
+		},
+		{
+			.block_type = BLOCK_TYPE_REF,
+			.restart_count = 2,
+			.record_count = 24,
+		},
+		{
+			.block_type = BLOCK_TYPE_INDEX,
+			.restart_count = 1,
+			.record_count = 4,
+		},
+		{
+			.block_type = BLOCK_TYPE_OBJ,
+			.restart_count = 1,
+			.record_count = 1,
+		},
+	};
+	const size_t nrecords = 500;
+	int ret;
+
+	REFTABLE_CALLOC_ARRAY(records, nrecords);
+	for (size_t i = 0; i < nrecords; i++) {
+		records[i].value_type = REFTABLE_REF_VAL1;
+		records[i].refname = xstrfmt("refs/heads/branch-%03"PRIuMAX,
+					     (uintmax_t) i);
+	}
+
+	t_reftable_write_to_buf(&buf, records, nrecords, NULL, 0, NULL);
+	block_source_from_buf(&source, &buf);
+
+	ret = reftable_table_new(&table, &source, "name");
+	check(!ret);
+
+	ret = reftable_table_iterator_init(&it, table);
+	check(!ret);
+
+	for (size_t i = 0; i < ARRAY_SIZE(expected_blocks); i++) {
+		struct reftable_iterator record_it = { 0 };
+		struct reftable_record record = {
+			.type = expected_blocks[i].block_type,
+		};
+
+		ret = reftable_table_iterator_next(&it, &block);
+		check(!ret);
+
+		check_int(block->block_type, ==, expected_blocks[i].block_type);
+		check_int(block->header_off, ==, expected_blocks[i].header_off);
+		check_int(block->restart_count, ==, expected_blocks[i].restart_count);
+
+		ret = reftable_block_init_iterator(block, &record_it);
+		check(!ret);
+
+		for (size_t j = 0; ; j++) {
+			ret = iterator_next(&record_it, &record);
+			if (ret > 0) {
+				check_int(j, ==, expected_blocks[i].record_count);
+				break;
+			}
+			check(!ret);
+		}
+
+		reftable_iterator_destroy(&record_it);
+		reftable_record_release(&record);
+	}
+
+	ret = reftable_table_iterator_next(&it, &block);
+	check_int(ret, ==, 1);
+
+	for (size_t i = 0; i < nrecords; i++)
+		reftable_free(records[i].refname);
+	reftable_table_iterator_release(&it);
+	reftable_table_decref(table);
+	reftable_buf_release(&buf);
+	reftable_free(records);
+	return 0;
+}
+
 int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
 {
 	TEST(t_table_seek_once(), "table can seek once");
 	TEST(t_table_reseek(), "table can reseek multiple times");
+	TEST(t_table_block_iterator(), "table can iterate through blocks");
 	return test_done();
 }
