# How Cordis Interceptors Modify Plugin Behavior at Runtime

> Learn how Cordis interceptors dynamically modify plugin behavior at runtime. Discover prototype-based configuration chains for flexible service settings overrides without code changes.

- Repository: [Cordiverse/cordis](https://github.com/cordiverse/cordis)
- Tags: how-to-guide
- Published: 2026-09-12

---

**Cordis interceptors alter plugin behavior at runtime by creating prototype-based configuration chains that services traverse to merge settings, allowing dynamic overrides without modifying source code.**

Cordis, the modular plugin framework maintained in the `cordiverse/cordis` repository, provides a sophisticated interceptor mechanism that enables runtime modification of service configurations. This system allows developers to customize plugin behavior—such as logging levels or reflection settings—on a per-context basis without altering the underlying plugin code. Understanding how Cordis interceptors work requires examining the prototype-chain architecture in the core context implementation and the service-level configuration merging logic.

## The Core Mechanism Behind Context.intercept

At the heart of Cordis interceptor functionality lies the `Context.intercept` method defined in [`packages/core/src/context.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/context.ts) (lines 71-77). When invoked as `ctx.intercept(name, config)`, the system creates a new intercept map that prototypes the existing `this[Context.intercept]` object.

### Prototype Chain Architecture

The implementation uses `Object.setPrototypeOf` (or equivalent prototype linkage) to chain configuration objects. When you call `intercept(name, config)`, Cordis:

1. Creates a new object inheriting from the current intercept map
2. Stores the supplied `config` under the specified `name` key
3. Attaches this new map to a fresh `Context` instance via the `extend` method

This produces a *shadow* context where the interceptor chain follows the pattern: `newIntercept → oldIntercept → …`. Services later traverse this chain to aggregate configurations, with each link in the chain representing a layer of potential overrides.

## Runtime Service Resolution

Services that support interception walk the prototype chain at runtime to resolve their final configuration. For each service name, implementations look for matching entries on `ctx[Context.intercept]` and merge intercepted configurations into their default options.

### LoggerService Configuration Merging

The `LoggerService` in [`packages/core/src/logger.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/logger.ts) (lines 215-221) demonstrates this traversal explicitly. The service iterates while the `'logger'` key exists in the intercept map, collecting each configuration object into a `configs` array. After collecting all entries from the prototype chain, the service applies these configurations in order, ensuring that the most recent interceptor (closest to the execution context) takes precedence over earlier values.

### ReflectService Pattern

Similarly, `ReflectService` in [`packages/core/src/reflect.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/reflect.ts) (lines 52-58) follows an identical pattern for the `'reflect'` key. This enables dynamic tweaks to reflection behavior without restarting the application or modifying the service instantiation code. Any custom service can implement equivalent logic by traversing `ctx[Context.intercept]` to detect and apply configuration overrides.

## Hierarchical Context Isolation

The interceptor chain plays a crucial role in plugin isolation within the Cordis loader. In [`packages/loader/src/config/isolate.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/isolate.ts) (lines 88-122), the loader creates fresh intercept objects for child plugin entries and explicitly links them to parent interceptors via `Object.setPrototypeOf`. This wiring ensures that isolated plugins inherit their parent's interceptor configurations while maintaining the ability to add or override their own settings, creating a hierarchical configuration tree that mirrors the plugin dependency structure.

## Practical Cordis Interceptor Implementation

Developers interact with interceptors through the `Context` API, applying configurations that affect all subsequent service operations within that context scope.

### Basic Runtime Interception

To modify a logger's name for a specific execution context:

```typescript
import { Context } from 'cordis'

const ctx = new Context()
ctx.intercept('logger', { name: 'my-plugin' })

export default function myPlugin(ctx: Context) {
  // The logger now uses the intercepted name 'my-plugin'
  ctx.logger.info('plugin started')
}

```

### Layered Contexts

Child contexts inherit parent interceptors but can override them:

```typescript
const parent = new Context()
parent.intercept('logger', { name: 'parent' })

const child = parent.extend()
child.intercept('logger', { name: 'child' })

// Logging behavior demonstrates precedence
child.logger.info('from child')   // → [child] from child
parent.logger.info('from parent') // → [parent] from parent

```

### Loader-Level Configuration

Interceptors can be applied globally to all loaded plugins:

```typescript
import { Loader } from '@cordis/loader'

const loader = new Loader({
  intercept: { logger: { level: 'debug' } }
})
await loader.load('my-plugin')

```

## Summary

- Cordis interceptors store hierarchical configurations using prototype chains initiated by `Context.intercept` in [`packages/core/src/context.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/context.ts) (lines 71-77).
- Services like `LoggerService` traverse these chains at runtime to aggregate settings, with later interceptors taking precedence over earlier ones.
- The loader leverages `Object.setPrototypeOf` in [`packages/loader/src/config/isolate.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/isolate.ts) (lines 88-122) to ensure isolated plugins inherit parent interceptors while maintaining override capabilities.
- This architecture enables hot-reloading, test isolation, and feature toggles without requiring modifications to plugin source code.

## Frequently Asked Questions

### What is the primary purpose of Cordis interceptors?

Cordis interceptors allow developers to modify service configurations (such as logger names or reflection settings) at runtime without changing plugin source code. They create a prototype chain of configuration objects that services traverse to determine final behavior.

### How do Cordis interceptors handle configuration precedence?

When multiple interceptors target the same service, Cordis merges configurations by walking the prototype chain from the context outward. Configurations applied closer to the execution context override earlier values, as verified in [`packages/core/tests/logger.spec.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/tests/logger.spec.ts) (lines 91-103).

### Can interceptors be used across parent and child contexts?

Yes. When extending a context via `ctx.extend()`, child contexts inherit the parent's interceptor chain through prototype linkage. The loader's isolation logic in [`packages/loader/src/config/isolate.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/isolate.ts) (lines 88-122) explicitly wires child interceptors to their parents using `Object.setPrototypeOf`, ensuring hierarchical configuration inheritance.

### Which Cordis services support interception out of the box?

The core framework provides interception support for `LoggerService` ([`packages/core/src/logger.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/logger.ts)) and `ReflectService` ([`packages/core/src/reflect.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/reflect.ts)). Custom services can implement similar logic by traversing `ctx[Context.intercept]` to check for configuration overrides.