Metadata
A PDF carries information about itself: a title, an author, when it was made. Register
metadataPlugin() to show it in your UI and let people change it:
<script lang="ts">
import { useMetadata, useMetadataState } from '@embedpdf/svelte/metadata';
const metadata = useMetadata();
const state = useMetadataState();
</script>
<label>
Title
<input
value={state.metadata?.title ?? ''}
disabled={!metadata.canUpdate()}
onblur={(event) => metadata.update({ title: event.currentTarget.value })}
/>
</label>What useMetadataState() gives you updates by itself: after your own changes, and on a shared
document, after someone else’s. Nothing is read when a document opens: the metadata loads the
first time something asks for it, so a document nobody shows the properties of costs nothing.
Read#
metadata has every standard field. A field the file doesn’t set is null:
| Field | What it is |
|---|---|
title | The document’s title |
author | Who wrote it |
subject | What it’s about |
keywords | Keywords, as one string (often separated by commas) |
creator | The app the document was made in |
producer | The app that turned it into a PDF |
createdAt | When it was created, as an ISO date |
modifiedAt | When it was last changed, as an ISO date |
trapped | A print-shop setting: 'true', 'false' or 'unknown', never null |
status says whether the metadata hasn’t been asked for yet ('idle'), is loading
('loading'), is there ('ready'), or is not allowed ('forbidden'). Reading the state is what
asks: useMetadataState()
loads the metadata of the document it reads, so its first answer is 'loading'. Show that while
it lasts, the way you show any other load:
const title = status === 'ready' ? (metadata?.title ?? 'Untitled') : 'Loading…';From code, ensureLoaded() reads it and resolves once it’s there:
await metadata.ensureLoaded();
const author = metadata.getSnapshot()?.author;Every field useMetadataState() gives you, and every status, is listed under State.
Loading live preview…
<script lang="ts" module>
import { DocumentGate, Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
import { metadataPlugin } from '@embedpdf/svelte/metadata';
import { localEngine } from '@embedpdf/engine';
import Properties from './Properties.svelte';
import '../properties.css';
const engine = localEngine();
const plugins = [metadataPlugin()];
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()) };
};
</script>
<Viewer {engine} {plugins} initialDocuments={[{ source: ebook }]}>
<DocumentGate>
{#snippet fallback()}
<p class="loading">Loading…</p>
{/snippet}
<Properties />
</DocumentGate>
</Viewer>Change#
update() changes the fields you pass, and null removes one:
await metadata.update({
title: 'Q2 Proposal (final)',
author: 'Dana Smith',
subject: null,
});It resolves with { metadata }: the fields after the change. The change is part of the document, kept when it’s
downloaded.
Loading live preview…
<script lang="ts" module>
import { DocumentGate, Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
import { metadataPlugin } from '@embedpdf/svelte/metadata';
import { localEngine } from '@embedpdf/engine';
import PropertiesEditor from './PropertiesEditor.svelte';
import '../edit.css';
const engine = localEngine();
const plugins = [metadataPlugin()];
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()) };
};
</script>
<Viewer {engine} {plugins} initialDocuments={[{ source: ebook }]}>
<DocumentGate>
{#snippet fallback()}
<p class="loading">Loading…</p>
{/snippet}
<PropertiesEditor />
</DocumentGate>
</Viewer>Your own fields#
Keep your own values in the document, such as a contract number or who reviewed it. PDF apps show them as custom properties:
<script lang="ts">
import { useMetadataState } from '@embedpdf/svelte/metadata';
const state = useMetadataState(); // state.custom is { contractId: 'C-2026-114', reviewedBy: 'dana' }
</script>
<span>Contract {state.custom?.contractId}</span>await metadata.custom.update({ reviewedBy: 'lee', draftOwner: null });A string sets a field, null removes it, and fields you leave out stay as they are. A field name is
1 to 127 plain characters and can’t be a standard field’s name.
Loading live preview…
<script lang="ts" module>
import { DocumentGate, Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
import { metadataPlugin } from '@embedpdf/svelte/metadata';
import { localEngine } from '@embedpdf/engine';
import CustomFields from './CustomFields.svelte';
import '../custom-fields.css';
const engine = localEngine();
const plugins = [metadataPlugin()];
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()) };
};
</script>
<Viewer {engine} {plugins} initialDocuments={[{ source: ebook }]}>
<DocumentGate>
{#snippet fallback()}
<p class="loading">Loading…</p>
{/snippet}
<CustomFields />
</DocumentGate>
</Viewer>Methods#
Everything useMetadata() gives you.
| Method | What it does |
|---|---|
update(changes) | Changes the standard fields you pass; null removes one. Resolves { metadata }, the fields after the change. Change |
custom.update(changes) | Changes your own fields: a string sets one, null removes it, the rest stay. Resolves { custom }. Your own fields |
ensureLoaded() | Reads the standard fields and your own from the file, unless they’re loaded or loading. Resolves once they’re there. Read |
refresh(), custom.refresh() | Reads the standard fields, or your own, from the file again. Rarely needed: they stay up to date by themselves. |
canUpdate() | Whether changing the standard fields and your own ones is allowed. Permissions |
Every method that returns a promise also takes a signal in its options, to cancel it.
State#
Everything useMetadataState() gives you, as one reactive object: read its fields, and your markup updates when they change. The method in the last column reads the same value once.
| Field | What it is | Read once |
|---|---|---|
metadata | Every standard field, as listed under Read, or null until it has loaded | getSnapshot() |
custom | Your own fields, as { name: value }, or null until they have loaded | custom.getSnapshot() |
status | 'idle' until something asks for it, then 'loading', 'ready', 'forbidden' or 'error' | getStatus(), or custom.getStatus() for your own fields |
Events#
<script lang="ts">
import { useMetadataEvent } from '@embedpdf/svelte/metadata';
let notice = $state('');
useMetadataEvent(
(metadata) => metadata.onUpdated,
({ metadata, origin }) => {
if (origin.kind === 'remote') notice = `The title is now "${metadata.title}"`;
},
);
</script>
<p role="status">{notice}</p>| Event | Fires when | Carries |
|---|---|---|
onUpdated | The standard fields changed, by you or by someone else | metadata, previous, changedKeys, origin |
onResynced | The standard fields were loaded from the file: the first time they’re asked for, after refresh(), or to catch up after a lost connection | metadata |
custom.onUpdated | Your own fields changed | custom, previous, changedKeys, origin |
custom.onResynced | Your own fields were loaded from the file | custom |
changedKeys names the fields that changed, and previous has the values before.
Permissions#
| Permission | What it allows | Check |
|---|---|---|
doc.open | Reading every field | none: it opened |
doc.metadata.modify | Changing the standard fields and your own ones | metadata.canUpdate() |
Good to know#
The modified date#
Changing a property doesn’t change modifiedAt for you. Set it yourself when you want it to move:
update({ modifiedAt: new Date() }).
Dates#
Dates are ISO strings, with the time zone the PDF gives, so they survive JSON. Write a date as an
ISO string or a Date. See Metadata for the details.
Your feedback goes directly to the documentation team.