# Creating and Using Context Extensions in Cordis: The Complete Guide

> Master Cordis context extensions. Learn to create and use these powerful tools for isolated state management and efficient service resolution. Your complete guide awaits.

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

---

**Cordis context extensions create immutable, shallow copies of the runtime Context that isolate state changes while maintaining a shadow chain back to the parent for service resolution.**

Cordis relies on an immutable **Context** object to represent the runtime environment of plugins and services. According to the cordiverse/cordis source code, you never mutate an existing Context instance directly; instead, you use the `extend()` method to create isolated snapshots that inherit the parent's state. This pattern enables safe plugin isolation and composable service architecture without side effects.

## How Context Extensions Work in Cordis

At the core of Cordis is the principle that **contexts are immutable**. Rather than modifying a context in place, the framework creates a *shadow*—a shallow copy that inherits from the original via JavaScript’s prototype chain.

### The `extend` Method Implementation

The extension mechanism lives in [[`packages/core/src/context.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/context.ts)](https://github.com/cordiverse/cordis/blob/main/packages/core/src/context.ts). The `extend` method signature is:

```typescript
extend(meta = {}): this

```

The implementation creates a traceable shallow copy of the current context while preserving the shadow chain:

```typescript
extend(meta = {}): this {
  const shadow = Reflect.getOwnPropertyDescriptor(this, symbols.shadow)?.value
  const self = Object.create(getTraceable(this, this))
  for (const prop of Reflect.ownKeys(meta)) {
    Object.defineProperty(self, prop, Reflect.getOwnPropertyDescriptor(meta, prop)!)
  }
  if (!shadow) return self
  return Object.assign(Object.create(self), { [symbols.shadow]: shadow })
}

```

This implementation performs three critical operations:

- **Creates a traceable prototype chain** via `Object.create(getTraceable(this, this))`, ensuring the new context inherits all existing services and properties from the parent.
- **Defines new properties** from the `meta` object directly onto the new context instance using `Reflect.getOwnPropertyDescriptor`, allowing you to add or override services and configuration values.
- **Preserves the shadow reference** by storing the original context in `symbols.shadow`, enabling Cordis to resolve services up the hierarchy while tracking the context's origin.

Because `extend` returns a **new Context instance**, modifications affect only that extension, leaving the parent context untouched.

## Real-World Usage Patterns for Context Extensions

The Cordis loader and fiber systems rely heavily on context extensions to create isolated execution environments.

### Isolating Plugin State with Custom Services

When building a plugin, you often need to add services that should not pollute the global context. In [[`packages/loader/src/config/tree.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/tree.ts)](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/tree.ts), the loader extends the base context with plugin-specific metadata:

```typescript
// packages/loader/src/config/tree.ts
this.ctx = ctx.extend({ baseUrl: ctx.baseUrl })

```

You can apply this same pattern to inject custom services:

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

export default function plugin(root: Context) {
  // Create an isolated context with a new service
  const pluginCtx = root.extend({ myService: new MyService() })

  // Register handlers using the extended context
  pluginCtx.on('message', (msg) => {
    pluginCtx.myService.handle(msg)
  })

  // The original root context remains clean
  // root.myService // ❌ TypeScript error: Property does not exist
}

```

### Creating Scoped Execution Contexts for Loaders

Each plugin entry in Cordis receives a dedicated context that carries a reference to the entry itself. This pattern appears in [[`packages/loader/src/config/entry.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/entry.ts)](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/entry.ts):

```typescript
// packages/loader/src/config/entry.ts
this.ctx = loader.ctx.extend({ [Entry.key]: this })

```

This ensures that any code running within that context can access the entry metadata without exposing it to sibling plugins.

### Fiber-Specific Context Creation

Execution fibers (async execution scopes) extend the parent context to include the fiber instance. The [[`packages/core/src/fiber.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/fiber.ts)](https://github.com/cordiverse/cordis/blob/main/packages/core/src/fiber.ts) implementation shows:

```typescript
// packages/core/src/fiber.ts
this.ctx = this.context = parent.extend({ fiber: this })

```

This creates an isolated scope for the fiber while maintaining access to all parent services.

## Using `isolate()` with Extensions

For temporary, named isolation scopes, Cordis provides the `isolate` helper, which internally leverages `extend`. In [[`packages/core/src/context.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/context.ts)](https://github.com/cordiverse/cordis/blob/main/packages/core/src/context.ts), `isolate` ultimately returns an extended context:

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

async function runTask(root: Context) {
  // Create an isolated scope named 'task' then extend it
  const taskCtx = root.isolate('task', Symbol('task')).extend({ taskId: 42 })

  // The taskId exists only within this context slice
  for await (const item of someAsyncGenerator(taskCtx)) {
    console.log(taskCtx.taskId, item)
  }
}

```

## Benefits of the Context Extension Pattern

**Isolation** — Extensions create a fresh sandbox that cannot accidentally corrupt the parent state. Services added to a child context are invisible to siblings and ancestors.

**Traceability** — The hidden `symbols.shadow` chain stores the original parent reference, allowing Cordis to resolve services up the prototype hierarchy while maintaining a clear ancestry graph for debugging and dependency injection.

**Composability** — Multiple extensions can be layered (e.g., a plugin extends a loader which extends the root), building a clear inheritance graph without deep cloning overhead.

**Type Safety** — The method signature `extend(meta = {}): this` preserves TypeScript typings, enabling IDE autocomplete for newly added properties while maintaining the base Context interface.

## Summary

- Contexts in Cordis are **immutable**; use `ctx.extend()` to create modified copies rather than mutating state directly.
- The `extend` method in [`packages/core/src/context.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/context.ts) creates a shallow copy using `Object.create()` and preserves the parent chain via `symbols.shadow`.
- Extensions are used throughout the framework for **loader configuration**, **plugin entries**, and **fiber creation** to isolate state while sharing services.
- Properties passed to `extend()` become own properties of the new context, inherited services remain accessible via the prototype chain, and the shadow reference enables upstream resolution.

## Frequently Asked Questions

### How does `ctx.extend()` differ from direct property assignment in Cordis?

Direct property assignment violates the immutability contract of the Cordis architecture. The `extend()` method creates a new context instance with the original as its prototype, ensuring that the parent context remains unchanged. This prevents accidental side effects between plugins and maintains predictable service resolution order.

### What is the purpose of the `symbols.shadow` chain in Cordis contexts?

The `symbols.shadow` property stores a reference to the original context from which an extension was created. This shadow chain enables Cordis to trace service lookups up the hierarchy and provides the framework with metadata about context ancestry, which is essential for debugging and for the `getTraceable` utility in [`packages/core/src/utils.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/utils.ts).

### Can I extend a Cordis context multiple times to create nested scopes?

Yes, you can chain `extend()` calls to create deeply nested context hierarchies. Each call creates a new layer in the prototype chain. The framework efficiently handles this through the shadow mechanism, though for temporary scopes, consider using `ctx.isolate()` first to create a named boundary before extending.

### Does extending a context copy service instances or share them?

Context extensions **share** existing service instances via JavaScript’s prototype chain rather than deep cloning them. When you call `extend()`, the new context inherits properties from the parent through `Object.create()`. Only the new properties defined in the `meta` parameter become own properties of the extended context. This shallow copy approach ensures memory efficiency while maintaining isolation for new additions.