DEV Community

Cover image for 🧠✨ Extending OpenSearch Dashboards: A Complete Plugin Guide
João Victor
João Victor

Posted on

🧠✨ Extending OpenSearch Dashboards: A Complete Plugin Guide

Table of Contents

  1. Introduction
  2. Prerequisites
  3. Understanding the Architecture
  4. Step-by-Step Plugin Creation
  5. Advanced Features
  6. Testing and Debugging
  7. Deployment
  8. Troubleshooting
  9. Resources

Introduction

What is an OpenSearch Dashboards Plugin?

An OpenSearch Dashboards plugin is a modular extension that adds custom functionality to your OpenSearch Dashboards installation. Plugins can:

  • Create new visualizations and dashboards
  • Add custom data sources
  • Implement specialized search interfaces
  • Integrate external services
  • Extend existing functionality

Why Create a Plugin?

Create a plugin when you need to:

  • Customize the user interface beyond standard configurations
  • Add business-specific features not available in core OpenSearch
  • Integrate third-party services or APIs
  • Create reusable components across multiple dashboards
  • Implement specialized data processing or analytics

About This Guide

For more insights and to explore my other repositories or access this post in Portuguese, be sure to visit my GitHub profile at my GitHub.


Prerequisites

Before you begin, ensure you have:

Required Software

Software Minimum Version Purpose
Node.js 14.x or higher Runtime environment
Yarn 1.22.x Package manager
Git 2.x Version control
Code Editor Any VS Code recommended
  • Note: minimum version requirements may vary depending on the OpenSearch Dashboards version in use. Always check the official documentation for the target release to ensure full compatibility of the development environment. The example above is based on OpenSearch Dashboards version 2.11.

Required Knowledge

  • JavaScript/TypeScript: Basic to intermediate level
  • React: Understanding of components and hooks
  • REST APIs: How to create and consume APIs
  • Command Line: Basic terminal/command prompt usage

Verify Your Setup

Run these commands to verify your environment:

# Check Node.js version
node --version
# Should output: v14.x.x or higher

# Check Yarn version
yarn --version
# Should output: 1.22.x or higher

# Check Git version
git --version
# Should output: 2.x.x or higher
Enter fullscreen mode Exit fullscreen mode

Understanding the Architecture

Plugin Structure Overview

Before diving into code, let's understand how OpenSearch Dashboards plugins are organized:

my-plugin-name/
│
├── opensearch_dashboards.json    # Plugin metadata and dependencies
├── package.json                   # NPM package configuration
├── tsconfig.json                  # TypeScript configuration
│
├── public/                        # Client-side code (runs in browser)
│   ├── index.ts                   # Public plugin entry point
│   ├── plugin.ts                  # Main plugin class
│   ├── application.tsx            # React app initialization
│   ├── components/                # React components
│   │   └── app.tsx               # Main app component
│   └── types.ts                   # TypeScript type definitions
│
└── server/                        # Server-side code (runs on backend)
    ├── index.ts                   # Server plugin entry point
    ├── plugin.ts                  # Server plugin class
    ├── routes/                    # API route definitions
    └── types.ts                   # Server-side types
Enter fullscreen mode Exit fullscreen mode

Key Concepts

1. Plugin Lifecycle

Plugins follow a specific lifecycle:

  1. Initialization: Plugin is loaded and initialized
  2. Setup: Dependencies are configured, routes registered
  3. Start: Plugin becomes active and functional
  4. Stop: Cleanup when plugin is disabled

2. Client-Server Architecture

  • Public (Client): Handles UI, user interactions, browser-side logic
  • Server: Manages API endpoints, data processing, OpenSearch communication

3. Core Services

OpenSearch Dashboards provides core services:

  • HTTP: Create API routes and make requests
  • Notifications: Display toast messages and alerts
  • SavedObjects: Store and retrieve plugin data
  • Application: Register and navigate between apps

Step-by-Step Plugin Creation

Step 1: Set Up Your Development Environment

1.1 Clone OpenSearch Dashboards Repository

# Clone the repository
git clone https://github.com/opensearch-project/OpenSearch-Dashboards.git

# Navigate into the directory
cd OpenSearch-Dashboards

# Checkout the version you're targeting (e.g., 2.11)
git checkout 2.11
Enter fullscreen mode Exit fullscreen mode

Why? You need the OpenSearch Dashboards source code to develop and test plugins locally.

1.2 Install Dependencies

# Install all dependencies (this may take 10-15 minutes)
yarn osd bootstrap
Enter fullscreen mode Exit fullscreen mode

What does this do?

  • Installs all required packages
  • Links dependencies between plugins
  • Prepares the development environment

⏱️ Grab a coffee! This process takes time but only needs to be done once.


Step 2: Generate Your Plugin

2.1 Use the Plugin Generator

# Run the generator from the OpenSearch Dashboards root directory
node scripts/generate_plugin.js my-custom-plugin

# When prompted, answer:
# - Generate a plugin in ./plugins? Yes
# - Would you like to create the plugin in a different folder? No
# - Should your plugin have server-side code? Yes
# - Should your plugin have a UI component? Yes
Enter fullscreen mode Exit fullscreen mode

What just happened?

  • A complete plugin scaffold was created in plugins/my-custom-plugin
  • All necessary files and folders were generated
  • Basic TypeScript configurations were set up

2.2 Navigate to Your Plugin

cd plugins/my-custom-plugin
Enter fullscreen mode Exit fullscreen mode

Step 3: Configure Plugin Metadata

3.1 Understanding opensearch_dashboards.json

Open opensearch_dashboards.json and update it:

{
  "id": "myCustomPlugin",
  "version": "1.0.0",
  "opensearchDashboardsVersion": "2.11.0",
  "server": true,
  "ui": true,
  "requiredPlugins": ["data"],
  "optionalPlugins": ["visualizations"],
  "requiredBundles": []
}
Enter fullscreen mode Exit fullscreen mode

Field Explanations:

  • id: Unique identifier (camelCase, no spaces)
  • version: Your plugin version (follows semantic versioning)
  • opensearchDashboardsVersion: Compatible OSD version
  • server: Set to true if plugin has backend code
  • ui: Set to true if plugin has frontend UI
  • requiredPlugins: Plugins that MUST be installed
  • optionalPlugins: Plugins that enhance functionality if available

3.2 Configure package.json

{
  "name": "my-custom-plugin",
  "version": "1.0.0",
  "description": "A custom plugin for OpenSearch Dashboards",
  "main": "target/my-custom-plugin",
  "opensearchDashboards": {
    "version": "2.11.0",
    "templateVersion": "1.0.0"
  },
  "scripts": {
    "build": "yarn plugin-helpers build",
    "plugin-helpers": "node ../../scripts/plugin_helpers"
  },
  "devDependencies": {
    "@types/react": "^17.0.3"
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Build the Client-Side (Frontend)

4.1 Create the Plugin Class (public/plugin.ts)

import { CoreSetup, CoreStart, Plugin } from '../../../src/core/public';

// Define what your plugin provides to other plugins during setup
export interface MyCustomPluginSetup {}

// Define what your plugin provides after it starts
export interface MyCustomPluginStart {}

export class MyCustomPlugin implements Plugin<MyCustomPluginSetup, MyCustomPluginStart> {

  /**
   * Setup is called when the plugin is initialized
   * This is where you register your application
   */
  public setup(core: CoreSetup) {
    // Register the main application
    core.application.register({
      id: 'myCustomPlugin',
      title: 'My Custom Plugin',

      // Where the app appears in the navigation
      category: {
        id: 'customCategory',
        label: 'Custom Tools',
        order: 1000,
      },

      // The function that renders your app
      mount: async (params) => {
        // Lazy load the app component
        const { renderApp } = await import('./application');
        return renderApp(core, params);
      },
    });

    return {};
  }

  /**
   * Start is called when all plugins are set up
   * This is where you can use services from other plugins
   */
  public start(core: CoreStart) {
    return {};
  }

  /**
   * Stop is called when the plugin is being shut down
   * Clean up any resources here
   */
  public stop() {
    // Cleanup code here
  }
}
Enter fullscreen mode Exit fullscreen mode

Key Points:

  • setup(): Runs once during initialization - register your app here
  • mount(): Lazy loads your React app for better performance
  • start(): Runs after all plugins are ready - use plugin dependencies here

4.2 Create the Entry Point (public/index.ts)

import { PluginInitializerContext } from '../../../src/core/public';
import { MyCustomPlugin } from './plugin';

/**
 * This is the entry point for your plugin
 * OpenSearch Dashboards calls this function to create your plugin instance
 */
export function plugin(initializerContext: PluginInitializerContext) {
  return new MyCustomPlugin();
}

// Export plugin types for other plugins to use
export { MyCustomPluginSetup, MyCustomPluginStart } from './plugin';
Enter fullscreen mode Exit fullscreen mode

4.3 Create the React Application (public/application.tsx)

import React from 'react';
import ReactDOM from 'react-dom';
import { AppMountParameters, CoreStart } from '../../../src/core/public';
import { MyPluginApp } from './components/app';

/**
 * Renders the React application into the DOM
 * @param core - OpenSearch Dashboards core services
 * @param params - Mount parameters including the DOM element
 */
export const renderApp = (
  core: CoreStart,
  params: AppMountParameters
) => {
  // Render the React app
  ReactDOM.render(
    <MyPluginApp coreStart={core} />,
    params.element
  );

  // Return cleanup function
  return () => {
    ReactDOM.unmountComponentAtNode(params.element);
  };
};
Enter fullscreen mode Exit fullscreen mode

4.4 Create the Main Component (public/components/app.tsx)

import React, { useState } from 'react';
import {
  EuiPage,
  EuiPageBody,
  EuiPageContent,
  EuiPageContentBody,
  EuiPageHeader,
  EuiTitle,
  EuiText,
  EuiButton,
  EuiSpacer,
} from '@elastic/eui';
import { CoreStart } from '../../../../src/core/public';

interface MyPluginAppProps {
  coreStart: CoreStart;
}

export const MyPluginApp: React.FC<MyPluginAppProps> = ({ coreStart }) => {
  const [counter, setCounter] = useState(0);

  const handleClick = () => {
    setCounter(counter + 1);

    // Show a success toast notification
    coreStart.notifications.toasts.addSuccess({
      title: 'Button Clicked!',
      text: `Counter is now ${counter + 1}`,
    });
  };

  return (
    <EuiPage>
      <EuiPageBody>
        <EuiPageHeader>
          <EuiTitle size="l">
            <h1>My Custom Plugin</h1>
          </EuiTitle>
        </EuiPageHeader>

        <EuiPageContent>
          <EuiPageContentBody>
            <EuiText>
              <h2>Welcome to Your Custom Plugin!</h2>
              <p>
                This is a basic plugin template. You can extend it with your own
                functionality.
              </p>
            </EuiText>

            <EuiSpacer size="l" />

            <EuiText>
              <p>Counter: <strong>{counter}</strong></p>
            </EuiText>

            <EuiSpacer size="m" />

            <EuiButton fill onClick={handleClick}>
              Increment Counter
            </EuiButton>
          </EuiPageContentBody>
        </EuiPageContent>
      </EuiPageBody>
    </EuiPage>
  );
};
Enter fullscreen mode Exit fullscreen mode

What's happening here?

  • Elastic UI (EUI) Components: Using pre-built UI components for consistency
  • State Management: Using React hooks (useState) for interactivity
  • Core Services: Accessing notifications through coreStart

Step 5: Build the Server-Side (Backend)

5.1 Create the Server Plugin (server/plugin.ts)

import {
  CoreSetup,
  CoreStart,
  Plugin,
  Logger,
  PluginInitializerContext,
} from '../../../src/core/server';

export class MyCustomPluginServer implements Plugin {
  private readonly logger: Logger;

  constructor(initializerContext: PluginInitializerContext) {
    this.logger = initializerContext.logger.get();
  }

  public setup(core: CoreSetup) {
    this.logger.info('MyCustomPlugin: Setting up');

    // Create a router for API endpoints
    const router = core.http.createRouter();

    // Register a GET endpoint
    router.get(
      {
        path: '/api/my-custom-plugin/hello',
        validate: false, // We'll add validation later
      },
      async (context, request, response) => {
        this.logger.info('Hello endpoint called');

        return response.ok({
          body: {
            message: 'Hello from my custom plugin!',
            timestamp: new Date().toISOString(),
          },
        });
      }
    );

    // Register a POST endpoint with data
    router.post(
      {
        path: '/api/my-custom-plugin/data',
        validate: {
          body: schema.object({
            name: schema.string(),
            value: schema.number(),
          }),
        },
      },
      async (context, request, response) => {
        const { name, value } = request.body;

        this.logger.info(`Received data: ${name} = ${value}`);

        return response.ok({
          body: {
            success: true,
            received: { name, value },
          },
        });
      }
    );

    return {};
  }

  public start(core: CoreStart) {
    this.logger.info('MyCustomPlugin: Started');
    return {};
  }

  public stop() {
    this.logger.info('MyCustomPlugin: Stopping');
  }
}
Enter fullscreen mode Exit fullscreen mode

5.2 Add Request Validation (server/plugin.ts - enhanced)

import { schema } from '@osd/config-schema';

// Inside your route definition:
router.get(
  {
    path: '/api/my-custom-plugin/search',
    validate: {
      query: schema.object({
        term: schema.string({ minLength: 1, maxLength: 100 }),
        page: schema.number({ defaultValue: 1, min: 1 }),
        size: schema.number({ defaultValue: 10, min: 1, max: 100 }),
      }),
    },
  },
  async (context, request, response) => {
    const { term, page, size } = request.query;

    // Your search logic here

    return response.ok({
      body: { results: [], total: 0 },
    });
  }
);
Enter fullscreen mode Exit fullscreen mode

Why validation matters:

  • Security: Prevents malicious input
  • Data Integrity: Ensures correct data types
  • Clear API Contract: Documents expected parameters

5.3 Create Server Entry Point (server/index.ts)

import { PluginInitializerContext } from '../../../src/core/server';
import { MyCustomPluginServer } from './plugin';

export function plugin(initializerContext: PluginInitializerContext) {
  return new MyCustomPluginServer(initializerContext);
}

export { MyCustomPluginServer as Plugin };
Enter fullscreen mode Exit fullscreen mode

Step 6: Connect Frontend to Backend

6.1 Create an API Service (public/services/api.ts)

import { HttpSetup } from '../../../../src/core/public';

export class ApiService {
  private http: HttpSetup;

  constructor(http: HttpSetup) {
    this.http = http;
  }

  /**
   * Fetch hello message from the backend
   */
  async getHello(): Promise<{ message: string; timestamp: string }> {
    try {
      const response = await this.http.get('/api/my-custom-plugin/hello');
      return response;
    } catch (error) {
      throw new Error(`Failed to fetch hello: ${error.message}`);
    }
  }

  /**
   * Send data to the backend
   */
  async sendData(name: string, value: number): Promise<any> {
    try {
      const response = await this.http.post('/api/my-custom-plugin/data', {
        body: JSON.stringify({ name, value }),
      });
      return response;
    } catch (error) {
      throw new Error(`Failed to send data: ${error.message}`);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

6.2 Use the API in Your Component

// In public/components/app.tsx
import React, { useState, useEffect } from 'react';
import { ApiService } from '../services/api';

export const MyPluginApp: React.FC<MyPluginAppProps> = ({ coreStart }) => {
  const [message, setMessage] = useState<string>('');
  const [loading, setLoading] = useState<boolean>(true);

  useEffect(() => {
    const api = new ApiService(coreStart.http);

    const fetchData = async () => {
      try {
        const data = await api.getHello();
        setMessage(data.message);
      } catch (error) {
        coreStart.notifications.toasts.addDanger({
          title: 'Error',
          text: error.message,
        });
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [coreStart]);

  if (loading) {
    return <div>Loading...</div>;
  }

  return (
    <EuiPage>
      {/* Your UI here */}
      <EuiText><p>{message}</p></EuiText>
    </EuiPage>
  );
};
Enter fullscreen mode Exit fullscreen mode

Step 7: Build and Test Your Plugin

7.1 Development Mode

# From the OpenSearch Dashboards root directory
yarn start

# This will:
# - Start the development server
# - Watch for file changes
# - Enable hot reloading
# - Run on http://localhost:5601
Enter fullscreen mode Exit fullscreen mode

Access your plugin:

  1. Open browser to http://localhost:5601
  2. Look for "Custom Tools" in the left sidebar
  3. Click "My Custom Plugin"

7.2 Build for Production

# Navigate to your plugin directory
cd plugins/my-custom-plugin

# Build the plugin
yarn build

# This creates a ZIP file in:
# build/my-custom-plugin-1.0.0.zip
Enter fullscreen mode Exit fullscreen mode

Advanced Features

Working with OpenSearch Data

Query OpenSearch from Backend

// In server/plugin.ts
router.get(
  {
    path: '/api/my-custom-plugin/search-data',
    validate: {
      query: schema.object({
        index: schema.string(),
        query: schema.string(),
      }),
    },
  },
  async (context, request, response) => {
    const { index, query } = request.query;

    try {
      // Get OpenSearch client
      const client = context.core.opensearch.client.asCurrentUser;

      // Perform search
      const searchResponse = await client.search({
        index: index,
        body: {
          query: {
            match: {
              _all: query,
            },
          },
        },
      });

      return response.ok({
        body: {
          hits: searchResponse.body.hits.hits,
          total: searchResponse.body.hits.total,
        },
      });
    } catch (error) {
      this.logger.error(`Search failed: ${error}`);
      return response.customError({
        statusCode: 500,
        body: { message: 'Search failed' },
      });
    }
  }
);
Enter fullscreen mode Exit fullscreen mode

Adding Plugin Configuration

Define Configuration Schema (server/index.ts)

import { schema, TypeOf } from '@osd/config-schema';

export const configSchema = schema.object({
  enabled: schema.boolean({ defaultValue: true }),
  apiKey: schema.string({ defaultValue: '' }),
  maxResults: schema.number({ defaultValue: 100, min: 1, max: 1000 }),
});

export type MyPluginConfig = TypeOf<typeof configSchema>;

export const config = {
  schema: configSchema,
};
Enter fullscreen mode Exit fullscreen mode

Use Configuration in Plugin

// In server/plugin.ts
export class MyCustomPluginServer implements Plugin {
  private config: MyPluginConfig;

  constructor(initializerContext: PluginInitializerContext) {
    this.logger = initializerContext.logger.get();
    this.config = initializerContext.config.get<MyPluginConfig>();
  }

  public setup(core: CoreSetup) {
    if (!this.config.enabled) {
      this.logger.info('Plugin is disabled');
      return {};
    }

    // Use config values
    const maxResults = this.config.maxResults;
    // ...
  }
}
Enter fullscreen mode Exit fullscreen mode

User Configuration File

Users can configure your plugin in config/opensearch_dashboards.yml:

myCustomPlugin:
  enabled: true
  apiKey: "your-api-key-here"
  maxResults: 50
Enter fullscreen mode Exit fullscreen mode

Creating Custom Visualizations

// In public/plugin.ts
public setup(core: CoreSetup, { visualizations }: SetupDeps) {
  // Register custom visualization
  visualizations.createReactVisualization({
    name: 'my_custom_viz',
    title: 'My Custom Visualization',
    icon: 'visArea',
    description: 'A custom visualization for specialized data',
    visConfig: {
      component: MyCustomVizComponent,
    },
    editorConfig: {
      optionsTemplate: MyCustomVizOptions,
    },
    requestHandler: 'custom',
    responseHandler: 'none',
  });
}
Enter fullscreen mode Exit fullscreen mode

Saved Objects

Define Saved Object Type

// In server/plugin.ts
public setup(core: CoreSetup) {
  core.savedObjects.registerType({
    name: 'my-custom-object',
    hidden: false,
    namespaceType: 'single',
    mappings: {
      properties: {
        title: { type: 'text' },
        description: { type: 'text' },
        config: { type: 'object', enabled: false },
      },
    },
    migrations: {},
  });
}
Enter fullscreen mode Exit fullscreen mode

Use Saved Objects

// Create
const savedObject = await context.core.savedObjects.client.create(
  'my-custom-object',
  {
    title: 'My Object',
    description: 'Description here',
    config: { setting1: 'value1' },
  }
);

// Read
const object = await context.core.savedObjects.client.get(
  'my-custom-object',
  'object-id'
);

// Update
await context.core.savedObjects.client.update(
  'my-custom-object',
  'object-id',
  { title: 'Updated Title' }
);

// Delete
await context.core.savedObjects.client.delete(
  'my-custom-object',
  'object-id'
);
Enter fullscreen mode Exit fullscreen mode

Testing and Debugging

Unit Testing

Install Testing Dependencies

yarn add --dev @testing-library/react @testing-library/jest-dom
Enter fullscreen mode Exit fullscreen mode

Example Component Test

// public/components/__tests__/app.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { MyPluginApp } from '../app';

describe('MyPluginApp', () => {
  const mockCoreStart = {
    notifications: {
      toasts: {
        addSuccess: jest.fn(),
      },
    },
  };

  it('renders the title', () => {
    render(<MyPluginApp coreStart={mockCoreStart as any} />);
    expect(screen.getByText('My Custom Plugin')).toBeInTheDocument();
  });

  it('increments counter on button click', () => {
    render(<MyPluginApp coreStart={mockCoreStart as any} />);
    const button = screen.getByText('Increment Counter');

    fireEvent.click(button);

    expect(screen.getByText('Counter: 1')).toBeInTheDocument();
  });
});
Enter fullscreen mode Exit fullscreen mode

Run Tests

yarn test
Enter fullscreen mode Exit fullscreen mode

Debugging Tips

Enable Debug Logging

// In your plugin
this.logger.debug('Debug message here');
this.logger.info('Info message');
this.logger.warn('Warning message');
this.logger.error('Error message');
Enter fullscreen mode Exit fullscreen mode

Browser DevTools

  1. Open browser DevTools (F12)
  2. Go to Sources tab
  3. Find your plugin files under localhost:5601
  4. Set breakpoints
  5. Interact with your plugin to trigger breakpoints

Server-Side Debugging

Add this to your launch configuration:

{
  "type": "node",
  "request": "launch",
  "name": "Debug OpenSearch Dashboards",
  "program": "${workspaceFolder}/scripts/opensearch_dashboards",
  "args": ["--dev", "--no-base-path"],
  "console": "integratedTerminal"
}
Enter fullscreen mode Exit fullscreen mode

Deployment

Installing Your Plugin

Method 1: Install from ZIP

# Build your plugin first
cd plugins/my-custom-plugin
yarn build

# Install the plugin
bin/opensearch-dashboards-plugin install file:///path/to/my-custom-plugin-1.0.0.zip

# Restart OpenSearch Dashboards
Enter fullscreen mode Exit fullscreen mode

Method 2: Install from URL

bin/opensearch-dashboards-plugin install https://example.com/my-custom-plugin-1.0.0.zip
Enter fullscreen mode Exit fullscreen mode

Method 3: Manual Installation

# Copy plugin to plugins directory
cp -r my-custom-plugin /path/to/opensearch-dashboards/plugins/

# Restart OpenSearch Dashboards
Enter fullscreen mode Exit fullscreen mode

Removing Your Plugin

bin/opensearch-dashboards-plugin remove my-custom-plugin

# Restart OpenSearch Dashboards
Enter fullscreen mode Exit fullscreen mode

Updating Your Plugin

# Remove old version
bin/opensearch-dashboards-plugin remove my-custom-plugin

# Install new version
bin/opensearch-dashboards-plugin install file:///path/to/my-custom-plugin-2.0.0.zip

# Restart OpenSearch Dashboards
Enter fullscreen mode Exit fullscreen mode

Troubleshooting

Common Issues and Solutions

Issue: Plugin Won't Load

Symptoms:

  • Plugin doesn't appear in sidebar
  • Error messages in console

Solutions:

# 1. Check plugin ID matches in all files
# 2. Verify opensearch_dashboards.json is valid
# 3. Check server logs:
tail -f /var/log/opensearch-dashboards/opensearch-dashboards.log

# 4. Restart with verbose logging:
bin/opensearch-dashboards --verbose
Enter fullscreen mode Exit fullscreen mode

Issue: Build Fails

Symptoms:

  • yarn build produces errors

Solutions:

# 1. Clear cache and rebuild
rm -rf build/ target/ node_modules/
yarn install
yarn build

# 2. Check TypeScript errors:
yarn type-check

# 3. Verify all imports are correct
Enter fullscreen mode Exit fullscreen mode

Issue: API Calls Fail

Symptoms:

  • 404 errors on API endpoints
  • CORS errors

Solutions:

// 1. Verify route registration:
router.get(
  {
    path: '/api/my-custom-plugin/endpoint',  // Must start with /api/
    validate: false,
  },
  handler
);

// 2. Check server logs for errors

// 3. Test endpoint directly:
curl http://localhost:5601/api/my-custom-plugin/endpoint
Enter fullscreen mode Exit fullscreen mode

Issue: Hot Reload Not Working

Solutions:

# 1. Stop the dev server
# 2. Clear build artifacts:
rm -rf optimize/ .cache/

# 3. Restart dev server:
yarn start
Enter fullscreen mode Exit fullscreen mode

Performance Optimization

Lazy Loading

// Instead of:
import { HeavyComponent } from './heavy_component';

// Use:
const HeavyComponent = React.lazy(() => import('./heavy_component'));

// In your render:
<React.Suspense fallback={<div>Loading...</div>}>
  <HeavyComponent />
</React.Suspense>
Enter fullscreen mode Exit fullscreen mode

Minimize Bundle Size

// Avoid importing entire libraries:
// ❌ Bad:
import _ from 'lodash';

// ✅ Good:
import debounce from 'lodash/debounce';
Enter fullscreen mode Exit fullscreen mode

Best Practices Checklist

Development

  • [ ] Use TypeScript for type safety
  • [ ] Follow Elastic UI Framework guidelines
  • [ ] Implement proper error handling
  • [ ] Add loading states for async operations
  • [ ] Validate all user inputs
  • [ ] Use meaningful variable and function names
  • [ ] Add comments for complex logic

Security

  • [ ] Validate all API inputs with schemas
  • [ ] Sanitize user-provided data
  • [ ] Use proper authentication/authorization
  • [ ] Don't expose sensitive data in logs
  • [ ] Implement rate limiting for APIs

Performance

  • [ ] Lazy load heavy components
  • [ ] Minimize bundle size
  • [ ] Debounce frequent operations
  • [ ] Use pagination for large datasets
  • [ ] Cache frequently accessed data

Testing

  • [ ] Write unit tests for components
  • [ ] Write integration tests for APIs
  • [ ] Test error scenarios
  • [ ] Test with different data sets
  • [ ] Perform cross-browser testing

Documentation

  • [ ] Document all public APIs
  • [ ] Provide usage examples
  • [ ] Document configuration options
  • [ ] Include troubleshooting guide
  • [ ] Keep README up to date

Resources

Official Documentation

GitHub Repositories

UI Framework

Community

Learning Resources


Quick Reference

Common Commands

# Generate plugin
node scripts/generate_plugin.js <plugin-name>

# Install dependencies
yarn osd bootstrap

# Start dev server
yarn start

# Build plugin
yarn build

# Run tests
yarn test

# Install plugin
bin/opensearch-dashboards-plugin install <plugin-path>

# Remove plugin
bin/opensearch-dashboards-plugin remove <plugin-name>

# List installed plugins
bin/opensearch-dashboards-plugin list
Enter fullscreen mode Exit fullscreen mode

Useful Code Snippets

Show Toast Notification

coreStart.notifications.toasts.addSuccess('Success message');
coreStart.notifications.toasts.addWarning('Warning message');
coreStart.notifications.toasts.addDanger('Error message');
Enter fullscreen mode Exit fullscreen mode

Navigate to Another App

coreStart.application.navigateToApp('discover', {
  path: '#/view/some-id',
});
Enter fullscreen mode Exit fullscreen mode

Make HTTP Request

const response = await coreStart.http.get('/api/endpoint');
const data = await coreStart.http.post('/api/endpoint', {
  body: JSON.stringify({ key: 'value' }),
});
Enter fullscreen mode Exit fullscreen mode

Top comments (0)