← Back to DevBytes

IntelliJ IDEA Extensions/Plugins: Complete Guide

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:

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:

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:

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.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles