diff --git a/Pr/PrAlgorithms/src/PrFilter.cpp b/Pr/PrAlgorithms/src/PrFilter.cpp
index f1c0b89277620f16dab360cffe64d71ad4c0c7d3..7cfe144372bde80ddd2db94ce9a10378c844779e 100644
--- a/Pr/PrAlgorithms/src/PrFilter.cpp
+++ b/Pr/PrAlgorithms/src/PrFilter.cpp
@@ -20,12 +20,23 @@
 #include "boost/algorithm/string/join.hpp"
 #include <vector>
 
+#include "SOAContainer/SOAContainer.h"
+#include "SOAExtensions/ZipContainer.h"
+#include "SOAExtensions/ZipSelection.h"
+#include "Event/TrackSkin.h"
+
 namespace {
   // Just shorthand for below
   template <typename T>
   using FilterTransform =
       Gaudi::Functional::MultiTransformerFilter<std::tuple<Pr::Selection<T>>( Pr::Selection<T> const& )>;
 
+  // Just shorthand for below
+  using SOATracks                 = Zipping::ZipContainer<decltype(
+    SOA::make_soaview<LHCb::Event::v2::TrackSkin>( std::declval<const std::vector<LHCb::Event::v2::Track>&>() ) )>;
+  using SOAFilterTransform = Gaudi::Functional::MultiTransformerFilter<std::tuple<Zipping::ExportedSelection<>>(
+      SOATracks const&, Zipping::ExportedSelection<> const& )>;
+
   // Helper class used to collect the type names we need to construct LoKi functors for various objects
   template <typename T>
   struct TypeHelper {};
@@ -49,6 +60,16 @@ namespace {
     constexpr static auto DefaultFunctorCode = "~TrALL";
     constexpr static auto FunctorFactoryName = "LoKi::Hybrid::Pr::TrackFunctorFactory/PrTrackFunctorFactory:PUBLIC";
   };
+  // LoKi type names for Filter<v2::Track>
+  template <>
+  struct TypeHelper<SOATracks> {
+    using Predicate                          = LoKi::Pr::TrackTypes::TrCut;
+    using FunctorFactory                     = LoKi::Pr::ITrackFunctorFactory;
+    using BooleanConstant                    = LoKi::BasicFunctors<LoKi::Pr::TrackType const*>::BooleanConstant;
+    constexpr static auto DefaultFunctorCode = "~TrALL";
+    constexpr static auto FunctorFactoryName = "LoKi::Hybrid::Pr::TrackFunctorFactory/PrTrackFunctorFactory:PUBLIC";
+  };
+
 } // namespace
 
 namespace Pr {
@@ -138,4 +159,94 @@ namespace Pr {
 
   DECLARE_COMPONENT_WITH_ID( Filter<LHCb::Event::v1::Track>, "PrFilter__Track_v1" )
   DECLARE_COMPONENT_WITH_ID( Filter<LHCb::Event::v2::Track>, "PrFilter__Track_v2" )
+
+
+  /** @class SOAFilter PrFilter.cpp
+   *
+   *  SOAFilter<T> applies a selection to an input Selection<T> and returns a new Selection<T> object.
+   *
+   *  @tparam T The selected object type (e.g. Track, Particle, ...). By contruction this is not copied, as the
+   *            input/output type Selection<T> is just a view of some other underlying storage.
+   */
+
+  class SOAFilter final : public SOAFilterTransform {
+  public:
+    using SOAFilterTransform::debug;
+    using SOAFilterTransform::msgLevel;
+
+    SOAFilter( const std::string& name, ISvcLocator* pSvcLocator )
+        : SOAFilterTransform( name, pSvcLocator, {KeyValue{"InputTracks", ""}, KeyValue{"InputSelection", ""}}, {"OutputSelection", ""} ) {}
+
+    std::tuple<bool, Zipping::ExportedSelection<>> operator()( SOATracks const& in,
+                                                               Zipping::ExportedSelection<> const& sel ) const override {
+      auto selected_input = Zipping::SelectionView<decltype( in )>{&in, sel};
+      auto buffer            = m_cutEff.buffer();
+      auto pred_with_counter = [this, &buffer]( auto const& x ) {
+        auto dec = this->m_pred( &(x.track()) );
+        buffer += dec; // record selection efficiency
+        return dec;
+      };
+
+      // Make a new Selection by applying an extra cut to 'in'
+      auto filtered = selected_input.select( pred_with_counter );
+
+      // For use in the control flow: did we select anything?
+      auto filter_pass = !filtered.empty();
+
+      return {filter_pass, std::move( filtered )};
+    }
+
+    StatusCode initialize() override {
+      auto sc = SOAFilterTransform::initialize();
+      decode();
+      return sc;
+    }
+
+  private:
+    // Counter for recording cut retention statistics
+    mutable Gaudi::Accumulators::BinomialCounter<> m_cutEff{this, "Cut selection efficiency"};
+
+    // Boilerplate to load a LoKi filter predicate
+    // These two flags are used to ensure that the values of both 'Code' and 'Preambulo' have been loaded before we try
+    // to decode the functor
+    bool m_code_updated{false};
+    bool m_preambulo_updated{false};
+
+    // This is the functor cut string
+    Gaudi::Property<std::string> m_code{this, "Code", TypeHelper<SOATracks>::DefaultFunctorCode, [this]( auto& ) {
+                                          this->m_code_updated = true;
+                                          if ( this->FSMState() < Gaudi::StateMachine::INITIALIZED ) return;
+                                          if ( !this->m_preambulo_updated ) return;
+                                          this->decode();
+                                        }};
+
+    // Finally we can set a list of preambulo statements
+    Gaudi::Property<std::vector<std::string>> m_preambulo{this, "Preambulo", {}, [this]( auto& ) {
+                                                            this->m_preambulo_updated = true;
+                                                            if ( this->FSMState() < Gaudi::StateMachine::INITIALIZED )
+                                                              return;
+                                                            if ( !this->m_code_updated ) return;
+                                                            this->decode();
+                                                          }};
+
+    // Use the TypeHelper template defined above to choose the right types/defaults based on the template parameter T
+    ToolHandle<typename TypeHelper<SOATracks>::FunctorFactory> m_factory{this, "Factory", TypeHelper<SOATracks>::FunctorFactoryName};
+    typename TypeHelper<SOATracks>::Predicate                  m_pred = typename TypeHelper<SOATracks>::BooleanConstant( false );
+
+    void decode() {
+      if ( msgLevel( MSG::DEBUG ) ) debug() << "decoding " << m_code.value() << endmsg;
+      m_factory.retrieve().ignore();
+      StatusCode sc = m_factory->get( m_code.value(), m_pred, boost::algorithm::join( m_preambulo.value(), "\n" ) );
+      if ( sc.isFailure() ) {
+        throw GaudiException{"Failure to decode: " + m_code.value(), "Pr::Filter<T>", StatusCode::FAILURE};
+      }
+      m_factory.release().ignore();
+      m_code_updated      = false;
+      m_preambulo_updated = false;
+      if ( msgLevel( MSG::DEBUG ) ) debug() << "decoded " << m_code.value() << endmsg;
+    }
+  };
+  //using SOATrackFilter = SOAFilter<SOATracks>;
+  DECLARE_COMPONENT_WITH_ID( SOAFilter, "PrFilter__SOATracks" )
+
 } // namespace Pr