EmbedPDF

Search

The search plugin finds text in the whole document and highlights every match. Register withSearch(), put <epdf-search-layer> in each page, and search from your own search box:

Loading live preview…

import {
  ChangeDetectionStrategy,
  Component,
  computed,
  effect,
  inject,
  signal,
  ViewEncapsulation,
} from '@angular/core';
import { EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput } from '@embedpdf/angular/runtime';
import { EpdfPageTemplate, EpdfStage, withStage } from '@embedpdf/angular/stage';
import { EpdfRenderLayer, withRender } from '@embedpdf/angular/render';
import { EpdfSearch, EpdfSearchLayer, withSearch } from '@embedpdf/angular/search';
import { localEngine } from '@embedpdf/engine';

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};

@Component({
  selector: 'demo-search-box',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="toolbar">
      <input
        #field
        class="field"
        type="search"
        aria-label="Search"
        placeholder="Search…"
        [value]="text()"
        (input)="text.set(field.value)"
        (keydown.enter)="search.nextHit()"
        (keydown.shift.enter)="search.previousHit()"
      />
      <output class="readout">{{ count() }}</output>
      <button
        type="button"
        class="button"
        aria-label="Previous match"
        [disabled]="search.hitCount() === 0"
        (click)="search.previousHit()"
      >
        ↑
      </button>
      <button
        type="button"
        class="button"
        aria-label="Next match"
        [disabled]="search.hitCount() === 0"
        (click)="search.nextHit()"
      >
        ↓
      </button>
    </div>
  `,
})
export class SearchBox {
  protected readonly search = inject(EpdfSearch);
  protected readonly text = signal('PDF');

  protected readonly count = computed(() => {
    if (this.search.hitCount() > 0) {
      return `${this.search.activeHitIndex() + 1} of ${this.search.hitCount()}`;
    }
    if (this.search.status() === 'searching') return 'Searching…';
    if (this.search.status() === 'complete') return 'No matches';
    return '';
  });

  constructor() {
    // Every change searches again, replacing the last search. An empty text clears it.
    effect(() => {
      const text = this.text();
      void this.search.search({ text });
    });
  }
}

@Component({
  selector: 'demo-root',
  imports: [
    EpdfDocumentGate,
    EpdfStage,
    EpdfPageTemplate,
    EpdfRenderLayer,
    EpdfSearchLayer,
    SearchBox,
  ],
  providers: [
    provideEmbedPdf(
      { engine: () => localEngine(), initialDocuments: [{ source: ebook }] },
      withStage(),
      withRender(),
      withSearch(),
    ),
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
  styleUrl: './basic.css',
  encapsulation: ViewEncapsulation.None,
  template: `
    <ng-container *epdfDocumentGate="let document; fallback: loading">
      <demo-search-box />
      <epdf-stage class="stage">
        <ng-template epdfPage>
          <epdf-render-layer />
          <epdf-search-layer />
        </ng-template>
      </epdf-stage>
    </ng-container>

    <ng-template #loading><p class="loading">Loading…</p></ng-template>
  `,
})
export class App {}

Try it: type a word, then press Enter for the next match and Shift+Enter for the one before. Each move scrolls the match into view.

search-box.ts
import { Component, inject } from '@angular/core';
import { EpdfRenderLayer } from '@embedpdf/angular/render';
import { EpdfSearch, EpdfSearchLayer } from '@embedpdf/angular/search';
import { EpdfPageTemplate, EpdfStage } from '@embedpdf/angular/stage';

@Component({
  selector: 'app-search',
  imports: [EpdfStage, EpdfPageTemplate, EpdfRenderLayer, EpdfSearchLayer],
  template: `
    <form (submit)="$event.preventDefault()">
      <input #query (input)="search.search({ text: query.value })" />
      @if (search.status() === 'searching') {
        <span>Searching…</span>
      }
      @if (search.hitCount() > 0) {
        <span>{{ search.activeHitIndex() + 1 }} of {{ search.hitCount() }}</span>
      }
      <button type="button" (click)="search.previousHit()">↑</button>
      <button type="button" (click)="search.nextHit()">↓</button>
    </form>

    <epdf-stage>
      <ng-template epdfPage>
        <epdf-render-layer />
        <epdf-search-layer />
      </ng-template>
    </epdf-stage>
  `,
})
export class SearchPage {
  protected readonly search = inject(EpdfSearch);
}
  • Matches appear as they’re found. Large documents are searched a few pages at a time, and the count and the highlights grow as the search goes.
  • Searching again replaces the search. Type another letter and the old search stops; an empty text clears it.
  • The first match becomes the active one without moving the view. nextHit(), previousHit() and goToHit(hit) move to a match and bring it into view, and wrap around at the ends.

Search options#

search.search({ text: 'invoice total', wholeWord: true, matchCase: false });

Loading live preview…

import {
  ChangeDetectionStrategy,
  Component,
  effect,
  inject,
  signal,
  ViewEncapsulation,
} from '@angular/core';
import { EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput } from '@embedpdf/angular/runtime';
import { EpdfPageTemplate, EpdfStage, withStage } from '@embedpdf/angular/stage';
import { EpdfRenderLayer, withRender } from '@embedpdf/angular/render';
import { EpdfSearch, EpdfSearchLayer, withSearch } from '@embedpdf/angular/search';
import type { SearchQuery } from '@embedpdf/angular/search';
import { localEngine } from '@embedpdf/engine';

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};

@Component({
  selector: 'demo-search-with-options',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="toolbar">
      <input
        #field
        class="field"
        type="search"
        aria-label="Search"
        placeholder="Search…"
        [value]="query().text"
        (input)="change({ text: field.value })"
      />
      <label class="option">
        <input
          #matchCase
          type="checkbox"
          [checked]="query().matchCase"
          (change)="change({ matchCase: matchCase.checked })"
        />
        Match case
      </label>
      <label class="option">
        <input
          #wholeWord
          type="checkbox"
          [checked]="query().wholeWord"
          (change)="change({ wholeWord: wholeWord.checked })"
        />
        Whole words
      </label>
      <output class="readout">{{ search.hitCount() }} matches</output>
    </div>
  `,
})
export class SearchWithOptions {
  protected readonly search = inject(EpdfSearch);
  protected readonly query = signal<SearchQuery>({
    text: 'pdf',
    matchCase: false,
    wholeWord: false,
  });

  constructor() {
    // The text and the options are one query: changing either searches again.
    effect(() => {
      const query = this.query();
      void this.search.search(query);
    });
  }

  protected change(part: Partial<SearchQuery>) {
    this.query.update((query) => ({ ...query, ...part }));
  }
}

@Component({
  selector: 'demo-root',
  imports: [
    EpdfDocumentGate,
    EpdfStage,
    EpdfPageTemplate,
    EpdfRenderLayer,
    EpdfSearchLayer,
    SearchWithOptions,
  ],
  providers: [
    provideEmbedPdf(
      { engine: () => localEngine(), initialDocuments: [{ source: ebook }] },
      withStage(),
      withRender(),
      withSearch(),
    ),
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
  styleUrl: './options.css',
  encapsulation: ViewEncapsulation.None,
  template: `
    <ng-container *epdfDocumentGate="let document; fallback: loading">
      <demo-search-with-options />
      <epdf-stage class="stage">
        <ng-template epdfPage>
          <epdf-render-layer />
          <epdf-search-layer />
        </ng-template>
      </epdf-stage>
    </ng-container>

    <ng-template #loading><p class="loading">Loading…</p></ng-template>
  `,
})
export class App {}

Try it: turn on Match case and pdf no longer finds PDF; turn on Whole words and it no longer finds EmbedPDF.

OptionWhat it doesDefault
textWhat to findrequired
matchCaseInvoice doesn’t match invoicefalse
wholeWordcat doesn’t match categoryfalse
matchDiacriticsresume doesn’t match résuméfalse
ignoreWhitespaceinvoice also finds i n v o i c e, for text with odd spacingfalse
regextext is a regular expressionfalse

A search starts from the page the reader is on, and { from: page.ref } starts it elsewhere.

A list of results#

search.hits() gives you every match found so far, each with a few words around it:

Loading live preview…

import {
  ChangeDetectionStrategy,
  Component,
  effect,
  inject,
  signal,
  ViewEncapsulation,
} from '@angular/core';
import { EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput } from '@embedpdf/angular/runtime';
import { EpdfPageTemplate, EpdfStage, withStage } from '@embedpdf/angular/stage';
import { EpdfRenderLayer, withRender } from '@embedpdf/angular/render';
import { EpdfSearch, EpdfSearchLayer, withSearch } from '@embedpdf/angular/search';
import { localEngine } from '@embedpdf/engine';

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};

@Component({
  selector: 'demo-search-box',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="toolbar">
      <input
        #field
        class="field"
        type="search"
        aria-label="Search"
        placeholder="Search…"
        [value]="text()"
        (input)="text.set(field.value)"
      />
      <output class="readout">{{ search.hitCount() }} matches</output>
    </div>
  `,
})
export class SearchBox {
  protected readonly search = inject(EpdfSearch);
  protected readonly text = signal('PDF');

  constructor() {
    effect(() => {
      const text = this.text();
      void this.search.search({ text });
    });
  }
}

@Component({
  selector: 'demo-root',
  imports: [
    EpdfDocumentGate,
    EpdfStage,
    EpdfPageTemplate,
    EpdfRenderLayer,
    EpdfSearchLayer,
    SearchBox,
  ],
  providers: [
    provideEmbedPdf(
      { engine: () => localEngine(), initialDocuments: [{ source: ebook }] },
      withStage(),
      withRender(),
      withSearch(),
    ),
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
  styleUrl: './results-list.css',
  encapsulation: ViewEncapsulation.None,
  template: `
    <ng-container *epdfDocumentGate="let document; fallback: loading">
      <demo-search-box />
      <div class="viewer">
        <epdf-stage class="stage">
          <ng-template epdfPage>
            <epdf-render-layer />
            <epdf-search-layer />
          </ng-template>
        </epdf-stage>
        <!-- Every match with the words around it. Clicking one makes it the active match and
             scrolls to it. -->
        <ol class="results">
          @for (hit of search.hits(); track hit.page.objectNumber + ':' + hit.start) {
            <li>
              <button
                type="button"
                class="result"
                [attr.aria-current]="$index === search.activeHitIndex()"
                (click)="search.goToHit(hit)"
              >
                <span class="result-page">Page {{ hit.pageIndex + 1 }}</span>
                <!-- A match has no snippet when the user may search but not copy text. -->
                @if (hit.snippet; as snippet) {
                  <span class="result-text">
                    …{{ snippet.before }}<mark>{{ snippet.match }}</mark
                    >{{ snippet.after }}…
                  </span>
                }
              </button>
            </li>
          }
        </ol>
      </div>
    </ng-container>

    <ng-template #loading><p class="loading">Loading…</p></ng-template>
  `,
})
export class App {
  protected readonly search = inject(EpdfSearch);
}
results.ts
import { Component, inject } from '@angular/core';
import { EpdfSearch } from '@embedpdf/angular/search';

@Component({
  selector: 'app-search-results',
  template: `
    <ol>
      @for (hit of search.hits(); track $index) {
        <li>
          <button type="button" (click)="search.goToHit(hit)">
            Page {{ hit.pageIndex + 1 }}: …{{ hit.snippet?.before }}
            <mark>{{ hit.snippet?.match }}</mark>
            {{ hit.snippet?.after }}…
          </button>
        </li>
      }
    </ol>
  `,
})
export class SearchResults {
  protected readonly search = inject(EpdfSearch);
}

search.hitsOn(page.ref) gives the matches on one page, for a count per thumbnail.

FieldWhat it is
pageThe page it’s on
pageIndexThat page’s position, from 0
start, countThe characters it covers: a range you can select or highlight
boundsA box around it, in page coordinates
segmentsOne piece per line it covers, each with its four corners
snippetThe words before, the match and the words after

Highlight or select a match#

A match is a range of characters, the same kind a selection and a highlight use, so it goes straight into either:

const hit = search.getActiveHit();
 
selection.select(hit); // select it
await annotation.create(hit.page, {
  subtype: 'highlight',
  quadPoints: hit.segments.map((segment) => segment.quad),
}); // highlight it

Search without showing it#

findAll() searches the whole document and gives you the matches, without touching the search box’s results or the highlights:

const hits = await search.findAll({ text: 'confidential', wholeWord: true });

Use it for a “mark every occurrence” feature, or to redact every match.

Change how matches look#

Set the highlight colors when you register the plugin:

withSearch({
  highlight: { color: '#fde047', activeColor: '#f97316' },
});

On dark or scanned pages, where a multiplied highlight disappears, use translucent colors and blendMode: 'normal'. Each of these can also come from CSS, which wins over the setting, for example in dark mode:

@media (prefers-color-scheme: dark) {
  .pdf-viewer {
    --epdf-search-highlight: rgb(253 224 71 / 0.4);
    --epdf-search-blend-mode: normal;
  }
}

Loading live preview…

import {
  ChangeDetectionStrategy,
  Component,
  effect,
  inject,
  signal,
  ViewEncapsulation,
} from '@angular/core';
import { EpdfDocument, EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput } from '@embedpdf/angular/runtime';
import { EpdfPageTemplate, EpdfStage, withStage } from '@embedpdf/angular/stage';
import { EpdfRenderLayer, withRender } from '@embedpdf/angular/render';
import { EpdfSearch, EpdfSearchLayer, withSearch } from '@embedpdf/angular/search';
import { localEngine } from '@embedpdf/engine';

// Each pair: every match, and the active one.
const COLORS = [
  { name: 'Yellow', color: '#ffd500', activeColor: '#ff9632' },
  { name: 'Blue', color: '#a5d8ff', activeColor: '#4c9bff' },
  { name: 'Green', color: '#b2f2bb', activeColor: '#40c057' },
  { name: 'Pink', color: '#fcc2d7', activeColor: '#f06595' },
];

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};

@Component({
  selector: 'demo-root',
  imports: [EpdfDocumentGate, EpdfStage, EpdfPageTemplate, EpdfRenderLayer, EpdfSearchLayer],
  providers: [
    provideEmbedPdf(
      { engine: () => localEngine(), initialDocuments: [{ source: ebook }] },
      withStage(),
      withRender(),
      withSearch({ highlight: { color: '#ffd500', activeColor: '#ff9632' } }),
    ),
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
  styleUrl: './highlight-colors.css',
  encapsulation: ViewEncapsulation.None,
  template: `
    <ng-container *epdfDocumentGate="let document; fallback: loading">
      <div class="toolbar">
        @for (choice of colors; track choice.name) {
          <button
            type="button"
            class="swatch"
            [attr.aria-label]="choice.name"
            [attr.aria-pressed]="search.settings().highlight.color === choice.color"
            [style.background]="
              'linear-gradient(135deg, ' + choice.color + ' 50%, ' + choice.activeColor + ' 50%)'
            "
            (click)="
              search.updateSettings({
                highlight: { color: choice.color, activeColor: choice.activeColor },
              })
            "
          ></button>
        }
        <button type="button" class="button" (click)="search.resetSettings()">Reset</button>
        <label class="option">
          <input
            #override
            type="checkbox"
            [checked]="fromCss()"
            (change)="fromCss.set(override.checked)"
          />
          Override with CSS
        </label>
      </div>
      <epdf-stage class="stage" [class.from-css]="fromCss()">
        <ng-template epdfPage>
          <epdf-render-layer />
          <epdf-search-layer />
        </ng-template>
      </epdf-stage>
    </ng-container>

    <ng-template #loading><p class="loading">Loading…</p></ng-template>
  `,
})
export class App {
  protected readonly colors = COLORS;
  protected readonly fromCss = signal(false);
  protected readonly search = inject(EpdfSearch);
  private readonly document = inject(EpdfDocument);

  constructor() {
    // Something to highlight, once the document is open.
    effect(() => {
      if (this.document.status() === 'ready') {
        void this.search.search({ text: 'PDF' });
      }
    });
  }
}

Try it: pick a color, and search.updateSettings({ highlight }) changes it at once. Then turn on Override with CSS: the CSS variables win over the color you picked, and their translucent highlights, drawn with blendMode: 'normal', stay visible on the dark cover.

Click a match on the page#

Give <epdf-search-layer (hitClick)> a function, and it’s called with the match someone clicks. goToHit() takes that match as it is, so making it the active one is one line:

hit-click.ts
import { Component, inject } from '@angular/core';
import { EpdfSearch, EpdfSearchLayer } from '@embedpdf/angular/search';

@Component({
  selector: 'app-page-matches',
  imports: [EpdfSearchLayer],
  template: `<epdf-search-layer (hitClick)="search.goToHit($event)" />`,
})
export class PageMatches {
  protected readonly search = inject(EpdfSearch);
}

Loading live preview…

import { ChangeDetectionStrategy, Component, inject, ViewEncapsulation } from '@angular/core';
import { EpdfDocumentGate, provideEmbedPdf } from '@embedpdf/angular/runtime';
import type { OpenInput } from '@embedpdf/angular/runtime';
import { EpdfPageTemplate, EpdfStage, withStage } from '@embedpdf/angular/stage';
import { EpdfRenderLayer, withRender } from '@embedpdf/angular/render';
import { EpdfSearch, EpdfSearchLayer, withSearch } from '@embedpdf/angular/search';
import { localEngine } from '@embedpdf/engine';

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};

// Which match is the active one: the first on load, then the one you click.
@Component({
  selector: 'demo-active-match',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="toolbar">
      <output class="readout">
        @if (search.hitCount() > 0) {
          Match {{ search.activeHitIndex() + 1 }} of {{ search.hitCount() }}
        } @else {
          Searching…
        }
      </output>
      <span class="hint">Click a highlighted match to make it the active one.</span>
    </div>
  `,
})
export class ActiveMatch {
  protected readonly search = inject(EpdfSearch);

  constructor() {
    // It's drawn once the document is open: search right away.
    void this.search.search({ text: 'PDF' });
  }
}

@Component({
  selector: 'demo-root',
  imports: [
    EpdfDocumentGate,
    EpdfStage,
    EpdfPageTemplate,
    EpdfRenderLayer,
    EpdfSearchLayer,
    ActiveMatch,
  ],
  providers: [
    provideEmbedPdf(
      { engine: () => localEngine(), initialDocuments: [{ source: ebook }] },
      withStage(),
      withRender(),
      withSearch(),
    ),
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
  styleUrl: './click-match.css',
  encapsulation: ViewEncapsulation.None,
  template: `
    <ng-container *epdfDocumentGate="let document; fallback: loading">
      <demo-active-match />
      <epdf-stage class="stage">
        <ng-template epdfPage>
          <epdf-render-layer />
          <epdf-search-layer (hitClick)="search.goToHit($event)" />
        </ng-template>
      </epdf-stage>
    </ng-container>

    <ng-template #loading><p class="loading">Loading…</p></ng-template>
  `,
})
export class App {
  protected readonly search = inject(EpdfSearch);
}

The text under a match stays selectable: a drag that starts on a match selects text, and only a click calls the function. Without one, matches are only paint, and the pointer goes straight through to the page.

Methods#

Everything inject(EpdfSearch) gives you.

MethodWhat it does
search(query, { from? })Starts a search, replacing the one before, and highlights the matches as they’re found. Resolves { status, hitCount } when it’s done, replaced or cancelled. An empty text clears. Search options
nextHit(reveal?), previousHit(reveal?)Makes the next or previous match the active one and scrolls to it, wrapping at the ends. Returns the match, or null when there is none.
goToHit(hit, reveal?)Makes one match the active one and scrolls to it: the match itself, or its place in the list. Click a match on the page
revealActiveHit(reveal?)Scrolls back to the active match without changing it
listHits({ page? })Every match found so far, or the ones on one page. In a component, use search.hits().
listPagesWithHits()The pages that have a match, in document order
cancel()Stops searching and keeps the matches found so far
clear()Stops searching and removes the matches and their highlights
refresh()Runs the current search again from the start. The plugin does this by itself when the document changes.
findAll(query, { snippets? })Finds every match without showing them or touching the current search. snippets: false leaves out the words around each match. Search without showing it
canSearch({ snippets? })Whether searching is allowed; with snippets: true, whether matches carry the words around them. Permissions
getSettings(), updateSettings(changes), resetSettings()Reads the settings, changes them while the app runs, or goes back to what you registered. In a component, use inject(EpdfSearch).settings(). Settings

reveal is { anchor, behavior }, the reveal setting for one call. Every method that returns a promise also takes a signal in its options, to cancel it.

State#

Every value is a signal on inject(EpdfSearch): read it in a template or a computed(), and your view updates when it changes. The method in the last column reads the same value once.

FieldWhat it isRead once
query()The last search, with its options, or nullgetQuery()
status()'idle', 'searching', 'complete', 'cancelled' or 'error'getStatus()
hitCount()How many matches were found so fargetHitCount(), or getHitCount(page) for one page
activeHitIndex()The active match’s place in the list, from 0, or -1getActiveHitIndex()
activeHit()The active match, or nullgetActiveHit()
progress()How far the search got: pagesSearched of pageCountgetProgress()
error()Why the search failed, when status is 'error'getError()

The matches themselves come from search.hits().

Settings#

Set them when you register the plugin, and change them while the app runs with search.updateSettings({ … }); search.resetSettings() goes back to what you registered, and inject(EpdfSearch).settings() gives you the current settings.

SettingWhat it doesDefaultFrom CSS
reveal.anchorWhere a match lands in the view when you move to it{ y: 0.35 }, like a browser—
reveal.behaviorGlide there ('smooth') or jump ('instant')'smooth'—
highlight.colorThe highlight of every match'#ffd500'--epdf-search-highlight
highlight.activeColorThe highlight of the active match'#ff9632'--epdf-search-highlight-active
highlight.blendModeHow the highlights mix with the page'multiply'--epdf-search-blend-mode

Events#

match-count.ts
import { Component, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { map } from 'rxjs';
import { EpdfSearch } from '@embedpdf/angular/search';

@Component({
  selector: 'app-match-count',
  template: `<p aria-live="polite">{{ message() }}</p>`,
})
export class MatchCount {
  private readonly search = inject(EpdfSearch);

  protected readonly message = toSignal(
    this.search.completed$.pipe(map(({ hitCount }) => `${hitCount} matches`)),
    { initialValue: '' },
  );
}
EventFires whenCarries
started$A search startedquery
progressChanged$The search’s progress changed: more pages were searched, or a new search started overhitCount, pagesSearched, pageCount
completed$The whole document was searchedhitCount
cancelled$A search stopped before the endreason
failed$A search failederror
activeHitChanged$Another match became the active oneindex, hit
cleared$The search was cleared—
settingsChanged$The settings changedsettings, changed

Permissions#

PermissionWhat it allowsCheck
doc.text.searchSearchingsearch.canSearch()
doc.text.copyThe words around each match (snippet)search.canSearch({ snippets: true })

Without doc.text.copy, search still finds and highlights every match, but a match has no snippet.

Good to know#

Searches stay up to date#

When the document changes, for example when pages are inserted or text is redacted, the current search runs again, so the matches and the count stay right.

Searching large documents#

The local engine searches the file in the browser. Big documents are searched a few pages at a time, so the page stays responsive and results arrive as they’re found.

Was this page helpful?

Your feedback goes directly to the documentation team.