diff --git a/examples/ulib/.gitignore b/examples/ulib/.gitignore
index d2f0dfa7a93..f275acf9df1 100644
--- a/examples/ulib/.gitignore
+++ b/examples/ulib/.gitignore
@@ -1,2 +1,4 @@
+/boot
+/boot_static
 /demo
 /demo_static
diff --git a/examples/ulib/Makefile b/examples/ulib/Makefile
index a11c5dad9be..36de37daf51 100644
--- a/examples/ulib/Makefile
+++ b/examples/ulib/Makefile
@@ -29,13 +29,14 @@
 include config.mk
 
 # Programs to build
-progs := demo
+progs := demo boot
 
 # Program definitions - list of object files for each program
 demo_objs := demo.o demo_helper.o
+boot_objs := boot.o bootflow.o
 
 # Objects that need system headers (default is U-Boot headers)
-sys-objs := demo_helper.o
+sys-objs := boot.o demo_helper.o
 
 # Include build rules (must come after variable definitions)
 include rules.mk
diff --git a/examples/ulib/README b/examples/ulib/README
index e6f48bf79cd..23fb2a3d097 100644
--- a/examples/ulib/README
+++ b/examples/ulib/README
@@ -18,8 +18,8 @@ This creates:
 Example Programs
 ----------------
 
-The examples are built automatically as part of the U-Boot build. So far there
-is only one.
+The examples are built automatically as part of the U-Boot build. Each program
+demonstrates different aspects of the U-Boot library.
 
 **demo.c** - Demonstrates using U-Boot library functions
 
@@ -28,6 +28,14 @@ is only one.
 - Reads and displays system information
 - Shows the U-Boot version
 
+**boot.c** - Demonstrates booting an OS using U-Boot bootflow
+
+- Shows bootflow scanning and booting functionality
+- Uses system headers for main program logic (boot.c)
+- Uses U-Boot headers for bootflow internals (bootflow.c)
+- Demonstrates attaching disk images and scanning for bootable OS
+- Attempts to boot the first discovered bootflow
+
 Building Examples
 -----------------
 
@@ -50,14 +58,21 @@ Running Examples
     # Run the demo (static version)
     ./demo_static
 
+    # Run the boot example (requires disk image at /home/sglass/u/mmc1.img)
+    LD_LIBRARY_PATH=/tmp/b/sandbox ./boot
+
+    # Run the boot example (static version)
+    ./boot_static
+
 Key Points
 ----------
 
-- Avoid including U-Boot headers that conflict with system headers. This
-  Makefile gives priority to the system headers
+- Files are compiled with U-Boot headers by default, except those listed in
+  sys-objs which use system headers
 - Use ulib_init() to init the library
 - Use ulib_uninit() to clean up
 - Set LD_LIBRARY_PATH when running dynamically linked programs
+- Each program automatically gets both dynamic and static versions built
 
 Copying for External Use
 -------------------------
diff --git a/examples/ulib/boot.c b/examples/ulib/boot.c
new file mode 100644
index 00000000000..ad8a52d5bc9
--- /dev/null
+++ b/examples/ulib/boot.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Boot test program using U-Boot library
+ *
+ * This demonstrates basic initialization and cleanup of the U-Boot library.
+ * It will be used for testing bootstd functionality using ulib.
+ *
+ * Copyright 2025 Canonical Ltd.
+ * Written by Simon Glass <simon.glass@canonical.com>
+ */
+
+/* Use system headers, not U-Boot headers */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <u-boot-api.h>
+#include <u-boot-lib.h>
+
+/* Forward declaration for bootflow function */
+int bootflow_internal_scan(void);
+
+/* Forward declaration for host function (simplified) */
+int host_create_attach_file(const char *label, const char *filename,
+			    int removable, unsigned long blksz,
+			    void *devp);
+
+static void fatal(const char *msg)
+{
+	fprintf(stderr, "Error: %s\n", msg);
+	exit(1);
+}
+
+static int try_boot(void)
+{
+	int ret;
+
+	printf("Scanning for bootflows...\n");
+
+	/* MMC device attachment will be done in bootflow_internal_scan() */
+
+	ret = bootflow_internal_scan();
+	if (ret) {
+		printf("Internal scan failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	int ret;
+
+	ret = ulib_init(argv[0]);
+	if (ret)
+		fatal("Failed to init U-Boot library");
+
+	ret = try_boot();
+	if (ret)
+		printf("Boot attempt failed: %d\n", ret);
+
+	ulib_uninit();
+	return ret;
+}
diff --git a/examples/ulib/bootflow.c b/examples/ulib/bootflow.c
new file mode 100644
index 00000000000..e100a0ee492
--- /dev/null
+++ b/examples/ulib/bootflow.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootflow internal functions using U-Boot headers
+ *
+ * This demonstrates functions that need direct access to U-Boot internal
+ * structures and would be compiled with U-Boot headers first.
+ *
+ * Copyright 2025 Canonical Ltd.
+ * Written by Simon Glass <simon.glass@canonical.com>
+ */
+
+/* This file uses U-Boot headers first */
+#include <bootflow.h>
+#include <bootdev.h>
+#include <bootmeth.h>
+#include <bootstd.h>
+#include <dm/device.h>
+#include <sandbox_host.h>
+#include <u-boot-api.h>
+
+static void show_bootflow(int num, struct bootflow *bflow)
+{
+	ub_printf("Bootflow %d:\n", num);
+	ub_printf("  name: '%s'\n", bflow->name ? bflow->name : "(null)");
+	ub_printf("  state: %s\n", bootflow_state_get_name(bflow->state));
+	ub_printf("  method: '%s'\n",
+		  bflow->method ? bflow->method->name : "(null)");
+	ub_printf("  fname: '%s'\n", bflow->fname ? bflow->fname : "(null)");
+	ub_printf("  dev: '%s'\n", bflow->dev ? bflow->dev->name : "(null)");
+	ub_printf("  part: %d\n", bflow->part);
+	ub_printf("  size: %d\n", bflow->size);
+	ub_printf("  err: %d\n", bflow->err);
+	if (bflow->os_name)
+		ub_printf("  os_name: '%s'\n", bflow->os_name);
+	if (bflow->logo)
+		ub_printf("  logo: present (%zu bytes)\n", bflow->logo_size);
+	ub_printf("\n");
+}
+
+int bootflow_internal_scan(void)
+{
+	struct bootflow bflow;
+	struct bootflow_iter iter;
+	struct bootstd_priv *std;
+	struct bootflow *first_bflow;
+	struct udevice *host_dev;
+	int ret, count = 0;
+
+	ub_printf("Internal bootflow scan using U-Boot headers first\n");
+
+	/* Get bootstd private data */
+	ret = bootstd_get_priv(&std);
+	if (ret) {
+		ub_printf("bootstd_get_priv() failed: %d\n", ret);
+		return ret;
+	}
+
+	/* Set bootmethod order to only use extlinux and efi */
+	ret = bootmeth_set_order("extlinux efi");
+	if (ret) {
+		ub_printf("bootmeth_set_order() failed: %d\n", ret);
+		return ret;
+	}
+	ub_printf("Set bootmethod order to: extlinux efi\n");
+
+	/* Now we can actually use bootflow.h definitions! */
+	ub_printf("BOOTFLOWST_MEDIA = %d\n", BOOTFLOWST_MEDIA);
+	ub_printf("sizeof(struct bootflow) = %zu\n", sizeof(struct bootflow));
+	ub_printf("sizeof(struct bootflow_iter) = %zu\n",
+		  sizeof(struct bootflow_iter));
+
+	/* Attach the MMC image file to make bootflows available */
+	ub_printf("Attaching mmc1.img file...\n");
+	ret = host_create_attach_file("mmc1", "/home/sglass/u/mmc1.img", false,
+				      512, &host_dev);
+	if (ret) {
+		ub_printf("host_create_attach_file() failed: %d\n", ret);
+
+		return ret;
+	}
+
+	/* List all available bootdevs */
+	ub_printf("Available bootdevs:\n");
+	bootdev_list(true);
+
+	/* Try to scan for the first bootflow */
+	ret = bootflow_scan_first(NULL, NULL, &iter, BOOTFLOWIF_SHOW,
+				  &bflow);
+	if (ret) {
+		ub_printf("bootflow_scan_first() failed: %d\n", ret);
+		return ret;
+	}
+
+	/* Iterate through all bootflows */
+	do {
+		count++;
+		show_bootflow(count, &bflow);
+
+		/* Add bootflow to the global list */
+		ret = bootstd_add_bootflow(&bflow);
+		if (ret < 0) {
+			ub_printf("bootstd_add_bootflow() failed: %d\n", ret);
+			bootflow_free(&bflow);
+		}
+
+		/* Get next bootflow */
+		ret = bootflow_scan_next(&iter, &bflow);
+	} while (!ret);
+
+	ub_printf("Found %d total bootflows\n", count);
+
+	/* Clean up the iterator */
+	bootflow_iter_uninit(&iter);
+
+	/* Return immediately if no bootflows found */
+	if (!count) {
+		ub_printf("No bootflows found to boot\n");
+		return 0;
+	}
+
+	/* Boot the first bootflow */
+	/* Get the first bootflow from the global list */
+	first_bflow = alist_getw(&std->bootflows, 0, struct bootflow);
+	if (!first_bflow) {
+		ub_printf("Failed to get first bootflow from global list\n");
+		return -1;
+	}
+
+	ub_printf("\bBooting: %s\n",
+		  first_bflow->name ? first_bflow->name : "(unnamed)");
+	if (first_bflow->os_name)
+		ub_printf("OS: %s\n", first_bflow->os_name);
+
+	ret = bootflow_boot(first_bflow);
+	if (ret)
+		ub_printf("bootflow_boot() failed: %dE\n", ret);
+	else
+		ub_printf("bootflow_boot() succeeded (shouldn't reach here!)\n");
+
+	return 0;
+}
