Skip to main content

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.
Extension TypesArgo 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

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$
Mount extensions using a ConfigMap or build them into a custom argocd-server image.

Extension Basics

JavaScript Structure

Extensions register themselves using the extensionsAPI global variable:
((window) => {
  const component = () => {
    return React.createElement("div", {}, "Hello World");
  };
  
  window.extensionsAPI.registerResourceExtension(
    component,
    "*",
    "*",
    "My Extension"
  );
})(window);

Using React

Don’t Bundle ReactExtensions must use the React global variable, not bundle React. Configure webpack:
externals: {
  react: "React"
}

Resource Tab Extensions

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

API

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

((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:
// 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
window.extensionsAPI.registerResourceExtension(
  component,
  "argoproj.io",
  "Application",
  "App Insights"
);

System-Level Extensions

Add new pages to the sidebar navigation.

API

registerSystemLevelExtension(
  component: ExtensionComponent,
  title: string,
  path: string,
  icon?: string
)

Example

((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

registerStatusPanelExtension(
  component: StatusPanelExtensionComponent,
  title: string,
  id: string,
  flyout?: ExtensionComponent
)

Basic Example

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

With Flyout Panel

((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

registerTopBarActionMenuExt(
  component: TopBarActionMenuExtComponent,
  title: string,
  id: string,
  flyout?: ExtensionComponent,
  shouldDisplay?: (app?: Application) => boolean,
  iconClassName?: string,
  isMiddle?: boolean
)

Conditional Display Example

((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

registerAppViewExtension(
  component: ExtensionComponent,
  title: string,
  icon: string,
  shouldDisplay?: (app: Application) => boolean
)

Example

((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

Mount extensions during local development:
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"
    }
  }]'
Use browser DevTools:
const component = (props) => {
  console.log("Extension props:", props);
  console.log("Application:", props.application);
  return React.createElement("div", {}, "Debug Extension");
};
Wrap components with error boundaries:
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"
    );
  }
};
Use webpack to bundle your extension:
// 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/
      }
    ]
  }
};

TypeScript Support

For better type safety, define types for your extensions:
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

Keep It Simple

Extensions should be focused and lightweight. Complex logic should call external services.

Handle Loading States

Show loading indicators for async operations to improve UX.

Error Boundaries

Implement error handling to prevent extension failures from breaking the UI.

Responsive Design

Ensure extensions work well on different screen sizes.

Security Considerations

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

Example: Complete Extension

((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

Resource Actions

Add custom actions to resources

Custom Health Checks

Define custom health checks

React Documentation

Learn React fundamentals

Argo CD API

View API type definitions