<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Scott Tippett</title>
    <description>The latest articles on DEV Community by Scott Tippett (@chaotic_neutral_dev).</description>
    <link>https://dev.to/chaotic_neutral_dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3995414%2F4be6ca29-b507-462e-ab2d-c072e82b87f2.jpg</url>
      <title>DEV Community: Scott Tippett</title>
      <link>https://dev.to/chaotic_neutral_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chaotic_neutral_dev"/>
    <language>en</language>
    <item>
      <title>Already using LaunchDarkly or Flagsmith? Here's how to try FtrIO on a single flag</title>
      <dc:creator>Scott Tippett</dc:creator>
      <pubDate>Sun, 21 Jun 2026 18:44:13 +0000</pubDate>
      <link>https://dev.to/chaotic_neutral_dev/already-using-launchdarkly-or-flagsmith-heres-how-to-try-ftrio-on-a-single-flag-54no</link>
      <guid>https://dev.to/chaotic_neutral_dev/already-using-launchdarkly-or-flagsmith-heres-how-to-try-ftrio-on-a-single-flag-54no</guid>
      <description>&lt;h1&gt;
  
  
  Thinking about trying FtrIO? The new CLI makes it easy to start with just one feature flag
&lt;/h1&gt;

&lt;p&gt;If you've been curious about FtrIO (the .NET feature toggle library that replaces &lt;code&gt;if (featureFlags.IsEnabled(...))&lt;/code&gt; with a &lt;code&gt;[Toggle]&lt;/code&gt; attribute woven directly into your compiled IL) but weren't sure where to start, the experimental release of FtrIO.onetwo just made that first step a lot smaller.&lt;/p&gt;

&lt;p&gt;You don't need to commit to a full migration. You don't need to rip out your existing flag library. You just need one method and 20 minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  What FtrIO.onetwo does
&lt;/h2&gt;

&lt;p&gt;FtrIO.onetwo is a .NET CLI audit tool. Its default mode scans your source tree, finds every FtrIO toggle reference, and tells you exactly what's live right now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; FtrIO.onetwo &lt;span class="nt"&gt;--version&lt;/span&gt; 1.1.1-experimental
ftrio.onetwo &lt;span class="nt"&gt;--source&lt;/span&gt; C:&lt;span class="se"&gt;\P&lt;/span&gt;rojects&lt;span class="se"&gt;\M&lt;/span&gt;yApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But in this experimental release it does two new things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ftrio.onetwo import&lt;/code&gt;&lt;/strong&gt;: pulls your current flag state from LaunchDarkly, Flagsmith, flagd, environment variables, or an HTTP endpoint directly into &lt;code&gt;appsettings.json&lt;/code&gt;. Your existing flag library keeps working unchanged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ftrio.onetwo migrate&lt;/code&gt;&lt;/strong&gt;: scans your &lt;code&gt;.cs&lt;/code&gt; files for LaunchDarkly or Flagsmith SDK call patterns using Roslyn, cross-references them against your live flag state, and generates a report showing exactly what each flag would look like in FtrIO and how to migrate it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The "try it on one flag" workflow
&lt;/h2&gt;

&lt;p&gt;The migrate report categorises every flag it finds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Ready to migrate&lt;/strong&gt;: boolean flag, no targeting rules, straightforward &lt;code&gt;[Toggle]&lt;/code&gt; replacement&lt;/li&gt;
&lt;li&gt;⚠️ &lt;strong&gt;Needs review&lt;/strong&gt;: targeting rules, number flags, needs a decision&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Cannot migrate&lt;/strong&gt;: JSON flags, recommend moving to &lt;code&gt;IConfiguration&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For every ready flag it shows the suggested refactor. Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;flow&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;NewCheckoutFlow&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;42&lt;/span&gt;

&lt;span class="n"&gt;Current&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BoolVariation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"new-checkout-flow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;ValidateCart&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;ApplyDiscounts&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;ProcessPayment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;Suggested&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;Extract&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="n"&gt;named&lt;/span&gt; &lt;span class="n"&gt;NewCheckoutFlow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;then&lt;/span&gt; &lt;span class="n"&gt;decorate&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Toggle&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt; &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;inner&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt; &lt;span class="n"&gt;remain&lt;/span&gt; &lt;span class="n"&gt;unchanged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Toggle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;NewCheckoutFlow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;ValidateCart&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;ApplyDiscounts&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;ProcessPayment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;appsettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;"NewCheckoutFlow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pick one of those ready flags. Do the refactor. Run the app. That's it: you've got FtrIO running in your codebase alongside your existing flag library, on exactly one feature, with everything else completely unchanged.&lt;/p&gt;

&lt;p&gt;If you like how it feels, pick another. If you don't, remove the &lt;code&gt;[Toggle]&lt;/code&gt; attribute and you're back to where you started.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the parent method suggestion matters
&lt;/h2&gt;

&lt;p&gt;The report nudges toward a pattern that's worth calling out explicitly: &lt;strong&gt;one named parent method per toggled feature&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BoolVariation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"send-welcome-email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;ValidateEmailAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;BuildEmailTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;SendViaSmtp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;LogEmailSent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FtrIO works best like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Toggle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;SendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;ValidateEmailAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;BuildEmailTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;SendViaSmtp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;LogEmailSent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The feature has a single, named, discoverable entry point. The toggle is a gate on the feature, not scattered through its implementation. The inner methods have no idea a toggle exists. This is better code regardless of FtrIO; the migration report teaches the pattern rather than just doing a mechanical find-and-replace.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running the migration report
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# With live flag state from LaunchDarkly&lt;/span&gt;
ftrio.onetwo migrate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from&lt;/span&gt; launchdarkly &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--api-key&lt;/span&gt; sdk-xxx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--project&lt;/span&gt; my-project &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env&lt;/span&gt; production &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source&lt;/span&gt; C:&lt;span class="se"&gt;\P&lt;/span&gt;rojects&lt;span class="se"&gt;\M&lt;/span&gt;yApp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--markdown&lt;/span&gt; plan.md

&lt;span class="c"&gt;# Code scan only, no API key needed&lt;/span&gt;
ftrio.onetwo migrate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from&lt;/span&gt; launchdarkly &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source&lt;/span&gt; C:&lt;span class="se"&gt;\P&lt;/span&gt;rojects&lt;span class="se"&gt;\M&lt;/span&gt;yApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--api-key&lt;/code&gt; is optional; without it the report still finds all your call sites and shows the suggested refactors, just with placeholder values in the &lt;code&gt;appsettings.json&lt;/code&gt; snippets instead of live flag states.&lt;/p&gt;




&lt;h2&gt;
  
  
  This is experimental
&lt;/h2&gt;

&lt;p&gt;Both &lt;code&gt;import&lt;/code&gt; and &lt;code&gt;migrate&lt;/code&gt; are in an experimental branch. To try it you need to explicitly request the experimental version — the default install will give you the stable release which doesn't include these commands yet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; FtrIO.onetwo &lt;span class="nt"&gt;--version&lt;/span&gt; 1.1.1-experimental
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Functional but not yet on a stable release. Feedback is genuinely welcome, especially on the migration report format and the flag mapping logic.&lt;/p&gt;

&lt;p&gt;The full FtrIO ecosystem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FtrIO&lt;/strong&gt;: the core library: &lt;code&gt;dotnet add package FtrIO&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FtrIO.Toaster&lt;/strong&gt;: self-hosted Docker UI for managing toggles live&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FtrIO.onetwo&lt;/strong&gt;: this CLI: &lt;code&gt;dotnet tool install -g FtrIO.onetwo&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs&lt;/strong&gt;: &lt;a href="https://ftronoff.github.io/FtrIO" rel="noopener noreferrer"&gt;ftronoff.github.io/FtrIO&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/FtrOnOff" rel="noopener noreferrer"&gt;github.com/FtrOnOff&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try it on a flag or two I'd love to hear how it goes; drop a comment or open an issue.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>featureflags</category>
      <category>csharp</category>
      <category>migration</category>
    </item>
    <item>
      <title>I got tired of writing ifs everywhere — so I built an ecosystem</title>
      <dc:creator>Scott Tippett</dc:creator>
      <pubDate>Sun, 21 Jun 2026 14:46:02 +0000</pubDate>
      <link>https://dev.to/chaotic_neutral_dev/i-got-tired-of-writing-ifs-everywhere-so-i-built-an-ecosystem-1koi</link>
      <guid>https://dev.to/chaotic_neutral_dev/i-got-tired-of-writing-ifs-everywhere-so-i-built-an-ecosystem-1koi</guid>
      <description>&lt;h1&gt;
  
  
  I got tired of writing &lt;code&gt;if (featureFlags.IsEnabled(...))&lt;/code&gt; everywhere — so I built an ecosystem
&lt;/h1&gt;

&lt;p&gt;I've been writing C# for over a decade and feature flags have always annoyed me in the same way.&lt;/p&gt;

&lt;p&gt;Not the concept — the concept is great. The ability to ship code dark, roll out to a percentage of traffic, or flip a switch without a redeploy is genuinely powerful. What annoyed me was the noise. Every time I added a feature flag I'd end up with this scattered through the codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;featureFlags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SendWelcomeEmail"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;SendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiply that across a real codebase and you've got &lt;code&gt;if&lt;/code&gt; statements everywhere, magic strings that drift out of sync with config, and no easy way to know what's actually live at any given moment without opening files manually.&lt;/p&gt;

&lt;p&gt;So I built something about it. Then I built something else. Then I hadn't slept and had consumed several root beers and I had an ecosystem.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem with feature flags in .NET
&lt;/h2&gt;

&lt;p&gt;Most feature flag libraries for .NET — including Microsoft's own &lt;code&gt;Microsoft.FeatureManagement&lt;/code&gt; — solve the storage and evaluation problem well. But they don't solve the noise problem. You still have to wrap every call site, inject a service everywhere, and remember to clean up the &lt;code&gt;if&lt;/code&gt; statements when a flag is retired.&lt;/p&gt;

&lt;p&gt;LaunchDarkly and Flagsmith go further with SaaS dashboards and targeting rules, but now you've got a vendor dependency, a network call on your hot path, and a subscription fee.&lt;/p&gt;

&lt;p&gt;What I wanted was something that felt like a natural part of the language — where toggling a feature on or off was as simple as adding or removing an attribute, and the checking happened automatically without touching every call site.&lt;/p&gt;




&lt;h2&gt;
  
  
  FtrIO — the core library
&lt;/h2&gt;

&lt;p&gt;The idea behind FtrIO is simple: decorate a method with &lt;code&gt;[Toggle]&lt;/code&gt; and it becomes config-gated by its own name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Toggle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;SendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// send the email&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;SendWelcomeEmail&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// only runs if "SendWelcomeEmail": true in appsettings.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No &lt;code&gt;if&lt;/code&gt;, no injected service, no wrapper. The method calls exactly like a normal method. If the toggle is off, it just doesn't run.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it actually works
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;[Toggle]&lt;/code&gt; is an &lt;a href="https://github.com/pamidur/aspect-injector" rel="noopener noreferrer"&gt;AspectInjector&lt;/a&gt; aspect. At compile time, AspectInjector weaves the gating check directly into the method's IL. The gate is applied at the IL level, so it works regardless of how the method is called — direct call, reflection, delegate, anything.&lt;/p&gt;

&lt;p&gt;Config lives in &lt;code&gt;appsettings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Toggles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SendWelcomeEmail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"NewCheckoutFlow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Compile-time validation
&lt;/h3&gt;

&lt;p&gt;The thing I'm most proud of: FtrIO ships a Roslyn analyzer that catches missing config entries &lt;strong&gt;at build time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you decorate a method with &lt;code&gt;[Toggle]&lt;/code&gt; but forget to add the matching entry to &lt;code&gt;appsettings.json&lt;/code&gt;, the build fails with &lt;code&gt;FTRIO001&lt;/code&gt; — not a silent runtime failure, not a logged warning, a proper compiler error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error FTRIO001: 'NewCheckoutFlow' is decorated with [Toggle] but has no entry in Toggles
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No other feature flag library I know of does this, at any price.&lt;/p&gt;

&lt;h3&gt;
  
  
  Async support
&lt;/h3&gt;

&lt;p&gt;For async methods, &lt;code&gt;[ToggleAsync]&lt;/code&gt; returns &lt;code&gt;Task.CompletedTask&lt;/code&gt; or &lt;code&gt;Task.FromResult(default)&lt;/code&gt; when the toggle is off — always safely awaitable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ToggleAsync&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;SendWelcomeEmailAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;emailClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;SendWelcomeEmailAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// safely awaitable whether the toggle is on or off&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Beyond true/false — strategy-based decisions
&lt;/h3&gt;

&lt;p&gt;FtrIO isn't limited to boolean toggles. &lt;code&gt;StrategyToggleParser&lt;/code&gt; routes raw config values through a chain of strategies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Toggles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"NewCheckout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"20%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PaymentV2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blue"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;ToggleParserProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StrategyToggleParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PercentageRolloutStrategy&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;      &lt;span class="c1"&gt;// "20%" runs ~1 in 5 calls&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BlueGreenStrategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"blue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"blue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"green"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// runs only on the blue slot&lt;/span&gt;
&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Percentage rollouts, blue-green deployment switching, or any custom logic you implement via &lt;code&gt;IToggleDecisionStrategy&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic providers
&lt;/h3&gt;

&lt;p&gt;For toggle state driven by external sources — HTTP endpoints, Azure App Config, environment variables — FtrIO uses a provider pipeline that writes into &lt;code&gt;appsettings.json&lt;/code&gt; in the background. The read path always comes from the file.&lt;/p&gt;

&lt;p&gt;This means if a remote provider goes offline, the last known state serves automatically from disk. No fallback code, no circuit breaker, no stale-cache TTL to configure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package FtrIO.Providers.Http
dotnet add package FtrIO.Providers.AzureAppConfig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package FtrIO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Targets .NET 6, 8, and 10.&lt;/p&gt;




&lt;h2&gt;
  
  
  FtrIO.Toaster — the management UI
&lt;/h2&gt;

&lt;p&gt;Once FtrIO was in use, I had a new problem: flipping toggles meant editing &lt;code&gt;appsettings.json&lt;/code&gt; by hand. Across multiple environments. That got old quickly.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;FtrIO.Toaster&lt;/strong&gt; — a lightweight Dockerized web UI for managing toggles live.&lt;/p&gt;

&lt;p&gt;The name has two origins: toast is binary (toasted or not, much like a feature toggle), and it's a nod to the Dungeon Master who runs our D&amp;amp;D sessions. Every good campaign needs someone deciding what's enabled and what isn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it does
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Boolean&lt;/strong&gt; on/off toggles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Percentage rollout&lt;/strong&gt; — slider and number input in sync&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blue/green&lt;/strong&gt; deployment switching&lt;/li&gt;
&lt;li&gt;Change toggle type at any time&lt;/li&gt;
&lt;li&gt;Add and delete toggles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-environment support&lt;/strong&gt; — manage any number of environments from a single UI instance via a dropdown&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit log&lt;/strong&gt; — every change recorded with timestamp, environment, toggle key, old value, new value, and the acting user&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Basic Auth&lt;/strong&gt; built in, with an OAuth2 Proxy sidecar option for SSO (Google, GitHub, Microsoft, GitLab, OIDC)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How it fits with FtrIO core
&lt;/h3&gt;

&lt;p&gt;Toaster implements FtrIO's own &lt;code&gt;ToggleProviderBuffer&lt;/code&gt; internally. Changes are staged in memory and flushed atomically to &lt;code&gt;appsettings.json&lt;/code&gt; on the configured interval — exactly as a native FtrIO provider would. Your running app picks up changes via &lt;code&gt;ReloadOnChange&lt;/code&gt; with no restart.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting started
&lt;/h3&gt;

&lt;p&gt;No clone required — pull from Docker Hub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;toaster&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;thescottbot/ftrio:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8000:8000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;APP_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;My&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Application"&lt;/span&gt;
      &lt;span class="na"&gt;APPSETTINGS_PATH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/data/appsettings.json&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bind&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/path/to/your/appsettings.json&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/data/appsettings.json&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:8000&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  FtrIO.onetwo — the audit CLI
&lt;/h2&gt;

&lt;p&gt;The third problem: as the codebase grew, there was no easy way to know what was actually live right now without opening &lt;code&gt;appsettings.json&lt;/code&gt; and cross-referencing it with the source code manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FtrIO.onetwo&lt;/strong&gt; is a .NET global tool that does that cross-referencing for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; FtrIO.onetwo
ftrio.onetwo &lt;span class="nt"&gt;--source&lt;/span&gt; C:&lt;span class="se"&gt;\P&lt;/span&gt;rojects&lt;span class="se"&gt;\M&lt;/span&gt;yApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It walks your source tree, finds every &lt;code&gt;[Toggle]&lt;/code&gt;, &lt;code&gt;[ToggleAsync]&lt;/code&gt;, and manual call, and outputs a table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;── Staging C:\Projects\MyApp\appsettings.Staging.json
╭──────────────────┬──────────────────┬────────────┬─────────┬───────────────────┬──────╮
│ Toggle Key       │ Method           │ Source     │  State  │ File              │ Line │
├──────────────────┼──────────────────┼────────────┼─────────┼───────────────────┼──────┤
│ NewCheckoutFlow  │ NewCheckoutFlow  │ [Toggle]   │   50%   │ Services\Order.cs │    9 │
│ PaymentV2        │ PaymentV2        │ [Toggle]   │  BLUE   │ Services\Pay.cs   │    6 │
│ SendWelcomeEmail │ SendWelcomeEmail │ [Toggle]   │   ON    │ Services\Email.cs │   22 │
│ UnknownFeature   │ UnknownFeature   │ ManualCall │ MISSING │ Controllers\Ho... │   42 │
╰──────────────────┴──────────────────┴────────────┴─────────┴───────────────────┴──────╯
4 toggle(s). 1 ON, 0 OFF, 1 PERCENTAGE, 1 BLUE/GREEN, 1 MISSING.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;MISSING&lt;/code&gt; state is the one I find most useful — it catches toggles that exist in code but have no config entry, across every environment, before they cause a silent runtime failure. No other feature flag tooling I know of does this.&lt;/p&gt;

&lt;p&gt;Supports &lt;code&gt;--env Staging&lt;/code&gt; to target a specific environment overlay, and &lt;code&gt;--markdown&lt;/code&gt; to write the output to a markdown file for documentation or CI artefacts.&lt;/p&gt;




&lt;h2&gt;
  
  
  How they fit together
&lt;/h2&gt;

&lt;p&gt;All three tools share &lt;code&gt;appsettings.json&lt;/code&gt; as the single source of truth. No coupling between them — use any combination without changing your call sites:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────┐
│  Your code                                          │
│  [Toggle] public void SendWelcomeEmail() { ... }    │
└───────────────────┬─────────────────────────────────┘
                    │ compile-time weaving
                    ▼
┌─────────────────────────────────────────────────────┐
│  FtrIO core                                         │
│  gates method execution at runtime                  │
└───────────────────┬─────────────────────────────────┘
                    │ reads
                    ▼
┌─────────────────────────────────────────────────────┐
│  appsettings.json  — source of truth                │
└──────────┬──────────────────────────┬───────────────┘
           │ writes live              │ reads &amp;amp; audits
           ▼                          ▼
  FtrIO.Toaster                 FtrIO.onetwo
  (web UI — manage toggles)     (CLI — audit state)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How it compares
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;FtrIO&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;LaunchDarkly&lt;/th&gt;
&lt;th&gt;Microsoft.FeatureManagement&lt;/th&gt;
&lt;th&gt;Flagsmith&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Call-site syntax&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[Toggle]&lt;/code&gt; attribute, zero noise&lt;/td&gt;
&lt;td&gt;SDK call at every site&lt;/td&gt;
&lt;td&gt;&lt;code&gt;if (await _fm.IsEnabledAsync(...))&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SDK call at every site&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Works offline&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ always (file-backed)&lt;/td&gt;
&lt;td&gt;❌ needs SDK fallback config&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌ needs SDK fallback config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compile-time validation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Roslyn analyzer&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Codebase audit / drift detection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ onetwo CLI&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Management UI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Toaster, self-hosted&lt;/td&gt;
&lt;td&gt;✅ SaaS dashboard&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ SaaS dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Percentage rollout&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Self-hosted / no vendor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌ paid SaaS&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (or SaaS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free, OSS&lt;/td&gt;
&lt;td&gt;Paid SaaS&lt;/td&gt;
&lt;td&gt;Free, OSS&lt;/td&gt;
&lt;td&gt;Free tier / paid SaaS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Where to find it
&lt;/h2&gt;

&lt;p&gt;Everything lives under the &lt;a href="https://github.com/FtrOnOff" rel="noopener noreferrer"&gt;FtrOnOff org on GitHub&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FtrIO&lt;/strong&gt; (core library) — &lt;code&gt;dotnet add package FtrIO&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FtrIO.Toaster&lt;/strong&gt; (Docker UI) — &lt;code&gt;docker compose up -d&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FtrIO.onetwo&lt;/strong&gt; (audit CLI) — &lt;code&gt;dotnet tool install -g FtrIO.onetwo&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full docs&lt;/strong&gt; — &lt;a href="https://ftronoff.github.io/FtrIO" rel="noopener noreferrer"&gt;ftronoff.github.io/FtrIO&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All free, all open source, all built on no sleep and root beer. 🍺&lt;/p&gt;

&lt;p&gt;If you try it I'd love to know what you think — drop a comment or open an issue.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>featureflags</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
