What Is ModuleScopePlugin in Create React App and How Does It Restrict Imports?

ModuleScopePlugin is a custom Webpack resolver plugin in Create React App (CRA) that enforces a strict project boundary by blocking any import that resolves outside the src/ directory unless explicitly whitelisted.

Create React App uses ModuleScopePlugin (located in packages/react-dev-utils/ModuleScopePlugin.js) to guarantee that application code remains isolated within the src/ folder. This prevents accidental dependencies on files scattered across the repository root or other arbitrary locations, ensuring consistent builds and predictable module resolution.

What Is ModuleScopePlugin?

ModuleScopePlugin is a Webpack plugin that intercepts module resolution at the compiler level. It was introduced in the facebook/create-react-app monorepo to solve a specific problem: developers accidentally importing configuration files, build scripts, or shared utilities located outside the src/ directory, which breaks the encapsulation of the React application.

The plugin operates as a resolver hook in Webpack’s enhanced-resolve pipeline. When Webpack attempts to resolve an import statement, ModuleScopePlugin examines both the issuer (the file containing the import) and the requested file path before allowing the resolution to proceed.

How ModuleScopePlugin Restricts Imports

The restriction logic follows a two-step validation process defined in packages/react-dev-utils/ModuleScopePlugin.js.

The Issuer Check

First, the plugin verifies whether the file performing the import (the issuer) resides within the configured source directories (typically paths.appSrc which maps to src/). If the issuer is outside these directories, the plugin immediately returns without enforcing any restrictions, allowing build scripts and configuration files at the project root to import freely.

The Request Validation

If the issuer is inside src/, the plugin then checks the requested file path:

  1. If the request resolves to a location inside any configured appSrc directory, resolution proceeds normally.
  2. If the request resolves outside all appSrc directories and is not present in the allowedFiles or allowedPaths sets, the plugin throws a blocking error.

The error message explicitly states:


You attempted to import ../../config which falls outside of the project src/ directory.
Relative imports outside of src/ are not supported.
You can either move it inside src/, or add a symlink to it from project's node_modules/.

This error originates from lines 66-92 in ModuleScopePlugin.js, where the plugin constructs the error using the relative path information and resolution context.

ModuleScopePlugin Configuration in CRA

The plugin is instantiated in packages/react-scripts/config/webpack.config.js at line 339:

new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson])

This configuration establishes two critical parameters:

  • paths.appSrc: Defines the primary source directory (usually ./src)
  • allowedFiles array: Contains paths.appPackageJson (the project's package.json), permitting imports like import packageJson from '../package.json' despite package.json residing at the repository root

The plugin applies to both development and production builds, ensuring consistent enforcement across all Webpack configurations generated by CRA.

What Imports Are Blocked vs. Allowed

Understanding the boundary conditions helps developers structure their projects correctly.

Allowed Imports

Any import where both the issuer and target reside within src/:

// src/components/App.js
import Helper from '../utils/helper';  // ✅ Allowed: both files under src/
import logo from './logo.svg';         // ✅ Allowed: relative import within src/

Imports from node_modules are also permitted because Webpack resolves these through its standard node_modules resolution algorithm before ModuleScopePlugin validates the result.

Blocked Imports

Any attempt to import files located outside src/ from within src/:

// src/components/App.js
import config from '../../config';     // ❌ Blocked: resolves to repository root
import rootUtil from '../..';         // ❌ Blocked: outside src/ boundary

Attempting to build this code triggers the descriptive error from ModuleScopePlugin.js, halting the compilation process.

How to Whitelist Files Outside src/

While CRA strongly recommends moving all application code into src/, the plugin provides escape hatches for edge cases.

Adding Files to allowedFiles

You can permit specific files by modifying the plugin instantiation in webpack.config.js (requires ejecting or using a tool like react-app-rewired):

const path = require('path');

new ModuleScopePlugin(paths.appSrc, [
  paths.appPackageJson,
  path.resolve(__dirname, '../../shared/constants.js')  // Whitelisted file
])

This allows import constants from '../../shared/constants' from within src/ despite the file residing outside the source directory.

Adding Directories to allowedPaths

The plugin also accepts an allowedPaths option (distinct from allowedFiles) that permits entire directories. This is handled internally in the plugin's resolution logic and can be passed as a third argument or configured through the plugin's options object depending on the CRA version.

Summary

  • ModuleScopePlugin is a Webpack resolver plugin in packages/react-dev-utils/ModuleScopePlugin.js that enforces the src/ directory boundary.
  • It blocks any import that resolves outside src/ unless the file is explicitly whitelisted in allowedFiles or allowedPaths.
  • The plugin is configured in packages/react-scripts/config/webpack.config.js with new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]).
  • Attempting to import from outside src/ produces a descriptive error suggesting moving the file into src/ or creating a symlink in node_modules/.

Frequently Asked Questions

Can I disable ModuleScopePlugin in CRA without ejecting?

Disabling ModuleScopePlugin without ejecting requires using a customization tool like react-app-rewired or craco. You would modify the Webpack configuration to remove the plugin from the resolve.plugins array. However, disabling it removes the safety guard that keeps your application code isolated, potentially leading to brittle dependencies on files outside your source tree.

Why does CRA restrict imports outside the src/ directory?

The restriction ensures that all application logic, assets, and components reside in a single, predictable location. This encapsulation prevents accidental dependencies on build configuration files, documentation, or server-side code that may exist at the repository root. It also simplifies future migrations, refactoring, and code splitting since the bundler can assume all source code lives under src/.

How do I import from a shared directory outside src/?

The recommended approach is to move the shared code into src/ or publish it as an npm package. If neither is feasible, you can whitelist specific files by adding them to the allowedFiles array in the ModuleScopePlugin constructor within webpack.config.js (requires ejecting or using a rewiring tool). Alternatively, create a symlink from node_modules/ pointing to the external directory, which bypasses the plugin's checks since node_modules imports are handled separately.

Does ModuleScopePlugin affect imports from node_modules?

No, ModuleScopePlugin does not block imports from node_modules. The plugin specifically checks if the requested file falls outside the configured appSrc directories, but it runs after Webpack's standard resolution for node_modules. Since packages in node_modules are resolved through a different mechanism and are considered external dependencies, they bypass the src/ boundary check entirely.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →