> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/argoproj/argo-cd/llms.txt
> Use this file to discover all available pages before exploring further.

# UI Extensions

> Extend the Argo CD web interface with custom UI components

## Overview

Argo CD's web interface can be extended with custom UI elements written in JavaScript/React. Extensions are loaded from JavaScript files placed in the argocd-server Pods.

<Info>
  **Extension Types**

  Argo CD supports several extension points:

  * Resource tab extensions
  * Application tab extensions
  * System-level extensions
  * Status panel extensions
  * Top bar action menu extensions
  * App view extensions
</Info>

## Installation

Place extension JavaScript files in `/tmp/extensions` directory of argocd-server Pods:

```
/tmp/extensions
├── example1
│   └── extension-1.js
└── example2
    └── extension-2.js
```

**File naming requirement:** Extension files must match the regex `^extension(.*)\.js$`

<Tip>
  Mount extensions using a ConfigMap or build them into a custom argocd-server image.
</Tip>

## Extension Basics

### JavaScript Structure

Extensions register themselves using the `extensionsAPI` global variable:

```javascript theme={null}
((window) => {
  const component = () => {
    return React.createElement("div", {}, "Hello World");
  };
  
  window.extensionsAPI.registerResourceExtension(
    component,
    "*",
    "*",
    "My Extension"
  );
})(window);
```

### Using React

<Warning>
  **Don't Bundle React**

  Extensions must use the `React` global variable, not bundle React. Configure webpack:

  ```javascript theme={null}
  externals: {
    react: "React"
  }
  ```
</Warning>

## Resource Tab Extensions

Add custom tabs to the resource sliding panel on the Application details page.

### API

```typescript theme={null}
registerResourceExtension(
  component: ExtensionComponent,
  group: string,
  kind: string, 
  tabTitle: string,
  opts?: { icon?: string }
)
```

**Component receives props:**

* `application: Application` - Argo CD Application resource
* `resource: State` - The Kubernetes resource object
* `tree: ApplicationTree` - List of all resources in the application

### Example

```javascript theme={null}
((window) => {
  const component = (props) => {
    const { application, resource, tree } = props;
    
    return React.createElement("div", { style: { padding: "20px" } }, [
      React.createElement("h3", {}, "Resource Details"),
      React.createElement("p", {}, `Name: ${resource.name}`),
      React.createElement("p", {}, `Kind: ${resource.kind}`),
      React.createElement("p", {}, `App: ${application.metadata.name}`)
    ]);
  };
  
  window.extensionsAPI.registerResourceExtension(
    component,
    "apps",           // Group glob pattern
    "Deployment",     // Kind glob pattern  
    "Custom Info",    // Tab title
    { icon: "fa-info-circle" }
  );
})(window);
```

### Glob Patterns

Use glob expressions to match multiple resource types:

```javascript theme={null}
// Match all resources
window.extensionsAPI.registerResourceExtension(
  component,
  "**",    // All groups (including empty)
  "*",     // All kinds
  "Universal Tab"
);

// Match specific API group
window.extensionsAPI.registerResourceExtension(
  component,
  "apps",
  "*",
  "Apps Resources"
);
```

## Application Tab Extensions

Since Applications are Kubernetes resources, use `registerResourceExtension` with:

* **group:** `argoproj.io`
* **kind:** `Application`

```javascript theme={null}
window.extensionsAPI.registerResourceExtension(
  component,
  "argoproj.io",
  "Application",
  "App Insights"
);
```

## System-Level Extensions

Add new pages to the sidebar navigation.

### API

```typescript theme={null}
registerSystemLevelExtension(
  component: ExtensionComponent,
  title: string,
  path: string,
  icon?: string
)
```

### Example

```javascript theme={null}
((window) => {
  const component = () => {
    return React.createElement("div", { style: { padding: "20px" } }, [
      React.createElement("h1", {}, "Custom Dashboard"),
      React.createElement("p", {}, "Your custom content here")
    ]);
  };
  
  window.extensionsAPI.registerSystemLevelExtension(
    component,
    "Dashboard",      // Sidebar title
    "/dashboard",     // URL path
    "fa-chart-line"   // FontAwesome icon
  );
})(window);
```

## Status Panel Extensions

Add custom items to the status bar at the top of the Application view.

### API

```typescript theme={null}
registerStatusPanelExtension(
  component: StatusPanelExtensionComponent,
  title: string,
  id: string,
  flyout?: ExtensionComponent
)
```

### Basic Example

```javascript theme={null}
((window) => {
  const component = () => {
    return React.createElement(
      "div",
      { style: { padding: "10px" } },
      "Status: OK"
    );
  };
  
  window.extensionsAPI.registerStatusPanelExtension(
    component,
    "Health Status",
    "health_status"
  );
})(window);
```

### With Flyout Panel

```javascript theme={null}
((window) => {
  const flyout = (props) => {
    return React.createElement("div", { style: { padding: "20px" } }, [
      React.createElement("h3", {}, "Detailed Health"),
      React.createElement("p", {}, "All services operational")
    ]);
  };
  
  const component = (props) => {
    return React.createElement(
      "div",
      {
        style: { padding: "10px", cursor: "pointer" },
        onClick: () => props.openFlyout()
      },
      "View Details"
    );
  };
  
  window.extensionsAPI.registerStatusPanelExtension(
    component,
    "Health Details",
    "health_details",
    flyout
  );
})(window);
```

## Top Bar Action Menu Extensions

Add buttons to the action bar at the top of the Application view.

### API

```typescript theme={null}
registerTopBarActionMenuExt(
  component: TopBarActionMenuExtComponent,
  title: string,
  id: string,
  flyout?: ExtensionComponent,
  shouldDisplay?: (app?: Application) => boolean,
  iconClassName?: string,
  isMiddle?: boolean
)
```

### Conditional Display Example

```javascript theme={null}
((window) => {
  const shouldDisplay = (app) => {
    // Only show for production apps
    return app.metadata?.labels?.["environment"] === "production";
  };
  
  const flyout = () => {
    return React.createElement(
      "div",
      { style: { padding: "20px" } },
      "Production deployment controls"
    );
  };
  
  const component = () => {
    return React.createElement(
      "div",
      {},
      "Deploy to Prod"
    );
  };
  
  window.extensionsAPI.registerTopBarActionMenuExt(
    component,
    "Production Deploy",
    "prod_deploy",
    flyout,
    shouldDisplay,
    "fa-rocket",
    true  // Position in middle section
  );
})(window);
```

## App View Extensions

Create custom application detail views alongside Tree, Pods, and Network views.

### API

```typescript theme={null}
registerAppViewExtension(
  component: ExtensionComponent,
  title: string,
  icon: string,
  shouldDisplay?: (app: Application) => boolean
)
```

### Example

```javascript theme={null}
((window) => {
  const component = (props) => {
    const { application, tree } = props;
    
    return React.createElement("div", { style: { padding: "20px" } }, [
      React.createElement("h2", {}, "Custom View"),
      React.createElement("p", {}, `Resources: ${tree.nodes.length}`)
    ]);
  };
  
  const shouldDisplay = (app) => {
    // Only show for apps with specific annotation
    return app.metadata?.annotations?.["custom-view"] === "enabled";
  };
  
  window.extensionsAPI.registerAppViewExtension(
    component,
    "Custom View",
    "fa-cubes",
    shouldDisplay
  );
})(window);
```

## Development Tips

<AccordionGroup>
  <Accordion title="Local Development" icon="laptop-code">
    Mount extensions during local development:

    ```bash theme={null}
    kubectl create configmap extension-config \
      --from-file=extension.js=./my-extension.js \
      -n argocd

    # Patch argocd-server deployment
    kubectl patch deployment argocd-server -n argocd --type json \
      -p='[{
        "op": "add",
        "path": "/spec/template/spec/containers/0/volumeMounts/-",
        "value": {
          "name": "extensions",
          "mountPath": "/tmp/extensions/my-ext"
        }
      }]'
    ```
  </Accordion>

  <Accordion title="Debugging" icon="bug">
    Use browser DevTools:

    ```javascript theme={null}
    const component = (props) => {
      console.log("Extension props:", props);
      console.log("Application:", props.application);
      return React.createElement("div", {}, "Debug Extension");
    };
    ```
  </Accordion>

  <Accordion title="Error Handling" icon="triangle-exclamation">
    Wrap components with error boundaries:

    ```javascript theme={null}
    const component = (props) => {
      try {
        // Your component logic
        return React.createElement("div", {}, "Content");
      } catch (error) {
        console.error("Extension error:", error);
        return React.createElement(
          "div",
          { style: { color: "red" } },
          "Extension failed to load"
        );
      }
    };
    ```
  </Accordion>

  <Accordion title="Build Process" icon="hammer">
    Use webpack to bundle your extension:

    ```javascript theme={null}
    // webpack.config.js
    module.exports = {
      entry: './src/extension.jsx',
      output: {
        filename: 'extension.js',
        path: __dirname + '/dist'
      },
      externals: {
        react: 'React'
      },
      module: {
        rules: [
          {
            test: /\.jsx?$/,
            use: 'babel-loader',
            exclude: /node_modules/
          }
        ]
      }
    };
    ```
  </Accordion>
</AccordionGroup>

## TypeScript Support

For better type safety, define types for your extensions:

```typescript theme={null}
import { Application, State, ApplicationTree } from './argocd-types';

interface ResourceExtensionProps {
  application: Application;
  resource: State;
  tree: ApplicationTree;
}

const MyExtension: React.FC<ResourceExtensionProps> = (props) => {
  const { application, resource, tree } = props;
  return <div>Typed Extension</div>;
};

declare global {
  interface Window {
    extensionsAPI: {
      registerResourceExtension: (
        component: React.ComponentType<ResourceExtensionProps>,
        group: string,
        kind: string,
        title: string,
        opts?: { icon?: string }
      ) => void;
      // ... other methods
    };
  }
}

window.extensionsAPI.registerResourceExtension(
  MyExtension,
  "apps",
  "Deployment",
  "My Extension"
);
```

## Best Practices

<CardGroup cols={2}>
  <Card title="Keep It Simple" icon="minimize">
    Extensions should be focused and lightweight. Complex logic should call external services.
  </Card>

  <Card title="Handle Loading States" icon="spinner">
    Show loading indicators for async operations to improve UX.
  </Card>

  <Card title="Error Boundaries" icon="shield">
    Implement error handling to prevent extension failures from breaking the UI.
  </Card>

  <Card title="Responsive Design" icon="mobile">
    Ensure extensions work well on different screen sizes.
  </Card>
</CardGroup>

## Security Considerations

<Warning>
  **Extension Security**

  * Extensions run with full access to the application context
  * Only install extensions from trusted sources
  * Sanitize any user input before displaying
  * Don't expose sensitive data in extensions
  * Review extension code before deployment
</Warning>

## Example: Complete Extension

```javascript theme={null}
((window) => {
  // Helper to safely access nested properties
  const get = (obj, path, defaultValue) => {
    const keys = path.split('.');
    let result = obj;
    for (const key of keys) {
      if (result && typeof result === 'object' && key in result) {
        result = result[key];
      } else {
        return defaultValue;
      }
    }
    return result;
  };

  // Main component
  const ResourceMetricsExtension = (props) => {
    const { application, resource } = props;
    const [metrics, setMetrics] = React.useState(null);
    const [loading, setLoading] = React.useState(true);

    React.useEffect(() => {
      // Fetch metrics from your monitoring system
      fetch(`/api/metrics/${resource.name}`)
        .then(res => res.json())
        .then(data => {
          setMetrics(data);
          setLoading(false);
        })
        .catch(err => {
          console.error('Failed to load metrics:', err);
          setLoading(false);
        });
    }, [resource.name]);

    if (loading) {
      return React.createElement('div', 
        { style: { padding: '20px', textAlign: 'center' } },
        'Loading metrics...'
      );
    }

    if (!metrics) {
      return React.createElement('div',
        { style: { padding: '20px', color: 'red' } },
        'Failed to load metrics'
      );
    }

    return React.createElement('div', { style: { padding: '20px' } }, [
      React.createElement('h3', { key: 'title' }, 'Resource Metrics'),
      React.createElement('p', { key: 'cpu' }, `CPU: ${metrics.cpu}`),
      React.createElement('p', { key: 'memory' }, `Memory: ${metrics.memory}`),
    ]);
  };

  // Register the extension
  window.extensionsAPI.registerResourceExtension(
    ResourceMetricsExtension,
    'apps',
    'Deployment',
    'Metrics',
    { icon: 'fa-chart-bar' }
  );
})(window);
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Resource Actions" icon="bolt" href="/developers/resource-actions">
    Add custom actions to resources
  </Card>

  <Card title="Custom Health Checks" icon="heart-pulse" href="/developers/custom-health-checks">
    Define custom health checks
  </Card>

  <Card title="React Documentation" icon="react" href="https://react.dev/">
    Learn React fundamentals
  </Card>

  <Card title="Argo CD API" icon="code" href="https://github.com/argoproj/argo-cd/blob/master/ui/src/app/shared/models.ts">
    View API type definitions
  </Card>
</CardGroup>
