diff --git a/CHANGELOG.md b/CHANGELOG.md
index ba10d892..f4f35a1a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
+- `ISupportDryRun` — an opt-in interface exposing `bool IsDryRun { get; set; }` for
+ ETL stages that support a dry run: the full pipeline is exercised but the external
+ side effect that mutates a destination or source is skipped. Implemented by the
+ stage that honours it (not by the base classes). (#259)
+
### Changed
### Deprecated
diff --git a/src/Wolfgang.Etl.Abstractions/ISupportDryRun.cs b/src/Wolfgang.Etl.Abstractions/ISupportDryRun.cs
new file mode 100644
index 00000000..69db54a1
--- /dev/null
+++ b/src/Wolfgang.Etl.Abstractions/ISupportDryRun.cs
@@ -0,0 +1,42 @@
+namespace Wolfgang.Etl.Abstractions;
+
+///
+/// Implemented by ETL stages that support a dry run — a mode in which the
+/// full pipeline is exercised but the external side effect that mutates a destination
+/// or source is skipped.
+///
+///
+/// A dry run runs the stage exactly as a real run would — enumerating the source,
+/// evaluating SkipItemCount and MaximumItemCount, incrementing the
+/// progress counters, firing the progress-timer callback and logging as usual — but
+/// it does not perform the side effect that mutates external state. This is
+/// useful for validating a newly built pipeline (source feed, mapping, batching,
+/// throttling) against production data without writing, and for estimating how long
+/// a real run would take.
+///
+/// The interface is opt-in: a stage advertises it only when it genuinely
+/// honours . It is therefore the responsibility of the
+/// implementer to gate its side effect on ; the ETL base
+/// classes deliberately do not implement this interface because the side effect
+/// lives inside the derived stage and the base class has no way to skip it.
+///
+///
+/// Dry run applies to any stage with an external side effect — a loader skips its
+/// write, and an extractor that mutates its source as it reads (for example
+/// committing a message-queue offset, deleting a message on receipt, marking mail
+/// read or moving a processed file) skips that acknowledgement. A pure transformer
+/// has no external side effect and would not normally implement this interface.
+///
+///
+public interface ISupportDryRun
+{
+ ///
+ /// Gets or sets a value indicating whether the stage runs in dry-run mode.
+ ///
+ ///
+ /// When , the stage runs the full pipeline but skips the
+ /// external side effect that mutates the destination or source. Defaults to
+ /// .
+ ///
+ bool IsDryRun { get; set; }
+}
diff --git a/src/Wolfgang.Etl.Abstractions/PublicAPI.Unshipped.txt b/src/Wolfgang.Etl.Abstractions/PublicAPI.Unshipped.txt
index 7dc5c581..31b06ed9 100644
--- a/src/Wolfgang.Etl.Abstractions/PublicAPI.Unshipped.txt
+++ b/src/Wolfgang.Etl.Abstractions/PublicAPI.Unshipped.txt
@@ -1 +1,4 @@
#nullable enable
+Wolfgang.Etl.Abstractions.ISupportDryRun
+Wolfgang.Etl.Abstractions.ISupportDryRun.IsDryRun.get -> bool
+Wolfgang.Etl.Abstractions.ISupportDryRun.IsDryRun.set -> void
diff --git a/src/Wolfgang.Etl.Abstractions/Wolfgang.Etl.Abstractions.csproj b/src/Wolfgang.Etl.Abstractions/Wolfgang.Etl.Abstractions.csproj
index 0ed4633d..5ced7c1e 100644
--- a/src/Wolfgang.Etl.Abstractions/Wolfgang.Etl.Abstractions.csproj
+++ b/src/Wolfgang.Etl.Abstractions/Wolfgang.Etl.Abstractions.csproj
@@ -2,7 +2,7 @@
net462;net472;net48;net481;netstandard2.0;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0
latest
- 0.14.1
+ 0.15.0