Skip to content

feat: Add plugin support to Client SDK#229

Merged
abelonogov-ld merged 12 commits intomainfrom
andrey/clientsdk-plugins-and-hooks
Mar 3, 2026
Merged

feat: Add plugin support to Client SDK#229
abelonogov-ld merged 12 commits intomainfrom
andrey/clientsdk-plugins-and-hooks

Conversation

@abelonogov-ld
Copy link
Contributor

@abelonogov-ld abelonogov-ld commented Feb 25, 2026

Summary

This PR adds plugin architecture and hook-based flag evaluation lifecycle support to the LaunchDarkly Client SDK for .NET, ported from the Server SDK implementation.

Plugin System

  • Introduced a Plugin base class (Plugins/Plugin.cs) that provides a GetHooks() method, allowing plugins to register hooks with the SDK
  • Added PluginConfiguration and PluginConfigurationBuilder to configure plugins via ConfigurationBuilder.Plugins()
  • Updated LdClient to initialize plugins during startup, collect their hooks, and dispose of them properly during shutdown
  • Refactored LdClient cleanup logic to ensure proper disposal order of resources

Hook Framework

  • Hook base class with virtual BeforeEvaluation, AfterEvaluation, BeforeIdentify, and AfterIdentify stage methods, plus IDisposable support
  • EvaluationSeriesContext - captures flag key, context, default value, method, and environment ID for evaluation hooks
  • IdentifySeriesContext / IdentifySeriesResult - context and result types for identify hooks
  • Method constants mapping to SDK variation methods (e.g. LdClient.BoolVariation)
  • SeriesDataBuilder - fluent API for building immutable series data passed between hook stages

Hook Execution Engine

  • Executor / NoopExecutor implementing IHookExecutor - manages hook lifecycle with proper forward/reverse ordering
  • BeforeEvaluation / AfterEvaluation stage executors with error isolation - exceptions in one hook do not prevent other hooks from executing
  • Integrated hook execution into LdClient.EvaluateInternal(), wrapping flag evaluations with BeforeEvaluation and AfterEvaluation calls
  • Added GetMethodName<T>() to resolve the correct Method constant based on variation type and detail mode
  • Fixed prerequisite evaluation to use EvaluateInternal directly, avoiding recursive hook invocations
image

Test Plan

  • EvaluationSeriesTest - hooks execute in LIFO order (forward before, reverse after)
  • EvaluationSeriesTest - exceptions in hook stages don't prevent other hooks from running
  • EvaluationSeriesTest - stage failures log expected error messages with flag key and hook name
  • EvaluationSeriesTest - disposing executor disposes all registered hooks
  • PluginConfigurationBuilderTest - plugin configuration builder tests pass
  • LdClientPluginTests - end-to-end plugin/hook integration tests pass
  • All 7 evaluation series tests pass (dotnet test --filter EvaluationSeriesTest)

Note

Medium Risk
Touches core LdClient evaluation flow by wrapping all Variation* calls with hook execution and adds plugin registration at startup, so regressions could affect flag evaluation behavior/performance despite safeguards like noop execution when unconfigured.

Overview
Adds client-side plugin support by introducing PluginConfigurationBuilder/PluginConfiguration and wiring a new Plugins(...) option through Components, ConfigurationBuilder, and Configuration.

Integrates plugin lifecycle into LdClient initialization: builds plugin config, creates EnvironmentMetadata, collects plugin-provided Hooks, instantiates a hook Executor (or NoopExecutor when none), then registers plugins; hook execution is disposed with the client and logs under a new LogNames.HooksSubLog.

Introduces a hook API and execution pipeline for evaluations (Hook, EvaluationSeriesContext, stage executors) and wraps VariationInternal so hooks run before/after evaluations; prerequisite evaluations are refactored to call EvaluateInternal directly to avoid triggering hooks. Adds unit tests covering hook ordering/error isolation/disposal and plugin builder/registration behavior.

Written by Cursor Bugbot for commit e5d9dc0. This will update automatically on new commits. Configure here.

Example of API

var ldConfig = Configuration.Builder(mobileKey, LaunchDarkly.Sdk.Client.ConfigurationBuilder.AutoEnvAttributes.Enabled)
        .Plugins(new PluginConfigurationBuilder()
        	.Add(ObservabilityPlugin.Builder(new ObservabilityOptions(
        		isEnabled: true,
        		serviceName: "maui-sample-app",
				otlpEndpoint: "https://otel.observability.ld-stg.launchdarkly.com:4318/",
				backendUrl: "https://pub.observability.ld-stg.launchdarkly.com/"
        	)).Build())
        	.Add(SessionReplayPlugin.Builder(new SessionReplayOptions(
        		isEnabled: true,
        		privacy: new SessionReplayOptions.PrivacyOptions(
        			maskTextInputs: true,
        			maskWebViews: false,
        			maskLabels: false
        		)
        	)).Build())
        ).Build();
        
        

@abelonogov-ld abelonogov-ld requested a review from a team as a code owner February 25, 2026 19:38
@abelonogov-ld abelonogov-ld changed the title feat: Plugin support for CliendSDK feat: Add plugin support to Client SDK Feb 25, 2026
@tanderson-ld tanderson-ld self-requested a review February 26, 2026 17:10
@abelonogov-ld abelonogov-ld changed the title feat: Add plugin support to Client SDK WIP feat: Add plugin support to Client SDK Feb 26, 2026
@abelonogov-ld abelonogov-ld changed the title WIP feat: Add plugin support to Client SDK feat: Add plugin support to Client SDK Feb 26, 2026
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

@abelonogov-ld abelonogov-ld merged commit ce5cdd9 into main Mar 3, 2026
14 checks passed
@abelonogov-ld abelonogov-ld deleted the andrey/clientsdk-plugins-and-hooks branch March 3, 2026 19:26
abelonogov-ld pushed a commit that referenced this pull request Mar 3, 2026
🤖 I have created a release *beep* *boop*
---


##
[5.6.0](LaunchDarkly.ClientSdk-v5.5.4...LaunchDarkly.ClientSdk-v5.6.0)
(2026-03-03)


### Features

* Add plugin support to Client SDK
([#229](#229))
([ce5cdd9](ce5cdd9))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> <sup>[Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) is
generating a summary for commit
2c47172. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
evgenygunko pushed a commit to evgenygunko/CopyWordsDA that referenced this pull request Mar 4, 2026
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [LaunchDarkly.ClientSdk](https://github.com/launchdarkly/dotnet-core) | nuget | minor | `5.5.4` -> `5.6.0` |

---

### Release Notes

<details>
<summary>launchdarkly/dotnet-core (LaunchDarkly.ClientSdk)</summary>

### [`v5.6.0`](https://github.com/launchdarkly/dotnet-core/releases/tag/LaunchDarkly.ClientSdk-v5.6.0): LaunchDarkly.ClientSdk: v5.6.0

[Compare Source](launchdarkly/dotnet-core@LaunchDarkly.ClientSdk-v5.5.4...LaunchDarkly.ClientSdk-v5.6.0)

##### Features

-   Add plugin support to Client SDK ([#&#8203;229](launchdarkly/dotnet-core#229)) ([ce5cdd9](launchdarkly/dotnet-core@ce5cdd9))

***

This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).

<!-- CURSOR_SUMMARY -->

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or PR is renamed to start with "rebase!".

👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/renovate/discussions) if that's undesired.

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
abelonogov-ld added a commit that referenced this pull request Mar 5, 2026
* main:
  chore(main): release LaunchDarkly.ServerSdk.Ai 0.9.3 (#233)
  fix: Make defaultValue optional with a disabled default (#232)
  chore(main): release LaunchDarkly.ClientSdk 5.6.0 (#231)
  feat: Add plugin support to Client SDK (#229)

# Conflicts:
#	pkgs/sdk/client/src/Internal/Hooks/Executor/Executor.cs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants