Redact PDFs using JavaScript

A custom sidebar UI to automate content redaction. Includes custom search options that only match specific data types, and a preview function to see redaction results before the redaction is applied to the PDF. Get additional resources by visiting our JavaScript PDF redaction library.


/*
* In this example we enable the toolbar items for redactions
* and add a custom sidebar from which a search criteria can
* be used to create redaction annotations, and from where
* users can preview and irreversibly apply redactions on
* the document.
*/
import PSPDFKit from "@nutrient-sdk/viewer";
import React from "react";
import styles from "./styles";
import * as Icons from "./static/icons";
let instance = null;
export function load(defaultConfiguration) {
return PSPDFKit.load({
...defaultConfiguration,
container: ".container",
toolbarItems: [
...PSPDFKit.defaultToolbarItems,
{ type: "redact-rectangle" },
{ type: "redact-text-highlighter" },
],
}).then((_instance) => {
console.log("Nutrient Web SDK successfully loaded!!", _instance);
instance = _instance;
return _instance;
});
}
export const CustomContainer = React.forwardRef(() => {
const [collapsed, setCollapsed] = React.useState(false);
return (
<>
<style jsx>{styles}</style>
<div className="container-wrapper">
{
// Custom overlapping sidebar
}
<div className={`sidebar ${collapsed ? "sidebar-collapsed" : ""}`}>
<div className="scroller">
<header>
<img src="/redaction/static/redaction.svg" alt="" />
<h2>Content Redaction Options</h2>
<p>
Below you can see an implementation of Search & Redact
functionality as well as the optional Redaction Preview toggle.
</p>
</header>
<div className="section-header">
<p>Search & Redact Tools</p>
</div>
<SearchSection />
<div className="section-header">
<p>Preview & Apply</p>
</div>
<PreviewSection />
</div>
<button
className="collapse-handle"
onClick={() => {
setCollapsed((val) => !val);
}}
aria-label={collapsed ? "Expand" : "Collapse"}
title={collapsed ? "Expand" : "Collapse"}
>
<svg
width="3"
height="12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M1 0H0v12h1V0zM3 0H2v12h1V0z" fill="#848C9A" />
</svg>
</button>
</div>
{
// Nutrient Web SDK container
}
<div className="container" />
</div>
</>
);
});
const SearchSection = () => {
const [searchType, setSearchType] = React.useState(PSPDFKit.SearchType.TEXT);
const [searchPattern, setSearchPattern] = React.useState();
const [searchInAnn, setSearchInAnn] = React.useState(true);
React.useEffect(() => {
if (searchType === PSPDFKit.SearchType.PRESET) {
setSearchPattern(PSPDFKit.SearchPattern.CREDIT_CARD_NUMBER);
} else {
setSearchPattern("");
}
}, [searchType]);
return (
<>
<style jsx>{styles}</style>
<section className="section">
<SearchTypeBar value={searchType} setter={setSearchType} />
<label>
<p>
<strong>Search for:</strong>
</p>
{searchType === PSPDFKit.SearchType.PRESET ? (
<div>
<PresetSelect value={searchPattern} setter={setSearchPattern} />
<p>
<small>
Verify the annotations created by applying a preset to discard
false positive results.{" "}
<a
href="https://www.nutrient.io/api/web/PSPDFKit.html#.SearchPattern"
target="_blank"
rel="noopener noreferrer"
aria-label="Learn more about search patterns"
>
Learn more
</a>
.
</small>
</p>
</div>
) : (
<input
id="search-pattern"
type="text"
className="search-input"
onChange={(e) => setSearchPattern(e.target.value)}
/>
)}
</label>
<div>
<input
type="checkbox"
checked={searchInAnn}
onChange={(e) => setSearchInAnn(e.target.checked)}
id="search-in-annot-check"
/>
<label htmlFor="search-in-annot-check">
Search inside annotations
</label>
</div>
<button
onClick={async () => {
if (!searchPattern) {
return window.alert("Search pattern cannot be empty");
}
// This method will automatically create new redaction annotations
const matches = await instance.createRedactionsBySearch(
searchPattern,
{
searchType,
searchInAnnotations: searchInAnn,
}
);
if (!matches || !matches.size) {
return window.alert("No matches were found");
}
}}
className="large-btn align-right mark-btn"
>
Mark for Redaction
</button>
</section>
</>
);
};
const PreviewSection = () => {
const [isPreviewing, setIsPreviewing] = React.useState(false);
React.useEffect(() => {
if (instance) {
// the "previewRedactionMode" flag will toggle between mark and redacted
instance.setViewState((vs) =>
vs.set("previewRedactionMode", isPreviewing)
);
}
}, [isPreviewing]);
const clearAnnotations = async () => {
for (let i = 0; i < instance.totalPageCount; i++) {
const anns = await instance.getAnnotations(i);
const redactionAnns = anns
.filter(
(ann) => ann instanceof PSPDFKit.Annotations.RedactionAnnotation
)
.map((ann) => ann.id);
instance.delete(redactionAnns);
}
};
return (
<>
<style jsx>{styles}</style>
<section className="preview-section">
<div className="form-section-bar">
<div
className="form-section-desc"
onClick={() => setIsPreviewing((val) => !val)}
>
<p>
<strong>Redaction Preview</strong>
</p>
<p>{isPreviewing ? "On" : "Off"}</p>
</div>
<input
type="checkbox"
className="sr-only"
checked={isPreviewing}
onChange={(e) => setIsPreviewing(e.target.checked)}
id="redaction-preview-input"
/>
<label
className={`img-btn ${isPreviewing ? "btn-active" : ""}`}
htmlFor="redaction-preview-input"
>
<span className="sr-only">Redaction preview</span>
<Icons.RedactionPreview />
</label>
</div>
<div className="btn-group confirm-btns">
<button className="btn-plain" onClick={clearAnnotations}>
Clear
</button>
<button
className="btn-primary"
onClick={() => {
// This method will reload the current document with the
// applied redactions.
instance.applyRedactions();
}}
>
Apply
</button>
</div>
</section>
</>
);
};
const SearchTypeBar = (props) => {
const { value, setter } = props;
const searchTypesMap = {
[PSPDFKit.SearchType.TEXT]: "Text",
[PSPDFKit.SearchType.PRESET]: "Preset",
[PSPDFKit.SearchType.REGEX]: "Regex",
};
return (
<>
<style jsx>{styles}</style>
<fieldset>
<legend className="sr-only">Search Type</legend>
<div className="form-section-bar">
<div className="form-section-desc">
<p>
<strong>Search Type</strong>
</p>
<p>{searchTypesMap[value]}</p>
</div>
<div className="btn-bar">
<input
type="radio"
id="text-search-pattern"
name="search-type"
className="sr-only"
checked={value === PSPDFKit.SearchType.TEXT}
onChange={(e) => {
if (e.target.checked) {
setter(PSPDFKit.SearchType.TEXT);
}
}}
/>
<label
className={`img-btn ${
value === PSPDFKit.SearchType.TEXT ? "btn-active" : ""
}`}
htmlFor="text-search-pattern"
>
<span className="sr-only">Text</span>
<Icons.TextRedactionSearch />
</label>
<input
type="radio"
id="preset-search-pattern"
name="search-type"
className="sr-only"
checked={value === PSPDFKit.SearchType.PRESET}
onChange={(e) => {
if (e.target.checked) {
setter(PSPDFKit.SearchType.PRESET);
}
}}
/>
<label
className={`img-btn ${
value === PSPDFKit.SearchType.PRESET ? "btn-active" : ""
}`}
htmlFor="preset-search-pattern"
>
<span className="sr-only">Preset</span>
<Icons.PatternRedactionSearch />
</label>
<input
type="radio"
name="search-type"
id="regex-search-pattern"
className="sr-only"
checked={value === PSPDFKit.SearchType.REGEX}
onChange={(e) => {
if (e.target.checked) {
setter(PSPDFKit.SearchType.REGEX);
}
}}
/>
<label
className={`img-btn ${
value === PSPDFKit.SearchType.REGEX ? "btn-active" : ""
}`}
htmlFor="regex-search-pattern"
>
<span className="sr-only">Regular expression</span>
<Icons.RegexRedactionSearch />
</label>
</div>
</div>
</fieldset>
</>
);
};
const PresetSelect = (props) => {
const { value, setter } = props;
const { SearchPattern } = PSPDFKit;
const presetsMap = {
CREDIT_CARD_NUMBER: "Credit card number",
DATE: "Date",
TIME: "Time",
EMAIL_ADDRESS: "E-mail address",
INTERNATIONAL_PHONE_NUMBER: "International phone number",
IP_V4: "IPv4 address",
IP_V6: "IPv6 address",
MAC_ADDRESS: "MAC address",
NORTH_AMERICAN_PHONE_NUMBER: "North American phone number",
SOCIAL_SECURITY_NUMBER: "Social Security number",
URL: "URL",
US_ZIP_CODE: "U.S. zip code",
VIN: "VIN",
};
return (
<>
<style jsx>{styles}</style>
<select
value={value}
onChange={(e) => setter(e.target.value)}
id="search-pattern"
className="search-input"
>
{Object.keys(SearchPattern).map((key) => (
<option key={key} value={SearchPattern[key]}>
{presetsMap[key]}
</option>
))}
</select>
</>
);
};

This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.