Appearance
A module extension represents framework- or language-specific configuration associated with a module. Unlike facets in IntelliJ IDEA, Consulo uses module extensions as a unified mechanism for attaching per-module settings such as SDK references, language levels, compiler options, and more.
A module can have multiple module extensions enabled simultaneously. For example, a module might have both a Java module extension (configuring language level and bytecode version) and a Spring module extension.
Architecture
The module extension system follows an immutable/mutable pattern and uses the Consulo annotation-based registration:
| Class / Interface | Role |
|---|---|
ModuleExtensionProvider | Factory that creates extension instances. Annotated with @ExtensionAPI(ComponentScope.APPLICATION). |
ModuleExtension | Read-only extension interface. Extends PersistentStateComponent<Element> for serialization. |
MutableModuleExtension | Editable variant used in the Project Structure UI. Adds setEnabled(), isModified(), and createConfigurationComponent(). |
ModuleExtensionBase | Base implementation with built-in enabled/disabled state and XML persistence. |
ModuleExtensionWithSdkBase | Extends ModuleExtensionBase with SDK pointer support for extensions that reference an SDK. |
ModuleExtensionHelper | Project-scoped service (@ServiceAPI(ComponentScope.PROJECT)) for querying enabled extensions across all modules. |
How It Works
ModuleExtensionProvideris annotated with@ExtensionAPI(ComponentScope.APPLICATION)-- it is the extension point.- Plugin authors implement
ModuleExtensionProviderand annotate their implementation with@ExtensionImpl. - When a module root layer is created, the platform iterates all registered
ModuleExtensionProviderinstances and callscreateImmutableExtension()orcreateMutableExtension()to instantiate extensions. - Each extension stores its state via
PersistentStateComponent<Element>-- serialized into the module's.consulodirectory. - In the Project Structure dialog,
MutableModuleExtension.createConfigurationComponent()provides the per-extension settings UI.
Creating a Module Extension
A typical module extension consists of four classes:
1. The Immutable Extension
Extend ModuleExtensionBase (or ModuleExtensionWithSdkBase if your extension references an SDK):
java
public class MyModuleExtension extends ModuleExtensionBase<MyModuleExtension> {
public MyModuleExtension(@Nonnull String id, @Nonnull ModuleRootLayer moduleRootLayer) {
super(id, moduleRootLayer);
}
@RequiredReadAction
@Override
public void commit(@Nonnull MyModuleExtension mutableModuleExtension) {
super.commit(mutableModuleExtension);
// copy additional fields from the mutable extension
}
@Override
protected void getStateImpl(@Nonnull Element element) {
// write custom state to XML element
}
@RequiredReadAction
@Override
protected void loadStateImpl(@Nonnull Element element) {
// read custom state from XML element
}
}Key points:
- The constructor receives the extension
idand theModuleRootLayerit belongs to. commit()copies state from a mutable extension into this instance when the user applies changes.getStateImpl()/loadStateImpl()handle XML serialization. The base class already handles theenabledflag and theidattribute.
If your extension needs an SDK reference, extend ModuleExtensionWithSdkBase instead and implement getSdkTypeClass():
java
public class MyModuleExtension extends ModuleExtensionWithSdkBase<MyModuleExtension> {
public MyModuleExtension(@Nonnull String id, @Nonnull ModuleRootLayer moduleRootLayer) {
super(id, moduleRootLayer);
}
@Nonnull
@Override
public Class<? extends SdkType> getSdkTypeClass() {
return MySdkType.class;
}
}2. The Mutable Extension
Extend your immutable extension and implement MutableModuleExtension (or MutableModuleExtensionWithSdk for SDK-based extensions):
java
public class MyMutableModuleExtension extends MyModuleExtension
implements MutableModuleExtension<MyModuleExtension> {
public MyMutableModuleExtension(@Nonnull String id, @Nonnull ModuleRootLayer moduleRootLayer) {
super(id, moduleRootLayer);
}
@Override
public void setEnabled(boolean val) {
myIsEnabled = val;
}
@Override
public boolean isModified(@Nonnull MyModuleExtension originalExtension) {
return myIsEnabled != originalExtension.isEnabled();
}
@RequiredUIAccess
@Nullable
@Override
public Component createConfigurationComponent(
@Nonnull Disposable uiDisposable, @Nonnull Runnable updateOnCheck) {
// build and return the settings UI
return VerticalLayout.create();
}
}Key points:
setEnabled()toggles the extension on/off in the UI.isModified()checks whether the mutable state differs from the original (persisted) state.createConfigurationComponent()returns the UI panel shown in the Project Structure > Modules dialog when the extension is selected.
3. The Extension Provider
Implement ModuleExtensionProvider and annotate with @ExtensionImpl:
java
@ExtensionImpl
public class MyModuleExtensionProvider
implements ModuleExtensionProvider<MyModuleExtension> {
@Nonnull
@Override
public String getId() {
return "my-framework";
}
@Nonnull
@Override
public LocalizeValue getName() {
return LocalizeValue.localizeTODO("My Framework");
}
@Nonnull
@Override
public Image getIcon() {
return MyIcons.FrameworkIcon;
}
@Nonnull
@Override
public ModuleExtension<MyModuleExtension> createImmutableExtension(
@Nonnull ModuleRootLayer layer) {
return new MyModuleExtension(getId(), layer);
}
@Nonnull
@Override
public MutableModuleExtension<MyModuleExtension> createMutableExtension(
@Nonnull ModuleRootLayer layer) {
return new MyMutableModuleExtension(getId(), layer);
}
}ModuleExtensionProvider is annotated with @ExtensionAPI(ComponentScope.APPLICATION) -- your implementation only needs @ExtensionImpl.
Provider Methods
| Method | Description |
|---|---|
getId() | Unique string identifier for this extension (e.g., "java", "spring"). |
getParentId() | Optional parent extension ID. Returns null by default. Used for hierarchical extensions. |
getName() | Human-readable display name shown in the UI. |
getIcon() | Icon displayed alongside the extension name. |
isAllowMixin() | Whether the extension can coexist with overlapping extensions. Default false. |
isSystemOnly() | Whether the extension is system-internal and hidden from the user. Default false. |
createImmutableExtension() | Factory method for the read-only extension instance. |
createMutableExtension() | Factory method for the editable extension instance. |
Accessing Module Extensions
From a Module
Use ModuleUtilCore.getExtension() or the ModuleRootLayer API:
java
// By class
MyModuleExtension ext = ModuleUtilCore.getExtension(module, MyModuleExtension.class);
// By ID
ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
MyModuleExtension ext = rootManager.getExtension("my-framework");Across All Modules
Use ModuleExtensionHelper, a @ServiceAPI(ComponentScope.PROJECT) service:
java
ModuleExtensionHelper helper = ModuleExtensionHelper.getInstance(project);
// Check if any module has the extension enabled
boolean hasIt = helper.hasModuleExtension(MyModuleExtension.class);
// Get all enabled instances across modules
Collection<MyModuleExtension> all = helper.getModuleExtensions(MyModuleExtension.class);From a ModuleRootLayer
ModuleRootLayer provides direct access:
java
// By class (returns null if not enabled)
MyModuleExtension ext = layer.getExtension(MyModuleExtension.class);
// By ID (returns null if not enabled)
MyModuleExtension ext = layer.getExtension("my-framework");
// By class, ignoring enabled state
MyModuleExtension ext = layer.getExtensionWithoutCheck(MyModuleExtension.class);
// All extensions (enabled only)
List<ModuleExtension> extensions = layer.getExtensions();Real-World Example: Java Module Extension
The Java language plugin in consulo-java provides a full module extension implementation:
Provider -- JavaModuleExtensionProvider is annotated with @ExtensionImpl and registers the "java" extension ID. It creates JavaModuleExtensionImpl (immutable) and JavaMutableModuleExtensionImpl (mutable) instances.
Extension interface -- JavaModuleExtension extends ModuleExtensionWithSdk and adds:
getLanguageLevel()-- the effective Java language level (user-set or derived from SDK)getBytecodeVersion()-- the target bytecode versiongetCompilerArguments()-- additional compiler flagsgetSpecialDirLocation()-- whether special directories are relative to module root or source rootgetCompilationClasspath()/getCompilationBootClasspath()-- resolved classpaths for compilation
Immutable implementation -- JavaModuleExtensionImpl extends ModuleExtensionWithSdkBase and persists language level, bytecode version, compiler arguments, and special directory location via getStateImpl() / loadStateImpl().
Mutable implementation -- JavaMutableModuleExtensionImpl extends JavaModuleExtensionImpl, implements JavaMutableModuleExtension, and provides a Swing-based settings panel (JavaModuleExtensionPanel) for configuring the SDK, language level, bytecode version, and compiler arguments.
Extension Hierarchy
Module extensions support a parent-child hierarchy via getParentId(). A child extension appears nested under its parent in the Project Structure UI. For example, a "Java EE" extension might return "java" from getParentId() to appear as a sub-extension of the Java extension.
SDK-Based Extensions
For extensions that associate an SDK with a module, use the SDK-aware base classes:
- Extend
ModuleExtensionWithSdkBasefor the immutable extension - Implement
MutableModuleExtensionWithSdkfor the mutable extension
These base classes manage an ModuleInheritableNamedPointer<Sdk> that supports inheriting the SDK from another module, and automatically persist the SDK reference.