What Are IntelliJ IDEA Extensions and Plugins?
IntelliJ IDEA extensions—more commonly called plugins—are modular software components that add new features, integrate external tools, or enhance existing functionality within the IntelliJ Platform. The platform powers not only IntelliJ IDEA but also JetBrains IDEs like PyCharm, WebStorm, PhpStorm, Rider, and DataGrip. Plugins can range from simple cosmetic changes (themes and icon packs) to deep integrations that provide support for entire programming languages, frameworks, and build systems.
Technically, a plugin is a JAR archive containing compiled Java/Kotlin classes, resource files, and a mandatory plugin.xml descriptor that declares extension points, actions, services, and other metadata. The IntelliJ Platform uses a sophisticated extension-point system that allows plugins to hook into nearly every aspect of the IDE: the editor, project tree, tool windows, settings panels, indexing infrastructure, debugger, and more.
Extension Points vs. Plugins
The IntelliJ Platform is built on a component framework where extension points act as sockets that plugins can plug into. A single plugin can implement dozens of extension points—for example, registering a new language parser, providing code completion contributors, adding inspection profiles, or contributing menu actions. This architecture makes the IDE extremely extensible without modifying core source code.
Why Plugins Matter for Developers
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Plugins transform IntelliJ IDEA from a generic code editor into a specialized workbench tailored to your exact stack and workflow. Here are the key reasons they are indispensable:
- Language and Framework Support: Official JetBrains plugins provide first-class support for Go, Rust, Dart, Python, Ruby, and dozens of other languages. Framework-specific plugins (Spring, Quarkus, Micronaut, React, Angular) offer deep code insight, template generation, and refactoring capabilities that would otherwise be impossible.
- Productivity Boosters: Plugins like Key Promoter X, Tabnine, GitHub Copilot, and Code Glance accelerate common tasks, predict your next action, and give you a high-level view of your codebase.
- Tool Integration: Docker, Kubernetes, database explorers, API clients (like the built-in HTTP Client), and VCS enhancements bring external systems directly into the IDE, eliminating context switching.
- Custom Workflow Automation: Team-specific plugins can enforce coding standards, automate repetitive refactorings, generate boilerplate code, and integrate with internal CI/CD pipelines.
- Visual Customization: Themes, icon packs, and font ligature plugins make long coding sessions more comfortable and visually appealing.
How to Use Plugins: Discovery, Installation, and Management
Browsing the JetBrains Marketplace
The primary source for plugins is the JetBrains Marketplace at https://plugins.jetbrains.com. You can browse by category (Editor, VCS Integration, Frameworks, etc.), search by keyword, and filter by rating, number of downloads, or compatibility with your IDE version. Each plugin page shows a description, changelog, compatibility matrix, and user reviews.
Installing Plugins from Within the IDE
The most common installation method is through the built-in plugin manager. Open Settings/Preferences (Ctrl+Alt+S on Windows/Linux, Cmd+, on macOS), navigate to Plugins, and use the Marketplace tab. Search for the desired plugin, click Install, and restart the IDE when prompted.
You can also install plugins manually by downloading the ZIP or JAR file from the Marketplace website and using the gear icon menu in the Plugins settings to select Install Plugin from Disk....
Managing Installed Plugins
The Installed tab lists all currently loaded plugins. You can disable, enable, update, or uninstall plugins from this view. Disabling a plugin without uninstalling lets you quickly test whether a particular plugin is causing performance issues or conflicts. The Plugin Homepage link opens the Marketplace entry for documentation and support.
JetBrains also supports Required Plugins—if a plugin declares dependencies on other plugins, those will be automatically resolved and installed. This is common for framework plugins that build upon language plugins.
Command-Line Plugin Management
For automated environments or team standardization, you can manage plugins via the plugincop command-line tool or by placing plugin JARs directly in the IDE's configuration directory. The directory structure is:
# IDE config directory (varies by OS and IDE version)
# Example: ~/.local/share/JetBrains/IntelliJIdea2024.1/
# Place plugin JARs or extracted folders under:
<config>/plugins/<plugin-name>/lib/
How to Develop Your Own IntelliJ Plugin
Developing a plugin gives you full control over the IDE experience. The following sections walk through the complete process, from project setup to publishing.
Step 1: Set Up the Gradle-Based Plugin Project
JetBrains strongly recommends using the IntelliJ Platform Gradle Plugin (the org.jetbrains.intellij Gradle plugin) for project scaffolding, dependency management, and IDE launch tasks. Create a new project using the IntelliJ Platform Plugin Template or manually set up your build.gradle.kts:
// build.gradle.kts
plugins {
id("org.jetbrains.intellij") version "1.17.4"
id("org.jetbrains.kotlin.jvm") version "1.9.23"
}
group = "com.example"
version = "1.0.0"
repositories {
mavenCentral()
}
intellij {
version.set("2024.1")
type.set("IC") // IC = IntelliJ Community Edition, IU = Ultimate
plugins.set(listOf("com.intellij.java"))
}
tasks {
patchPluginXml {
sinceBuild.set("241")
untilBuild.set("241.*")
}
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
}
The intellij block specifies the target IDE version and type. Setting type to "IC" means your plugin will be compatible with Community Edition; "IU" requires Ultimate Edition APIs. The plugins list declares which bundled or external plugins your plugin depends on.
Step 2: The plugin.xml Descriptor
Every plugin must have a plugin.xml file located under src/main/resources/META-INF/. This XML file declares the plugin identity, dependencies, extension points implemented, and the application/service components:
<!-- src/main/resources/META-INF/plugin.xml -->
<idea-plugin>
<id>com.example.my-awesome-plugin</id>
<name>My Awesome Plugin</name>
<version>1.0.0</version>
<vendor email="support@example.com">Example Inc.</vendor>
<description><![CDATA[
Provides incredible productivity boosts for developers.
<ul>
<li>Feature A</li>
<li>Feature B</li>
</ul>
]]></description>
<change-notes><![CDATA[
Initial release with core features.
]]></change-notes>
<depends>com.intellij.modules.platform</depends>
<depends>com.intellij.java</depends>
<extensions defaultExtensionNs="com.intellij">
<toolWindow id="MyToolWindow"
anchor="right"
factoryClass="com.example.MyToolWindowFactory"
icon="com.example.MyIcons.MY_ICON"/>
<applicationService
serviceImplementation="com.example.MyApplicationService"/>
<projectService
serviceImplementation="com.example.MyProjectService"/>
<inspectionToolProvider
implementation="com.example.MyInspectionProvider"/>
</extensions>
<actions>
<action id="com.example.MyAction"
class="com.example.MyAction"
text="My Custom Action"
description="Performs a custom operation">
<add-to-group group-id="EditorPopupMenu" anchor="last"/>
<keyboard-shortcut keymap="$default"
first-keystroke="ctrl alt shift m"/>
</action>
</actions>
</idea-plugin>
Key elements explained:
<id>— A globally unique identifier; never change it after publishing.<depends>— Declares required platform modules or other plugins.<extensions>— Implements extension points: tool windows, services, inspections, completion contributors, etc.<actions>— Registers menu/toolbar actions with grouping and optional keyboard shortcuts.<applicationService>— Singleton service scoped to the entire IDE instance.<projectService>— Service scoped to a single open project.
Step 3: Implementing an Action
Actions are the most common extension—they appear as menu items, toolbar buttons, or respond to keyboard shortcuts. Here's a complete action implementation in Kotlin:
// src/main/kotlin/com/example/MyAction.kt
package com.example
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.editor.Editor
class MyAction : AnAction() {
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
val editor = e.getData(CommonDataKeys.EDITOR)
val selectedText = editor?.selectionModel?.selectedText ?: ""
if (selectedText.isNotEmpty()) {
val transformedText = selectedText.uppercase()
// Replace selection in-place using a write action
com.intellij.openapi.command.WriteCommandAction.runWriteAction(project) {
editor.document.replaceString(
editor.selectionModel.selectionStart,
editor.selectionModel.selectionEnd,
transformedText
)
}
Messages.showInfoMessage(
project,
"Transformed ${selectedText.length} characters to uppercase.",
"Action Complete"
)
} else {
Messages.showWarningDialog(
project,
"Please select some text first.",
"No Selection"
)
}
}
override fun update(e: AnActionEvent) {
val editor = e.getData(CommonDataKeys.EDITOR)
val hasSelection = editor?.selectionModel?.hasSelection() == true
e.presentation.isEnabledAndVisible = hasSelection
e.presentation.text = "Uppercase Selection"
}
}
The update() method controls visibility and enablement—it runs frequently, so keep it fast. The actionPerformed() method executes the actual logic. Always use WriteCommandAction or CommandProcessor when modifying PSI or document content so the change appears in the undo stack.
Step 4: Creating a Tool Window
Tool windows are dockable panels that provide persistent UI for your plugin. To create one, implement the ToolWindowFactory interface and register it in plugin.xml:
// src/main/kotlin/com/example/MyToolWindowFactory.kt
package com.example
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowFactory
import com.intellij.ui.content.ContentFactory
import javax.swing.JLabel
import javax.swing.JPanel
import javax.swing.JTextArea
import java.awt.BorderLayout
class MyToolWindowFactory : ToolWindowFactory {
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
val panel = JPanel(BorderLayout())
val header = JLabel("Plugin Output", JLabel.CENTER)
val textArea = JTextArea(10, 40).apply {
isEditable = false
text = "Welcome to My Awesome Plugin!\nReady for action."
}
panel.add(header, BorderLayout.NORTH)
panel.add(textArea, BorderLayout.CENTER)
val content = ContentFactory.getInstance().createContent(panel, "", false)
toolWindow.contentManager.addContent(content)
}
override fun shouldBeAvailable(project: Project): Boolean = true
}
Step 5: Defining Application and Project Services
Services are the recommended way to manage persistent state. Application services are IDE-wide singletons; project services are scoped to each open project. Here's a project service with persistent state using PersistentStateComponent:
// src/main/kotlin/com/example/MyProjectService.kt
package com.example
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.project.Project
import com.intellij.util.xmlb.XmlSerializerUtil
@State(
name = "MyProjectSettings",
storages = [Storage("myAwesomePlugin.xml")]
)
class MyProjectService : PersistentStateComponent<MyProjectService.State> {
data class State(
var enabledFeatureA: Boolean = true,
var customSetting: String = "default"
)
private var myState = State()
override fun getState(): State = myState
override fun loadState(state: State) {
XmlSerializerUtil.copyBean(state, myState)
}
companion object {
fun getInstance(project: Project): MyProjectService =
project.getService(MyProjectService::class.java)
}
fun isFeatureAEnabled(): Boolean = myState.enabledFeatureA
fun setFeatureAEnabled(value: Boolean) {
myState.enabledFeatureA = value
}
}
The @Storage annotation specifies the XML file where state is persisted (inside the project's .idea directory or IDE config). The service is automatically loaded and saved by the platform.
Step 6: Contributing Code Inspections
Inspections analyze code and highlight potential problems. Here's a simple inspection that flags occurrences of the word "TODO" as warnings:
// src/main/kotlin/com/example/TodoHighlightInspection.kt
package com.example
import com.intellij.codeInspection.LocalInspectionTool
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiComment
import com.intellij.codeInspection.ProblemHighlightType
class TodoHighlightInspection : LocalInspectionTool() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : PsiElementVisitor() {
override fun visitComment(comment: PsiComment) {
val text = comment.text.lowercase()
if (text.contains("todo")) {
holder.registerProblem(
comment,
"TODO comment detected—consider tracking this task",
ProblemHighlightType.WARNING
)
}
}
}
}
}
Register this inspection in plugin.xml using <localInspection> inside <extensions>:
<localInspection
language="JAVA"
displayName="TODO Comment Flag"
groupName="My Plugin"
implementationClass="com.example.TodoHighlightInspection"/>
Step 7: Testing Your Plugin
The IntelliJ Platform Gradle Plugin provides several tasks for testing. For unit tests, use the test task with the IntelliJ test framework. For integration testing, you launch a sandbox IDE instance:
# Launch a sandbox IDE with your plugin loaded
gradle runIde
# Run tests with a headless IDE instance
gradle test
# Verify plugin structure and compatibility
gradle verifyPlugin
For writing tests, extend LightPlatformTestCase or use the com.intellij.testFramework package. Here's a basic action test:
// src/test/kotlin/com/example/MyActionTest.kt
package com.example
import com.intellij.testFramework.fixtures.BasePlatformTestCase
class MyActionTest : BasePlatformTestCase() {
fun testActionWithSelection() {
myFixture.configureByText("TestFile.txt", "hello world")
myFixture.editor.selectionModel.setSelection(0, 5)
myFixture.performById("com.example.MyAction")
val result = myFixture.editor.document.text
assertEquals("HELLO world", result)
}
}
Step 8: Publishing to the JetBrains Marketplace
Once your plugin is stable, publish it so others can benefit. The Gradle plugin automates publishing:
// Add to build.gradle.kts
publishPlugin {
token.set(System.getenv("PUBLISH_TOKEN"))
channels.set(listOf("stable"))
}
Steps to publish:
- Create an account on
https://plugins.jetbrains.comand generate a permanent token. - Add the token as an environment variable
PUBLISH_TOKEN. - Run
gradle publishPluginto upload the plugin. - Optionally use
gradle patchChangelogto generate changelogs from commit history. - After publishing, the plugin goes through an automated review; you'll receive feedback within 1–3 business days.
Best Practices for Plugin Development
1. Target the Right API Level
Always specify sinceBuild and untilBuild in plugin.xml or via the Gradle patchPluginXml task. This prevents your plugin from being loaded in incompatible IDE versions. Use the lowest possible sinceBuild that supports your required APIs to maximize audience.
2. Prefer Services Over Static State
Use application services (@Service annotation or <applicationService> in XML) and project services (<projectService>) for state management. These integrate with the platform's persistence framework, support PersistentStateComponent, and are testable. Avoid static singletons—they leak across projects and IDE restarts.
3. Respect the UI Thread
All UI interactions and PSI/document modifications must happen on the EDT (Event Dispatch Thread). Use ApplicationManager.getApplication().invokeLater() to schedule UI work, and ApplicationManager.getApplication().executeOnPooledThread() for background computation. For long-running tasks, use Task.Backgroundable to show a progress indicator.
4. Write Defensive PSI Code
The Program Structure Interface (PSI) represents the syntactic tree of your code. Always check for nulls when traversing PSI elements, use PsiElementVisitor patterns, and avoid holding stale PSI references after structural modifications. Use PsiDocumentManager to commit document changes before querying PSI.
5. Keep plugin.xml Clean and Versioned
The plugin.xml descriptor is the single source of truth for your plugin's contract with the IDE. Do not auto-generate it during build; maintain it manually. Increment the plugin version semantically. Use <![CDATA[ ]]> for HTML descriptions.
6. Minimize Dependencies
Each <depends> entry restricts your plugin's compatibility. Only depend on modules you truly need. If you depend on com.intellij.java, your plugin won't load in IDEs without Java support (like PyCharm or WebStorm). Consider using optional dependencies or runtime checks with PluginManager for cross-IDE functionality.
7. Test in Multiple Sandbox Environments
Use the Gradle runIde task with different IDE type/version configurations to verify compatibility. The verifyPlugin task checks structural correctness, but only manual testing reveals UI glitches, performance issues, and interaction problems with other popular plugins.
8. Provide Meaningful Documentation
The plugin description in plugin.xml and on the Marketplace page should clearly explain what the plugin does, show screenshots or GIFs of key features, and include a changelog. A dedicated documentation page or README with usage examples dramatically increases adoption.
Conclusion
IntelliJ IDEA plugins represent one of the most powerful extensibility frameworks in modern developer tooling. Whether you're consuming plugins to supercharge your daily workflow or building your own to codify team conventions and integrate proprietary systems, the IntelliJ Platform provides a mature, well-documented API surface that scales from simple menu actions to full-blown language support. By understanding the plugin lifecycle—from plugin.xml declaration through service management, action implementation, and Marketplace publishing—you gain the ability to mold the IDE into exactly the tool your development context demands. Start small with a single action or inspection, test thoroughly in sandbox environments, and iterate based on real usage feedback. The ecosystem thrives on community contributions, and your plugin could become the next indispensable tool for thousands of developers worldwide.