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).
6. Create the UI Component (Optional but Recommended)
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:
- Mocks
useToolOperationto capture the configuration passed. - Asserts that
toolType,endpoint, andbuildFormDataare correct.
Running ./test.sh (or npm test inside frontend) should now include the new test without failures.
Summary
- Create a parameter hook extending
useBaseParameterswith validation logic insrc/core/hooks/tools/{tool}/use{Tool}Parameters.ts. - Implement a pure FormData builder function for
multipart/form-dataconstruction. - Define a static operation configuration object containing
toolType,endpoint,buildFormData, andoperationTypefor automation support. - Export the main operation hook that wraps
useToolOperationwith error handling viacreateStandardErrorHandler. - Register the tool in
src/core/data/toolsTaxonomy.tswith theoperationConfigreference to enable UI and automation discovery. - Build a thin UI component using
ToolBaseto ensure consistent file handling and progress display. - Verify automation compatibility through
executeAutomationSequenceand 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →