Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/aidenybai/react-grab/llms.txt

Use this file to discover all available pages before exploring further.

Overview

React Grab’s plugin system allows you to extend functionality with custom context menu actions, toolbar items, lifecycle hooks, and theme overrides. Plugins provide a powerful way to integrate React Grab into your development workflow.

Plugin Interface

A plugin is an object that implements the Plugin interface:
name
string
required
Unique identifier for the plugin
theme
DeepPartial<Theme>
Partial theme overrides to customize the visual appearance
options
SettableOptions
Override default options like activationMode, keyHoldDuration, maxContextLines, etc.
actions
PluginAction[]
Array of context menu actions and/or toolbar menu items
hooks
PluginHooks
Lifecycle callbacks for events like activation, element selection, copying, etc.
setup
(api: ReactGrabAPI, hooks: ActionContextHooks) => PluginConfig | void
Setup function called when plugin is registered. Receives the full ReactGrabAPI and can return additional configuration or a cleanup function.

Registering a Plugin

Simple Registration

Register a plugin directly on the global API:
window.__REACT_GRAB__.registerPlugin({
  name: "my-plugin",
  hooks: {
    onElementSelect: (element) => {
      console.log("Selected:", element.tagName);
    },
  },
});

React Component Registration

Register inside a useEffect to ensure React Grab is loaded:
import { useEffect } from "react";

export function MyPlugin() {
  useEffect(() => {
    const api = window.__REACT_GRAB__;
    if (!api) return;

    api.registerPlugin({
      name: "my-plugin",
      actions: [
        {
          id: "my-action",
          label: "My Action",
          shortcut: "M",
          onAction: (context) => {
            console.log("Action on:", context.element);
            context.hideContextMenu();
          },
        },
      ],
    });

    return () => api.unregisterPlugin("my-plugin");
  }, []);

  return null;
}

Built-in Plugin Examples

Comment Plugin

The comment plugin adds context menu and toolbar actions for entering prompt mode:
import type { Plugin } from "react-grab";

export const commentPlugin: Plugin = {
  name: "comment",
  setup: (api) => ({
    actions: [
      {
        id: "comment",
        label: "Comment",
        shortcut: "Enter",
        onAction: (context) => {
          context.enterPromptMode?.();
        },
      },
      {
        id: "comment-toolbar",
        label: "Comment",
        shortcut: "Enter",
        target: "toolbar",
        onAction: () => {
          api.comment();
        },
      },
    ],
  }),
};

Open Plugin

The open plugin allows opening source files in your editor:
import type { Plugin } from "react-grab";
import { openFile } from "./utils/open-file";

export const openPlugin: Plugin = {
  name: "open",
  actions: [
    {
      id: "open",
      label: "Open",
      shortcut: "O",
      enabled: (context) => Boolean(context.filePath),
      onAction: (context) => {
        if (!context.filePath) return;

        const wasHandled = context.hooks.onOpenFile(
          context.filePath,
          context.lineNumber,
        );

        if (!wasHandled) {
          openFile(
            context.filePath,
            context.lineNumber,
            context.hooks.transformOpenFileUrl,
          );
        }

        context.hideContextMenu();
        context.cleanup();
      },
    },
  ],
};

Copy HTML Plugin

A more complex plugin that uses both hooks and actions:
import type { Plugin } from "react-grab";
import { appendStackContext } from "./utils/append-stack-context";
import { copyContent } from "./utils/copy-content";

export const copyHtmlPlugin: Plugin = {
  name: "copy-html",
  setup: (api, hooks) => {
    let isPendingSelection = false;

    return {
      hooks: {
        onElementSelect: (element) => {
          if (!isPendingSelection) return;
          isPendingSelection = false;
          
          void Promise.all([
            hooks.transformHtmlContent(element.outerHTML, [element]),
            api.getStackContext(element),
          ])
            .then(([transformedHtml, stackContext]) => {
              if (!transformedHtml) return;
              copyContent(appendStackContext(transformedHtml, stackContext));
            })
            .catch(() => {});
          
          return true;
        },
        onDeactivate: () => {
          isPendingSelection = false;
        },
        cancelPendingToolbarActions: () => {
          isPendingSelection = false;
        },
      },
      actions: [
        {
          id: "copy-html",
          label: "Copy HTML",
          onAction: async (context) => {
            await context.performWithFeedback(async () => {
              const combinedHtml = context.elements
                .map((element) => element.outerHTML)
                .join("\n\n");

              const transformedHtml = await context.hooks.transformHtmlContent(
                combinedHtml,
                context.elements,
              );

              if (!transformedHtml) return false;

              const stackContext = await api.getStackContext(context.element);
              return copyContent(
                appendStackContext(transformedHtml, stackContext),
                {
                  componentName: context.componentName,
                  tagName: context.tagName,
                },
              );
            });
          },
        },
        {
          id: "copy-html-toolbar",
          label: "Copy HTML",
          target: "toolbar",
          onAction: () => {
            isPendingSelection = true;
            api.activate();
          },
        },
      ],
    };
  },
};

Plugin Hooks

Plugins can listen to various lifecycle events:
onActivate
() => void
Called when React Grab is activated
onDeactivate
() => void
Called when React Grab is deactivated
onElementHover
(element: Element) => void
Called when hovering over an element
onElementSelect
(element: Element) => boolean | void | Promise<boolean>
Called when an element is selected. Return true to prevent default selection behavior.
onBeforeCopy
(elements: Element[]) => void | Promise<void>
Called before copying elements to clipboard
transformCopyContent
(content: string, elements: Element[]) => string | Promise<string>
Transform the content before it’s copied to clipboard
onAfterCopy
(elements: Element[], success: boolean) => void
Called after copy operation completes
onCopySuccess
(elements: Element[], content: string) => void
Called when copy succeeds
onCopyError
(error: Error) => void
Called when copy fails
transformHtmlContent
(html: string, elements: Element[]) => string | Promise<string>
Transform HTML content before copying
transformAgentContext
(context: AgentContext, elements: Element[]) => AgentContext | Promise<AgentContext>
Transform the context passed to AI agents
onOpenFile
(filePath: string, lineNumber?: number) => boolean | void
Handle file opening. Return true to prevent default behavior.
transformOpenFileUrl
(url: string, filePath: string, lineNumber?: number) => string
Transform the URL used to open files in editor

PluginConfig

The setup function can return a PluginConfig object:
theme
DeepPartial<Theme>
Theme overrides
options
SettableOptions
Option overrides
actions
PluginAction[]
Actions to register
hooks
PluginHooks
Lifecycle hooks
cleanup
() => void
Cleanup function called when the plugin is unregistered

Complete Example

Here’s a complete plugin that adds a custom analytics tracker:
import type { Plugin } from "react-grab";

const analyticsPlugin: Plugin = {
  name: "analytics",
  setup: (api) => {
    const trackEvent = (event: string, data: Record<string, any>) => {
      console.log("Analytics:", event, data);
      // Send to your analytics service
    };

    return {
      hooks: {
        onActivate: () => {
          trackEvent("grab_activated", {});
        },
        onElementSelect: (element) => {
          trackEvent("element_selected", {
            tagName: element.tagName,
          });
        },
        onCopySuccess: (elements, content) => {
          trackEvent("copy_success", {
            elementCount: elements.length,
            contentLength: content.length,
          });
        },
      },
      actions: [
        {
          id: "track-element",
          label: "Track Element",
          shortcut: "T",
          onAction: (context) => {
            trackEvent("custom_track", {
              tagName: context.tagName,
              componentName: context.componentName,
              filePath: context.filePath,
            });
            context.hideContextMenu();
          },
        },
      ],
      cleanup: () => {
        trackEvent("plugin_unregistered", {});
      },
    };
  },
};

// Register the plugin
window.__REACT_GRAB__.registerPlugin(analyticsPlugin);

Best Practices

  1. Unique Names: Always use unique plugin names to avoid conflicts
  2. Cleanup: Return a cleanup function from setup() to clean up resources
  3. Type Safety: Use TypeScript interfaces for better development experience
  4. Error Handling: Handle errors gracefully in async operations
  5. Performance: Avoid heavy operations in frequently called hooks like onElementHover
  6. Unregister: Always unregister plugins when components unmount in React

See Also

Build docs developers (and LLMs) love