Runtime Hooks

Hook return values

For SyncHook and AsyncHook, returning undefined means the plugin only observes the event. It does not clear a value returned by an earlier plugin. A later plugin can still replace that value by returning another non-undefined value.

For SyncWaterfallHook and AsyncWaterfallHook, return the full updated args object when you need to change the payload. Returning undefined keeps the current args and passes them to the next plugin.

beforeInit

SyncWaterfallHook

Updates the corresponding init configuration before the MF instance is initialized.

  • type
function beforeInit(args: BeforeInitOptions): BeforeInitOptions

type BeforeInitOptions ={
    userOptions: UserOptions;
    options: ModuleFederationRuntimeOptions;
    origin: ModuleFederation;
    shareInfo: ShareInfos;
}

interface ModuleFederationRuntimeOptions {
  id?: string;
  name: string;
  version?: string;
  remotes: Array<Remote>;
  shared: ShareInfos;
  plugins: Array<ModuleFederationRuntimePlugin>;
  inBrowser: boolean;
}

init

SyncHook

Called after the MF instance is initialized.

  • type
function init(args: InitOptions): void

type InitOptions ={
  options: ModuleFederationRuntimeOptions;
  origin: ModuleFederation;
}

beforeRequest

AsyncWaterfallHook

Called before resolving the remote path, useful for updating something before lookup.

  • type
async function beforeRequest(args: BeforeRequestOptions): Promise<BeforeRequestOptions>

type BeforeRequestOptions ={
  id: string;
  options: ModuleFederationRuntimeOptions;
  origin: ModuleFederation;
}

afterMatchRemote

AsyncHook

Called after the runtime matches a loadRemote request to a configured remote. If matching fails, the hook is still called with error. It is useful for diagnostics, request tracing, and checking whether a failure happened before manifest or remoteEntry loading.

  • type
async function afterMatchRemote(args: AfterMatchRemoteOptions): Promise<void>

type AfterMatchRemoteOptions ={
  id: string;
  options: ModuleFederationRuntimeOptions;
  remote?: Remote;
  expose?: string;
  remoteInfo?: RemoteInfo;
  error?: unknown;
  origin: ModuleFederation;
}

afterResolve

AsyncWaterfallHook

Called after resolving the remote path, allowing modification of the resolved content.

  • type
async function afterResolve(args: AfterResolveOptions): Promise<AfterResolveOptions>

type AfterResolveOptions ={
  id: string;
  pkgNameOrAlias: string;
  expose: string;
  remote: Remote;
  options: ModuleFederationRuntimeOptions;
  origin: ModuleFederation;
  remoteInfo: RemoteInfo;
  remoteSnapshot?: ModuleInfo;
}

onLoad

AsyncHook

Triggered once a federated module is loaded, allowing access and modification to the exports of the loaded file.

  • type
async function onLoad(args: OnLoadOptions): Promise<void>

type OnLoadOptions ={
  id: string;
  expose: string;
  pkgNameOrAlias: string;
  remote: Remote;
  options: ModuleOptions;
  origin: ModuleFederation;
  exposeModule: any;
  exposeModuleFactory: any;
  moduleInstance: Module;
}

type ModuleOptions = {
    remoteInfo: RemoteInfo;
    host: ModuleFederation;
}

interface RemoteInfo {
  name: string;
  version?: string;
  buildVersion?: string;
  entry: string;
  type: RemoteEntryType;
  entryGlobalName: string;
  shareScope: string;
}

afterLoadRemote

AsyncHook

Called after a loadRemote request finishes. It runs for successful loads, failed loads, and loads recovered by errorLoadRemote. Use it to record the final remote loading status.

onLoad runs before afterLoadRemote on a successful load. If a load fails and errorLoadRemote returns a fallback, afterLoadRemote receives the original error and recovered: true.

  • type
async function afterLoadRemote(args: AfterLoadRemoteOptions): Promise<void>

type AfterLoadRemoteOptions ={
  id: string;
  expose?: string;
  remote?: RemoteInfo;
  options?: {
    loadFactory?: boolean;
    from?: 'build' | 'runtime';
  };
  error?: unknown;
  recovered?: boolean;
  origin: ModuleFederation;
}

beforeInitContainer

AsyncWaterfallHook

Triggered before the host calls remoteEntry.init(...). Useful for dynamically rewriting the shareScope / initScope used in this initialization and the remoteEntryInitOptions passed to the remote.

  • type
async function beforeInitContainer(args: BeforeInitContainerOptions): Promise<BeforeInitContainerOptions>

type BeforeInitContainerOptions ={
  shareScope: ShareScopeMap[string];
  initScope: InitScope;
  remoteEntryInitOptions: RemoteEntryInitOptions;
  remoteInfo: RemoteInfo;
  origin: ModuleFederation;
}

initContainer

AsyncWaterfallHook

Triggered after remoteEntry.init(...) completes successfully. Useful for metrics, observability, or post-initialization customization.

  • type
async function initContainer(args: InitContainerOptions): Promise<InitContainerOptions>

type InitContainerOptions ={
  shareScope: ShareScopeMap[string];
  initScope: InitScope;
  remoteEntryInitOptions: RemoteEntryInitOptions;
  remoteInfo: RemoteInfo;
  remoteEntryExports: RemoteEntryExports;
  origin: ModuleFederation;
  id?: string;
  remoteSnapshot?: ModuleInfo;
}

handlePreloadModule

SyncHook

Handles the preloading logic for remotes.

  • type
function handlePreloadModule(args: HandlePreloadModuleOptions): void

type HandlePreloadModuleOptions ={
  id: string;
  name: string;
  remote: Remote;
  remoteSnapshot: ModuleInfo;
  preloadConfig: PreloadRemoteArgs;
  origin: ModuleFederation;
}

errorLoadRemote

AsyncHook

Called if loading remotes fails, enabling custom error handling. Can return a custom fallback logic.

  • type
async function errorLoadRemote(args: ErrorLoadRemoteOptions): Promise<void | unknown>

type ErrorLoadRemoteOptions ={
  id: string;
  error: unknown;
  options?: any;
  from: 'build' | 'runtime';
  lifecycle: 'beforeRequest' | 'beforeLoadShare' | 'afterResolve' | 'onLoad';
  remote?: RemoteInfo;
  expose?: string;
  origin: ModuleFederation;
}

The lifecycle parameter indicates the stage where the error occurred:

  • beforeRequest: Error during initial request processing
  • afterResolve: Error during manifest loading (most common for network failures)
  • onLoad: Error during module loading and execution
  • beforeLoadShare: Error while loading a remoteEntry during shared dependency initialization

If this hook returns a fallback module, the runtime uses that value to continue. If a diagnostics or logging plugin returns undefined, it does not clear a fallback returned by another plugin.

  • example
import { createInstance, loadRemote } from '@module-federation/enhanced/runtime'
import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime';

const fallbackPlugin: () => ModuleFederationRuntimePlugin =
  function () {
    return {
      name: 'fallback-plugin',
      errorLoadRemote(args) {
        const { lifecycle, id, error } = args;

        if (error) {
          console.warn(`Failed to load remote ${id} at ${lifecycle}:`, error?.message || error);
        }

        switch (lifecycle) {
          case 'afterResolve':
            return {
              id: id || 'fallback',
              name: id || 'fallback',
              metaData: { /* fallback manifest */ },
              shared: [],
              remotes: [],
              exposes: []
            };

          case 'beforeRequest':
            console.warn(`Request processing failed for ${id}`);
            return void 0;

          case 'onLoad':
            return () => ({
              __esModule: true,
              default: () => 'Fallback Component'
            });

          case 'beforeLoadShare':
            console.warn(`Shared dependency loading failed for ${id}`);
            return () => ({
              __esModule: true,
              default: {}
            });

          default:
            console.warn(`Unknown lifecycle ${lifecycle} for ${id}`);
            return void 0;
        }
      },
    };
  };

const mf = createInstance({
    name: 'mf_host',
    remotes: [
        {
            name: "remote",
            alias: "app1",
            entry: "http://localhost:2001/mf-manifest.json"
        }
    ],
    plugins: [fallbackPlugin()]
});

mf.loadRemote('app1/un-existed-module').then(mod=>{
  expect(mod).toEqual('fallback');
});

beforeLoadShare

AsyncWaterfallHook

Called before loading shared, can be used to modify the corresponding shared configuration.

  • type
async function beforeLoadShare(args: BeforeLoadShareOptions): Promise<BeforeLoadShareOptions>

type BeforeLoadShareOptions ={
  pkgName: string;
  shareInfo?: Shared;
  shared: Options['shared'];
  origin: ModuleFederation;
}

afterLoadShare

SyncHook

Called after a shared dependency is successfully resolved by loadShare or loadShareSync. Use it to observe which provider and version were selected.

  • type
function afterLoadShare(args: AfterLoadShareOptions): void

type AfterLoadShareOptions ={
  pkgName: string;
  shareInfo?: Partial<Shared>;
  selectedShared?: Partial<Shared>;
  shared: Options['shared'];
  shareScopeMap: ShareScopeMap;
  lifecycle: 'loadShare' | 'loadShareSync';
  origin: ModuleFederation;
}

errorLoadShare

SyncHook

Called when shared dependency resolution fails or cannot select a valid shared dependency. It is useful for diagnostics around missing shared dependencies, version mismatch, and eager configuration errors.

  • type
function errorLoadShare(args: ErrorLoadShareOptions): void

type ErrorLoadShareOptions ={
  pkgName: string;
  shareInfo?: Partial<Shared>;
  shared: Options['shared'];
  shareScopeMap: ShareScopeMap;
  lifecycle: 'loadShare' | 'loadShareSync';
  origin: ModuleFederation;
  error?: unknown;
  recovered?: boolean;
}

initContainerShareScopeMap

SyncWaterfallHook

Triggered when the host aligns a remote’s share-scope mapping. Useful for redirecting one scope to another (for example, aliasing scope1 to default so they share the same pool).

  • type
function initContainerShareScopeMap(args: InitContainerShareScopeMapOptions): InitContainerShareScopeMapOptions

type InitContainerShareScopeMapOptions ={
  shareScope: ShareScopeMap[string];
  options: Options;
  origin: ModuleFederation;
  scopeName: string;
  hostShareScopeMap?: ShareScopeMap;
}

resolveShare

SyncWaterfallHook

Allows overriding the final shared module selection result.

resolveShare runs after the runtime has already picked the candidate scope and version. At this point, changing fields like scope or version on args does not change the final result by itself. To switch to a different shared module, replace args.resolver and return the shared record you want to use.

  • type
function resolveShare(args: ResolveShareOptions): ResolveShareOptions

type ResolveShareOptions ={
  shareScopeMap: ShareScopeMap;
  scope: string;
  pkgName: string;
  version: string;
  shareInfo: Shared;
  GlobalFederation: Federation;
  resolver: () => {
    shared: Shared;
    useTreesShaking: boolean;
  } | undefined;
}
  • example
import { createInstance, loadRemote } from '@module-federation/enhanced/runtime'

import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime';

const customSharedPlugin: () => ModuleFederationRuntimePlugin =
  function () {
    return {
      name: 'custom-shared-plugin',
      resolveShare(args) {
        const { pkgName, shareScopeMap } = args;

        if (pkgName !== 'react') {
          return args;
        }

        const fallbackShared = shareScopeMap.default?.react?.['17.0.0'];
        if (!fallbackShared) {
          return args;
        }

        args.resolver = function () {
          return {
            shared: fallbackShared,
            useTreesShaking: false,
          };
        };
        return args;
      },
    };
  };


const mf = createInstance({
    name: 'mf_host',
    shared: {
      react: {
        version: '17.0.0',
        scope: 'default',
        lib: () => React,
        shareConfig: {
          singleton: true,
          requiredVersion: '^17.0.0',
        },
      },
    },
    plugins: [customSharedPlugin()]
});

mf.loadShare('react').then((reactFactory) => {
  expect(reactFactory()).toEqual(React);
});

beforePreloadRemote

AsyncHook

Called before the preload handler executes any preload logic.

  • type
async function beforePreloadRemote(args: BeforePreloadRemoteOptions): Promise<void>

type BeforePreloadRemoteOptions ={
  preloadOps: Array<PreloadRemoteArgs>;
  options: Options;
  origin: ModuleFederation;
}

generatePreloadAssets

AsyncHook

Used to control the generation of resources that need to be preloaded.

  • type
async function generatePreloadAssets(args: GeneratePreloadAssetsOptions): Promise<PreloadAssets>

type GeneratePreloadAssetsOptions ={
  origin: ModuleFederation;
  preloadOptions: PreloadOptions[number];
  remote: Remote;
  remoteInfo: RemoteInfo;
  remoteSnapshot: ModuleInfo;
  globalSnapshot: GlobalModuleInfo;
}

interface PreloadAssets {
  cssAssets: Array<string>;
  jsAssetsWithoutEntry: Array<string>;
  entryAssets: Array<EntryAssets>;
}

loaderHook

loaderHook is used to intercept resource loading and module-factory resolution.

beforeInitRemote

AsyncHook

Called before initializing the remote container with remoteEntry.init(...).

  • type
async function beforeInitRemote(args: BeforeInitRemoteOptions): Promise<void>

type BeforeInitRemoteOptions ={
  id?: string;
  remoteInfo: RemoteInfo;
  remoteSnapshot?: ModuleInfo;
  origin: ModuleFederation;
}

afterInitRemote

AsyncHook

Called after remote container initialization succeeds or fails. When the remote was already initialized, cached is set to true.

  • type
async function afterInitRemote(args: AfterInitRemoteOptions): Promise<void>

type AfterInitRemoteOptions ={
  id?: string;
  remoteInfo: RemoteInfo;
  remoteSnapshot?: ModuleInfo;
  remoteEntryExports?: RemoteEntryExports;
  error?: unknown;
  cached?: boolean;
  origin: ModuleFederation;
}

beforeGetExpose

AsyncHook

Called before remoteEntry.get(expose).

  • type
async function beforeGetExpose(args: BeforeGetExposeOptions): Promise<void>

type BeforeGetExposeOptions ={
  id: string;
  expose: string;
  moduleInfo: RemoteInfo;
  remoteEntryExports: RemoteEntryExports;
  origin: ModuleFederation;
}

afterGetExpose

AsyncHook

Called after remoteEntry.get(expose) succeeds or fails.

  • type
async function afterGetExpose(args: AfterGetExposeOptions): Promise<void>

type AfterGetExposeOptions ={
  id: string;
  expose: string;
  moduleInfo: RemoteInfo;
  remoteEntryExports: RemoteEntryExports;
  moduleFactory?: () => unknown | Promise<unknown>;
  error?: unknown;
  origin: ModuleFederation;
}

beforeExecuteFactory

AsyncHook

Called before executing the exposed module factory. It is skipped when loadRemote is called with loadFactory: false.

  • type
async function beforeExecuteFactory(args: BeforeExecuteFactoryOptions): Promise<void>

type BeforeExecuteFactoryOptions ={
  id: string;
  expose: string;
  moduleInfo: RemoteInfo;
  loadFactory: boolean;
  origin: ModuleFederation;
}

afterExecuteFactory

AsyncHook

Called after the exposed module factory succeeds or fails. It is skipped when loadRemote is called with loadFactory: false.

  • type
async function afterExecuteFactory(args: AfterExecuteFactoryOptions): Promise<void>

type AfterExecuteFactoryOptions ={
  id: string;
  expose: string;
  moduleInfo: RemoteInfo;
  loadFactory: boolean;
  exposeModule?: unknown;
  error?: unknown;
  origin: ModuleFederation;
}

createScript

SyncHook

Used to modify the script when loading resources.

  • type
function createScript(args: CreateScriptOptions): CreateScriptHookReturn

type CreateScriptOptions ={
  url: string;
  attrs?: Record<string, any>;
  remoteInfo?: RemoteInfo;
}
  • example
import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime';

const changeScriptAttributePlugin: () => ModuleFederationRuntimePlugin =
  function () {
    return {
      name: 'change-script-attribute',
      createScript({ url }) {
        if (url === 'http://localhost:3001/remoteEntry.js') {
          let script = document.createElement('script');
          script.src = url;
          script.setAttribute('loader-hooks', 'isTrue');
          script.setAttribute('crossorigin', 'anonymous');
          return script;
        }
      }
    };
  };

fetch

The fetch function allows customizing the request that fetches the manifest JSON. A successful Response must yield a valid JSON.

AsyncHook

  • Type
function fetch(manifestUrl: string, requestInit: RequestInit, remoteInfo?: RemoteInfo): Promise<Response> | void | false;
  • Example for including the credentials when fetching the manifest JSON:
// fetch-manifest-with-credentials-plugin.ts
import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';

export default function (): FederationRuntimePlugin {
  return {
    name: 'fetch-manifest-with-credentials-plugin',
    fetch(manifestUrl, requestInit) {
      return fetch(manifestUrl, {
        ...requestInit,
        credentials: 'include'
      });
    },
  }
};

SyncHook

Used to customize the link element created for preload/style loading.

  • type
function createLink(args: CreateLinkOptions): HTMLLinkElement | void

type CreateLinkOptions ={
  url: string;
  attrs?: Record<string, any>;
  remoteInfo?: RemoteInfo;
}

loadEntryError

AsyncHook

Triggered when loading remoteEntry fails (typically script loading errors). Useful for retries or custom fallback strategies.

  • type
async function loadEntryError(args: LoadEntryErrorOptions): Promise<Promise<RemoteEntryExports | undefined> | undefined>

type LoadEntryErrorOptions ={
  getRemoteEntry: typeof getRemoteEntry;
  origin: ModuleFederation;
  remoteInfo: RemoteInfo;
  remoteEntryExports?: RemoteEntryExports;
  globalLoading: Record<string, Promise<void | RemoteEntryExports> | undefined>;
  uniqueKey: string;
}

afterLoadEntry

AsyncHook

Called after loading a remoteEntry succeeds, fails, or is recovered by loadEntryError.

  • type
async function afterLoadEntry(args: AfterLoadEntryOptions): Promise<void>

type AfterLoadEntryOptions ={
  origin: ModuleFederation;
  remoteInfo: RemoteInfo;
  remoteEntryExports?: RemoteEntryExports | false | void;
  error?: unknown;
  recovered?: boolean;
}

getModuleFactory

AsyncHook

Triggered before calling remoteEntry.get(expose), allowing custom module-factory resolution.

  • type
async function getModuleFactory(args: GetModuleFactoryOptions): Promise<(() => Promise<Module>) | undefined>

type GetModuleFactoryOptions ={
  remoteEntryExports: RemoteEntryExports;
  expose: string;
  moduleInfo: RemoteInfo;
}

loadEntry

The loadEntry function allows for full customization of remotes, enabling you to extend and create new remote types. The following two simple examples demonstrate loading JSON data and module delegation.

asyncHook

  • Type
function loadEntry(args: LoadEntryOptions): RemoteEntryExports | void;

type LoadEntryOptions = {
  createScriptHook: SyncHook,
  remoteEntryExports?: RemoteEntryExports,
  remoteInfo: RemoteInfo
};
interface RemoteInfo {
  name: string;
  version?: string;
  buildVersion?: string;
  entry: string;
  type: RemoteEntryType;
  entryGlobalName: string;
  shareScope: string;
}
export type RemoteEntryExports = {
  get: (id: string) => () => Promise<Module>;
  init: (
    shareScope: ShareScopeMap[string],
    initScope?: InitScope,
    remoteEntryInitOPtions?: RemoteEntryInitOptions,
  ) => void | Promise<void>;
};
  • Example Loading JSON Data
// load-json-data-plugin.ts
import { init } from '@module-federation/enhanced/runtime';
import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';

const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () {
  return {
    name: 'load-json-data-plugin',
    loadEntry({ remoteInfo }) {
      if (remoteInfo.jsonA === "jsonA") {
        return {
          init(shareScope, initScope, remoteEntryInitOPtions) {},
          async get(path) {
            const json = await fetch(remoteInfo.entry + ".json").then(res => res.json())
            return () => ({
              path,
              json
            })
          }
        }
      }
    },
  };
};
// module-federation-config
{
  remotes: {
    jsonA: "jsonA@https://cdn.jsdelivr.net/npm/@module-federation/runtime/package"
  }
}
// src/bootstrap.js
import jsonA from "jsonA"
jsonA // {...json data}
  • Example Delegate Modules
// delegate-modules-plugin.ts
import { init } from '@module-federation/enhanced/runtime';
import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';

const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () {
  return {
    name: 'delegate-modules-plugin',
    loadEntry({ remoteInfo }) {
      if (remoteInfo.name === "delegateModulesA") {
        return {
          init(shareScope, initScope, remoteEntryInitOPtions) {},
          async get(path) {
            path = path.replace("./", "")
            const {[path]: factory} = await import("./delegateModulesA.js")
            const result = await factory()
            return () => result
          }
        }
      }
    },
  };
};
// ./src/delegateModulesA.js
export async function test1() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve("test1 value")
    }, 3000)
  })
}
export async function test2() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve("test2 value")
    }, 3000)
  })
}
// module-federation-config
{
  remotes: {
    delegateModulesA: "delegateModulesA@https://delegateModulesA.js"
  }
}
// src/bootstrap.js
import test1 from "delegateModulesA/test1"
import test2 from "delegateModulesA/test2"
test1 // "test1 value"
test2 // "test2 value"

bridgeHook

bridgeHook is defined in runtime-core/src/core.ts and is used to extend context across bridge render/destroy stages (for example, React/Vue bridge).

beforeBridgeRender

SyncHook

Triggered before bridge rendering. You can return an object to augment render arguments (for example, extraProps).

  • type
function beforeBridgeRender(args: Record<string, any>): void | Record<string, any>

afterBridgeRender

SyncHook

Triggered after bridge rendering.

  • type
function afterBridgeRender(args: Record<string, any>): void | Record<string, any>

beforeBridgeDestroy

SyncHook

Triggered before bridge teardown.

  • type
function beforeBridgeDestroy(args: Record<string, any>): void | Record<string, any>

afterBridgeDestroy

SyncHook

Triggered after bridge teardown.

  • type
function afterBridgeDestroy(args: Record<string, any>): void | Record<string, any>