# How Cordis Supports Dynamic Loading and Unloading of Plugins

> Discover how Cordis enables dynamic plugin loading and unloading. Learn about its Loader service, Entry objects, and Fiber instances for runtime management and hot-reloading.

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

---

**Cordis implements dynamic plugin management through a Loader service that orchestrates Entry objects and Fiber instances, enabling runtime loading, hot-reloading, and cleanup via RegistryService.**

Cordis (`cordiverse/cordis`) is a progressive TypeScript framework designed for modular application development. Its architecture centers on **dynamic loading and unloading of plugins**, allowing developers to add, update, or remove functionality at runtime without restarting the application.

## The Loader Service Architecture

The dynamic plugin system relies on the `Loader` service defined in [`packages/loader/src/index.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/index.ts). This service maintains a tree of **Entry** objects that represent individual plugin instances and their configurations.

### Entry Objects and Plugin Resolution

Each plugin is wrapped in an `Entry` class ([`packages/loader/src/config/entry.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/entry.ts)). When loading occurs:

1. The entry resolves the module using `loader.unwrapExports`
2. It invokes `ctx.registry.plugin` to instantiate the plugin's runtime `Fiber`
3. The `Entry._init` method establishes the plugin's context and dependency injection

The `RegistryService` ([`packages/core/src/registry.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/registry.ts)) stores a `Plugin.Runtime` for each plugin function and tracks active `Fiber` instances.

### Fiber Lifecycle Management

Every plugin instance runs inside a **Fiber**, which represents the active execution context. The `RegistryService.plugin` method returns a `Fiber` that manages the plugin's lifecycle, including updates and disposal.

## Runtime Loading Process

When you invoke `ctx.plugin()`, Cordis executes the following sequence:

- `ctx.plugin(plugin, config)` delegates to `RegistryService.plugin`
- The registry creates a runtime representation and instantiates a new `Fiber`
- For file-based loading, `Loader.read()` parses configuration files and constructs `Entry` objects
- The entry calls `ctx.registry.plugin` to activate the plugin

## Hot-Reloading Configuration Updates

Cordis supports hot-module replacement through the `'internal/update'` event. When a plugin's configuration changes:

- The `Loader` detects the change and triggers `Entry.update`
- The entry emits `'loader/partial-dispose'` and patches the context
- It calls `fiber.update` to apply new configuration without destroying the plugin instance

This process occurs in [`packages/loader/src/index.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/index.ts) (lines 74-86), allowing stateful updates without service interruption.

## Unloading and Resource Cleanup

Removing plugins requires careful resource management to prevent memory leaks.

### Explicit Unloading via Registry

To completely remove a plugin:

- Call `ctx.registry.delete(plugin)` to invoke `RegistryService.delete` (lines 62-70 in [`packages/core/src/registry.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/registry.ts))
- This removes the runtime from the internal map
- All associated fibers are disposed, releasing event listeners and injected dependencies

### Group Management and Partial Disposal

For grouped plugins, `EntryGroup` ([`packages/loader/src/config/group.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/group.ts)) manages collections atomically. Removing a plugin from a group:

- Invokes `EntryGroup.remove`, which disposes the specific fiber
- Emits `'loader/partial-dispose'` to notify the system
- Preserves other plugins in the group

The `Loader` also listens to `'internal/plugin'` events to handle implicit unloads (lines 88-124 in [`packages/loader/src/index.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/index.ts)), logging the operation and marking entries as disabled.

## Practical Implementation Examples

Here is how to dynamically manage plugins in Cordis applications:

Loading and updating a plugin programmatically:

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

// Dynamically load a plugin
await ctx.plugin(MyPlugin, { foo: 'bar' })

// Update its configuration later (hot‑reload)
await ctx.registry.plugin(MyPlugin).update({ foo: 'baz' })

// Unload the plugin
await ctx.registry.delete(MyPlugin)   // removes runtime and disposes all fibers

```

Using the Loader for file-based plugins:

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

const loader = ctx.loader as Loader

// Load a plugin from a file path
await loader.read([{ name: './plugins/example', id: 'example' }])

// Reload after editing the file (HMR)
await loader.update(loader.tree.get('example'), { config: { enabled: true } })

// Unload the plugin
await loader.tree.store['example'].fiber?.dispose()

```

## Summary

- **Cordis** enables **dynamic loading and unloading of plugins** through the `Loader` service and `RegistryService` coordination.
- **Entry** objects in [`packages/loader/src/config/entry.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/entry.ts) manage individual plugin lifecycles and configuration resolution.
- The **Fiber** system tracks active plugin instances, supporting hot-reloading via `fiber.update` without recreation.
- **Group management** through `EntryGroup` allows atomic operations on plugin collections.
- Explicit cleanup occurs through `ctx.registry.delete`, which removes runtimes and disposes all associated fibers defined in [`packages/core/src/registry.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/registry.ts).

## Frequently Asked Questions

### How does Cordis handle plugin updates without restarting the application?

Cordis listens for the `'internal/update'` event in the `Loader` service. When triggered, the corresponding `Entry` emits `'loader/partial-dispose'`, patches the context with new configuration, and invokes `fiber.update` to refresh the plugin state while preserving its identity and injected dependencies.

### What is the difference between `ctx.plugin()` and direct Loader usage?

`ctx.plugin()` provides a programmatic API for immediate plugin instantiation with configuration, while the `Loader` service (accessed via `ctx.loader`) manages file-based plugin discovery and hot-reloading. The Loader constructs `Entry` objects from configuration files and orchestrates updates through the internal event system.

### How does Cordis prevent memory leaks when unloading plugins?

The `RegistryService.delete` method (located in [`packages/core/src/registry.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/registry.ts)) removes the `Plugin.Runtime` from its internal Map and explicitly disposes every `Fiber` associated with that plugin. This ensures all event listeners, asynchronous operations, and injected services are properly terminated.

### Can plugins be organized into groups for bulk operations?

Yes, the `EntryGroup` class in [`packages/loader/src/config/group.ts`](https://github.com/cordiverse/cordis/blob/main/packages/loader/src/config/group.ts) enables collective management. You can add, update, or remove multiple plugins simultaneously, with the group handling partial disposal events and maintaining consistency across the plugin tree.