Skip to content

Configuration reference ​

An MFE Forge workspace has two metadata files with separate roles (see ADR 0001 in docs/ai/decisions/0001-manifest-and-config.md):

FileAuthorRole
mfeforge.config.js / .tsYouPolicy: defaults, federation settings, port range, tooling choices.
mfe-forge.manifest.jsonThe CLITopology: discovered apps, hosts, packages, ports, and federation names. Generated; do not edit by hand.

Config file ​

mfe-forge init writes mfeforge.config.js:

js
/** @type {import('mfe-forge').MFEConfig} */
export default {
  name: 'my-platform',
  organization: 'acme',
  defaults: {
    framework: 'react',
    language: 'typescript',
    styling: 'tailwind',
    stateManagement: 'zustand',
    packageManager: 'bun',
  },
  federation: {
    shared: ['react', 'react-dom', 'react-router-dom'],
  },
  dev: { portRange: [3000, 3999], autoStartHost: true, parallelLimit: 10, cors: true },
  testing: { unit: 'none', e2e: 'none' },
  ci: { provider: 'github', docker: true },
}

You can also use defineConfig, which returns its argument unchanged and exists only for editor typing:

ts
// mfeforge.config.ts
import { defineConfig } from 'mfe-forge'

export default defineConfig({
  name: 'my-platform',
  dev: { portRange: [4000, 4999] },
})

Loading rules ​

The configuration is read from the current working directory only. Parent directories are not searched.

  1. mfeforge.config.ts, otherwise mfeforge.config.js. Only the first file that exists is considered. It is evaluated by a lightweight static loader, not by a compiler or module loader. The loader:

    • requires an export default expression;
    • removes comments, import statements, variable type annotations, as / as const / satisfies assertions, and generic arguments on defineConfig<...>(...);
    • evaluates the result with defineConfig as an identity function.

    Imported values are not resolved. The exported object must be self-contained: literals, inline expressions, and local consts only.

  2. If mfeforge.config.ts cannot be evaluated, a warning naming the file is printed and the built-in defaults are used. No other source is tried.

  3. If mfeforge.config.js cannot be evaluated statically (for example it imports a helper), it is loaded through cosmiconfig as a regular module instead.

  4. If neither file exists, cosmiconfig looks in this order for: mfeforge.config.js, .mfeforgerc (JSON or YAML), .mfeforgerc.json, and an "mfeforge" key in package.json.

  5. If nothing is found, every field takes its default.

name defaults to the name of the current directory. The merged object is then validated. Unknown keys are dropped silently, and any violation aborts the command with exit code 3:

text
❌ Error: Invalid MFE Forge configuration:
  defaults.packageManager: Invalid enum value. Expected 'bun' | 'pnpm' | 'npm', received 'yarn'

WARNING

A broken mfeforge.config.ts does not fail the command: it prints Warning: Failed to load ... and continues with defaults. Read warnings on stderr carefully.

Writing the config ​

mfe-forge config --set and the interactive config mode rewrite the whole normalized configuration (defaults included) as a plain object literal into the first existing file among mfeforge.config.ts, mfeforge.config.js, .mfeforgerc.json. If none exists, they create mfeforge.config.ts. Comments and formatting are not preserved.

Field reference ​

Every section is optional; omitted fields take the defaults below. "Used by" lists what currently reads the field. Fields marked not read are validated and stored but have no effect on CLI behavior yet.

Top level ​

FieldTypeDefaultDescriptionUsed by
namenon-empty stringcurrent directory nameProject name; written to the manifest.status, manifest
organizationstringnonenpm scope without @. Generated packages are named @<organization>/<scope>-<name>.generate, discovery
scopesstring[]noneDeclared team scopes. Scopes are discovered from apps/ instead.not read
registry.urlURL stringnonePackage registry URL. Must be a valid URL when registry is present.not read
registry.authstringnoneRegistry auth reference. Do not commit secrets.not read

defaults ​

FieldTypeDefaultDescriptionUsed by
framework'react' | 'vue' | 'svelte''react'Only React templates exist.not read
language'typescript' | 'javascript''typescript'Templates are TypeScript only.not read
styling'tailwind' | 'css-modules' | 'styled-components' | 'none''tailwind'Templates always use Tailwind.not read
stateManagement'zustand' | 'redux' | 'jotai' | 'none''zustand'Templates always include Zustand.not read
packageManager'bun' | 'pnpm' | 'npm''bun'Package manager for generated scripts, dev, build, test, and the doctor lockfile check.generate, dev, build, test, doctor

federation ​

FieldTypeDefaultDescriptionUsed by
plugin'@originjs/vite-plugin-federation' | '@module-federation/vite''@originjs/vite-plugin-federation'Templates always use @originjs/vite-plugin-federation.not read
sharedstring[]['react', 'react-dom', 'react-router-dom']Dependencies expected to be shared singletons. Generated vite.config.ts files have their own hard-coded shared list.deps
runtimePluginstringnoneModule Federation runtime plugin path.not read

dev ​

FieldTypeDefaultDescriptionUsed by
portRange[number, number][3000, 3999]Inclusive range searched when generate assigns ports to apps and hosts.generate
autoStartHostbooleantrueEditable in interactive config.not read
parallelLimitpositive integer10Intended process limit for dev.not read
corsbooleantrueGenerated Vite configs always set cors: true.not read

build ​

FieldTypeDefaultDescriptionUsed by
targetstring | string[]'esnext'Build target.not read
minifybooleanfalseMinify output.not read
cssCodeSplitbooleanfalseCSS code splitting.not read
sourcemapbooleantrueEmit source maps.not read

Build settings live in each app's vite.config.ts (build: { target: 'esnext', minify: false, cssCodeSplit: false } in the templates).

testing ​

FieldTypeDefaultDescriptionUsed by
unit'vitest' | 'jest' | 'none''vitest'Unit test runner.not read
e2e'playwright' | 'cypress' | 'none''playwright'E2E test runner.not read
coveragebooleantrueCollect coverage.not read

designSystem ​

FieldTypeDefaultDescriptionUsed by
enabledbooleantrueDesign system enabled.not read
tokensbooleantrueDesign tokens enabled.not read
storybookbooleantrueStorybook enabled.not read

ci ​

FieldTypeDefaultDescriptionUsed by
provider'github' | 'gitlab' | 'azure' | 'none''github'CI provider. init writes 'github' or 'none'.not read
dockerbooleantrueDocker support.not read
deployTarget'vercel' | 'netlify' | 'aws' | 'gcp' | 'none''none'Deployment target.not read

Inspect the effective configuration

mfe-forge config --show prints the configuration after defaults are applied: exactly what the CLI uses.

Manifest ​

mfe-forge.manifest.json is written to the workspace root by sync (unless --dry-run), status, and migrate when it is missing. deps and check read it when present.

json
{
  "version": 1,
  "generatedAt": "2026-10-12T21:16:07.075Z",
  "project": { "name": "my-platform", "root": "/abs/path/to/my-platform" },
  "apps": [
    {
      "name": "shop/cart",
      "scope": "shop",
      "port": 3001,
      "url": "http://localhost:3001/assets/remoteEntry.js",
      "camelName": "shopCart",
      "pascalName": "ShopCart",
      "federationName": "shopCartApp",
      "packageName": "@acme/shop-cart",
      "entry": "./src/bootstrap.tsx"
    }
  ],
  "hosts": ["shop/host"],
  "packages": [{ "name": "@acme/utils", "path": "packages/utils", "dependencies": {} }],
  "environments": {}
}
FieldTypeDescription
version1Format version. check rejects other values.
generatedAtISO 8601 stringTime of generation. Changes on every write.
project.namestringname from the configuration.
project.rootstringAbsolute path of the workspace root.
apps[]RemoteApp[]Every apps/<scope>/<name>/ that contains vite.config.ts, hosts included.
apps[].namestringscope/name.
apps[].scopestringScope directory.
apps[].portnumberFirst port: <n> in vite.config.ts, or 3000 if none is found.
apps[].urlstringhttp://localhost:<port>/assets/remoteEntry.js.
apps[].camelName, pascalNamestringCase conversions of scope/name.
apps[].federationNamestring<camelName>App; the key used in host remotes.
apps[].packageNamestringComputed from organization and the path. It is not read from the app's package.json.
apps[].entrystringAlways ./src/bootstrap.tsx.
hostsstring[]scope/host for each scope with apps/<scope>/host/vite.config.ts.
packages[]object[]Each packages/<dir>/ with a package.json: name (from package.json, or the directory name), path (relative), and dependencies (runtime dependencies only).
environmentsobjectReserved for per-environment remote URLs. Always {} today.

The RemoteApp type also declares optional exposes, routes, and shared fields, but discovery never populates them.

Should I commit the manifest?

generatedAt and the absolute project.root change between runs and machines, so a committed manifest produces noisy diffs. Either add it to .gitignore and regenerate it in CI with mfe-forge status, or accept the churn.

Released under the MIT License.