How to Add a New PDF Processing Tool Using the useToolOperation Hook Pattern in Stirling-PDF

To add a new PDF processing tool in Stirling-PDF, create a parameter hook that extends useBaseParameters, define a pure FormData builder function, export a static operationConfig object, and wrap everything in a hook that calls the shared useToolOperation hook, then register the tool in toolsTaxonomy.ts with its operationConfig.

Stirling-PDF implements every PDF-processing feature as a small React hook that wraps the shared useToolOperation hook pattern. This architecture provides consistent error handling, progress tracking, and credit checks while maintaining a single source of truth for API endpoints and form-data construction. Whether you are adding a Watermark tool or a custom compression feature, following this pattern ensures your new PDF processing tool integrates seamlessly with the UI and automation system.

1. Define the Parameter Shape and Hook

Create a useWatermarkParameters.ts file following the pattern established by existing tools like Split. Define the interface extending BaseParameters and use useBaseParameters to manage state.

// src/core/hooks/tools/watermark/useWatermarkParameters.ts
import { BaseParameters } from '@app/types/parameters';
import { useBaseParameters, BaseParametersHook } from '@app/hooks/tools/shared/useBaseParameters';
import { ENDPOINTS, WATERMARK_METHODS } from '@app/constants/watermarkConstants';

export interface WatermarkParameters extends BaseParameters {
  text: string;
  opacity: string;               // 0‑1 as string
  rotation: string;              // degrees
  // add any extra fields you need
}

export type WatermarkParametersHook = BaseParametersHook<WatermarkParameters>;

export const defaultParameters: WatermarkParameters = {
  text: '',
  opacity: '0.5',
  rotation: '0',
};

export const useWatermarkParameters = (): WatermarkParametersHook => {
  return useBaseParameters({
    defaultParameters,
    endpointName: () => ENDPOINTS[WATERMARK_METHODS.ADD],
    validateFn: (p) => p.text.trim().length > 0 && p.opacity !== '' && p.rotation !== '',
  });
};

This pattern mirrors the Split parameters hook implementation in [useSplitParameters.ts](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/frontend/src/core/hooks/tools/split/useSplitParameters.ts).

2. Build the FormData Builder

Most tools send a multipart/form-data request. Create a pure function that receives the parameters and a File, returning a FormData instance.

// src/core/hooks/tools/watermark/useWatermarkOperation.ts (part)
export const buildWatermarkFormData = (params: WatermarkParameters, file: File): FormData => {
  const form = new FormData();
  form.append('fileInput', file);
  form.append('watermarkText', params.text);
  form.append('opacity', params.opacity);
  form.append('rotation', params.rotation);
  return form;
};

Compare this with the Rotate builder in [useRotateOperation.ts](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/frontend/src/core/hooks/tools/rotate/useRotateOperation.ts).

3. Create the Static Operation Configuration

Export a constant that describes the tool for useToolOperation. This object is also used by the automation engine.

// src/core/hooks/tools/watermark/useWatermarkOperation.ts (continued)
export const watermarkOperationConfig = {
  toolType: ToolType.singleFile,            // most PDF tools are single‑file
  buildFormData: buildWatermarkFormData,
  operationType: 'watermark' as const,     // matches a ToolId entry
  endpoint: '/api/v1/misc/watermark-pdf', // backend endpoint
  defaultParameters,
} as const;

See the Rotate static config in [useRotateOperation.ts](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/frontend/src/core/hooks/tools/rotate/useRotateOperation.ts).

4. Export the Hook that Calls useToolOperation

// src/core/hooks/tools/watermark/useWatermarkOperation.ts (final)
import { useTranslation } from 'react-i18next';
import { createStandardErrorHandler } from '@app/utils/toolErrorHandler';
import { ToolType, useToolOperation } from '@app/hooks/tools/shared/useToolOperation';

export const useWatermarkOperation = () => {
  const { t } = useTranslation();

  return useToolOperation<WatermarkParameters>({
    ...watermarkOperationConfig,
    getErrorMessage: createStandardErrorHandler(
      t('watermark.error.failed', 'An error occurred while adding the watermark.')
    ),
  });
};

This pattern is identical to useCompressOperation.ts and useRotateOperation.ts.

5. Register the Tool in the Global Registry

Add an entry to src/core/data/toolsTaxonomy.ts. Provide the component, icon, and operationConfig.

// src/core/data/toolsTaxonomy.ts (excerpt)
import WatermarkTool from '@app/components/tools/WatermarkTool';
import WatermarkIcon from '@mui/icons-material/Watermark';   // any MUI icon

export const toolRegistry: ToolRegistry = {
  // … existing tools
  watermark: {
    icon: <WatermarkIcon />,
    name: 'Watermark',
    component: WatermarkTool,
    description: 'Add a custom text watermark to PDFs.',
    categoryId: ToolCategoryId.STANDARD_TOOLS,
    subcategoryId: SubcategoryId.PAGE_FORMATTING,
    maxFiles: 1,
    supportedFormats: ['pdf'],
    operationConfig: watermarkOperationConfig,          // <-- critical!
    automationSettings: null,
  },
};

Every existing entry follows this shape; for example the Rotate entry uses rotateOperationConfig (see toolsTaxonomy.ts around line 40).

A thin wrapper component connects the parameters hook to the generic ToolRenderer.

// src/components/tools/WatermarkTool.tsx
import { useWatermarkParameters } from '@app/hooks/tools/watermark/useWatermarkParameters';
import { useWatermarkOperation } from '@app/hooks/tools/watermark/useWatermarkOperation';
import { ToolBase } from '@app/components/tools/ToolBase';

export const WatermarkTool = () => {
  const paramsHook = useWatermarkParameters();
  const operation = useWatermarkOperation();

  return (
    <ToolBase
      title="Watermark"
      parametersHook={paramsHook}
      operation={operation}
    />
  );
};

All existing tools (e.g., Rotate, Compress) use the same ToolBase wrapper, guaranteeing a uniform UI and correct interaction with FileContext.

7. Verify Automation Support

Because operationConfig is stored in the registry, the automation engine automatically knows how to invoke the new tool. A quick sanity test:

// src/core/utils/automationExecutor.test.ts (conceptual)
await executeAutomationSequence(
  [{ operation: 'watermark', parameters: { text: 'Demo', opacity: '0.3', rotation: '45' } }],
  [sampleFile],
  toolRegistry,
  console.log,
  () => {},    // no‑op callbacks
  (i, err) => { throw err; }
);

If the sequence runs without errors, the tool is fully automation‑compatible.

The custom Automate tool (useAutomateOperation.ts) shows how any tool can be called via executeAutomationSequence.

8. Run the Test Suite

The project already ships with unit tests for each tool hook (e.g., useRotateOperation.test.ts). Add a similar test file (useWatermarkOperation.test.ts) that:

  1. Mocks useToolOperation to capture the configuration passed.
  2. Asserts that toolType, endpoint, and buildFormData are correct.

Running ./test.sh (or npm test inside frontend) should now include the new test without failures.

Summary

  • Create a parameter hook extending useBaseParameters with validation logic in src/core/hooks/tools/{tool}/use{Tool}Parameters.ts.
  • Implement a pure FormData builder function for multipart/form-data construction.
  • Define a static operation configuration object containing toolType, endpoint, buildFormData, and operationType for automation support.
  • Export the main operation hook that wraps useToolOperation with error handling via createStandardErrorHandler.
  • Register the tool in src/core/data/toolsTaxonomy.ts with the operationConfig reference to enable UI and automation discovery.
  • Build a thin UI component using ToolBase to ensure consistent file handling and progress display.
  • Verify automation compatibility through executeAutomationSequence and run the full test suite to ensure integration.

Frequently Asked Questions

How does the useToolOperation hook handle errors?

The useToolOperation hook accepts a getErrorMessage function that creates standardized error handlers. Pass a translation key or default message to createStandardErrorHandler from @app/utils/toolErrorHandler, which returns a function that formats API errors consistently across all PDF processing tools. This ensures users see localized, actionable error messages regardless of which specific tool fails.

Can I use this pattern for multi-file PDF operations?

Yes. Change the toolType in your operationConfig to ToolType.multiFile instead of ToolType.singleFile. The useToolOperation hook automatically adjusts progress tracking and FormData construction to handle multiple files, and the automation engine will process file arrays accordingly. Ensure your buildFormData function iterates over the files array if the backend expects multiple fileInput entries.

What is the minimum required configuration to register a new tool?

You must provide the operationConfig object containing toolType, endpoint, buildFormData, operationType, and defaultParameters when registering in toolsTaxonomy.ts. Without this configuration, the automation engine cannot invoke your tool and the useToolOperation hook lacks the necessary metadata to execute API calls. The registry entry must also include component, icon, and supportedFormats for UI rendering.

How do I test the new tool hook before integrating the UI?

Create a unit test file (e.g., useWatermarkOperation.test.ts) that mocks useToolOperation to verify the configuration object passed contains the correct endpoint, toolType, and buildFormData function. Run the test suite with npm test or ./test.sh to ensure the hook exports valid metadata before building the UI component. This validates that the hook correctly wraps useToolOperation before React components consume it.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →