---
title: "Metadata — Vue"
description: "Show and change a document's properties: title, author, subject, keywords, dates, and your own fields."
framework: "Vue"
source: "https://www.embedpdf.com/docs/headless/vue/documents/metadata"
---

# 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:

**`title-field.vue`**

```vue
<script setup lang="ts">
import { useMetadata, useMetadataState } from '@embedpdf/vue/metadata';

const metadata = useMetadata();
const { metadata: fields } = useMetadataState();

function onBlur(event: FocusEvent) {
  metadata.update({ title: (event.target as HTMLInputElement).value });
}
</script>

<template>
  <label>
    Title
    <input :value="fields?.title ?? ''" :disabled="!metadata.canUpdate()" @blur="onBlur" />
  </label>
</template>
```

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:

```ts
const title = status === 'ready' ? (metadata?.title ?? 'Untitled') : 'Loading…';
```

From code, `ensureLoaded()` reads it and resolves once it's there:

```ts
await metadata.ensureLoaded();
const author = metadata.getSnapshot()?.author;
```

Every field `useMetadataState()` gives you, and every status, is listed under [State](#state).

**`App.vue`**

```vue
<script setup lang="ts">
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { metadataPlugin } from '@embedpdf/vue/metadata';
import { localEngine } from '@embedpdf/engine';
import Properties from './Properties.vue';

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>

<template>
  <Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: ebook }]">
    <DocumentGate>
      <template #fallback><p class="loading">Loading…</p></template>
      <Properties />
    </DocumentGate>
  </Viewer>
</template>
```

**`properties.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --surface: #ffffff;
  --canvas: #f7f9fc;
  --ready: #1f8a4c;
  --ready-tint: #e8f6ee;
  --waiting: #a86a00;
  --waiting-tint: #fdf3e1;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The properties, in a card */
.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
  overflow: hidden;
}

.panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 16px;
  background: var(--canvas);
  border-bottom: 1px solid var(--line);
}

.panel-title {
  margin: 0;
  font-size: 14px;
  font-weight: 650;
  color: var(--ink);
}

/* The load state: loading, ready, forbidden or error */
.status {
  font-size: 11.5px;
  font-weight: 600;
  border-radius: 999px;
  padding: 3px 9px;
  color: var(--waiting);
  background: var(--waiting-tint);
}

.status[data-status='ready'] {
  color: var(--ready);
  background: var(--ready-tint);
}

/* One field per row: its name beside its value, stacked on a phone */
.properties {
  container-type: inline-size;
  margin: 0;
  padding: 4px 16px 10px;
}

.property {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
}

.property:last-child {
  border-bottom: 0;
}

@container (min-width: 460px) {
  .property {
    grid-template-columns: 180px 1fr;
    gap: 16px;
  }
}

.property dt {
  font-size: 13px;
  color: var(--muted);
}

.property dd {
  margin: 0;
  font-size: 13px;
  font-weight: 550;
  color: var(--ink);
  overflow-wrap: anywhere;
}

.property dd.unset {
  font-weight: 400;
  font-style: italic;
  color: var(--muted);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

**`Properties.vue`**

```vue
<!-- The document's properties, the way a "Document properties" dialog shows them. -->
<script setup lang="ts">
import { computed } from 'vue';
import { useMetadataState } from '@embedpdf/vue/metadata';

const FIELDS = [
  ['title', 'Title'],
  ['author', 'Author'],
  ['subject', 'Subject'],
  ['keywords', 'Keywords'],
  ['creator', 'Made in'],
  ['producer', 'Turned into a PDF by'],
  ['createdAt', 'Created'],
  ['modifiedAt', 'Last changed'],
  ['trapped', 'Trapped'],
] as const;

// Dates are ISO strings: show them in the reader's own format.
const DATES: ReadonlySet<string> = new Set(['createdAt', 'modifiedAt']);
const show = (field: string, value: string | null) => {
  if (value === null) return null;
  return DATES.has(field) ? new Date(value).toLocaleString() : value;
};

const { metadata, status } = useMetadataState();

const rows = computed(() =>
  FIELDS.map(([field, label]) => ({
    field,
    label,
    value: metadata.value ? show(field, metadata.value[field]) : null,
  })),
);
</script>

<template>
  <section class="panel">
    <header class="panel-header">
      <h3 class="panel-title">Document properties</h3>
      <span class="status" :data-status="status">{{ status }}</span>
    </header>
    <dl class="properties">
      <div v-for="row in rows" :key="row.field" class="property">
        <dt>{{ row.label }}</dt>
        <dd :class="row.value === null ? 'unset' : undefined">{{ row.value ?? 'not set' }}</dd>
      </div>
    </dl>
  </section>
</template>
```

## Change

`update()` changes the fields you pass, and `null` removes one:

```ts
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.

**`App.vue`**

```vue
<script setup lang="ts">
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { metadataPlugin } from '@embedpdf/vue/metadata';
import { localEngine } from '@embedpdf/engine';
import PropertiesEditor from './PropertiesEditor.vue';

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>

<template>
  <Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: ebook }]">
    <DocumentGate>
      <template #fallback><p class="loading">Loading…</p></template>
      <PropertiesEditor />
    </DocumentGate>
  </Viewer>
</template>
```

**`edit.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --accent: #0876fd;
  --accent-strong: #0565da;
  --on-accent: #ffffff;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

.button {
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  line-height: 1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 12px;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 1px 2px var(--shadow);
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.button.primary {
  color: var(--on-accent);
  background: var(--accent);
  border-color: var(--accent);
}

.button.primary:hover {
  background: var(--accent-strong);
  border-color: var(--accent-strong);
}

/* The form and the changes: side by side where there's room, stacked on a phone */
.layout {
  container-type: inline-size;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.form,
.changes {
  flex: 1 1 100%;
  box-sizing: border-box;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 14px 16px;
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

@container (min-width: 620px) {
  .form {
    flex: 1 1 0;
    min-width: 0;
  }

  .changes {
    flex: 0 0 240px;
  }
}

/* One field per row: its name above its input */
.row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 10px;
}

.name {
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
}

.field {
  appearance: none;
  box-sizing: border-box;
  width: 100%;
  font: inherit;
  font-size: 13px;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 11px;
}

.field::placeholder {
  font-style: italic;
  color: var(--muted);
}

.field:focus-visible {
  outline: none;
  border-color: var(--focus);
  box-shadow: 0 0 0 3px rgb(125 182 255 / 0.35);
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 4px;
}

/* What changed, newest first */
.changes-title {
  margin: 0 0 6px;
  font-size: 13px;
  font-weight: 650;
  color: var(--ink);
}

.modified,
.empty {
  margin: 0 0 8px;
  font-size: 12.5px;
  color: var(--muted);
}

.log {
  margin: 0;
  padding: 0;
  list-style: none;
}

.log li {
  padding: 6px 0;
  font-size: 12.5px;
  color: var(--ink);
  border-top: 1px solid var(--line);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

**`PropertiesEditor.vue`**

```vue
<!-- The form starts once the properties have been read from the file. -->
<script setup lang="ts">
import { useMetadataState } from '@embedpdf/vue/metadata';
import PropertiesForm from './PropertiesForm.vue';

const fields = useMetadataState((state) => state.metadata);
</script>

<template>
  <PropertiesForm v-if="fields" :initial="fields" />
  <p v-else class="loading">Reading…</p>
</template>
```

**`PropertiesForm.vue`**

```vue
<!-- A properties form: the inputs start with what the file says, and Save writes them back. -->
<script setup lang="ts">
import { computed, reactive, ref } from 'vue';
import type { DocumentMetadata } from '@embedpdf/vue/runtime';
import { useMetadata, useMetadataEvent, useMetadataState } from '@embedpdf/vue/metadata';

const props = defineProps<{ initial: DocumentMetadata }>();

const EDITABLE = ['title', 'author', 'subject', 'keywords'] as const;
type Draft = Record<(typeof EDITABLE)[number], string>;

const metadata = useMetadata();
const modifiedAt = useMetadataState((state) => state.metadata?.modifiedAt ?? null);
const draft = reactive<Draft>({
  title: props.initial.title ?? '',
  author: props.initial.author ?? '',
  subject: props.initial.subject ?? '',
  keywords: props.initial.keywords ?? '',
});
const changes = ref<string[]>([]);

// Every change to the standard fields, by you or anyone else.
useMetadataEvent(
  (capability) => capability.onUpdated,
  ({ changedKeys, origin }) => {
    changes.value = [
      `${changedKeys.join(', ')} changed ${origin.kind === 'local' ? 'here' : 'elsewhere'}`,
      ...changes.value,
    ];
  },
);

const canUpdate = computed(() => metadata.canUpdate());
// An empty field removes it: null, not ''.
function save() {
  void metadata.update({
    title: draft.title.trim() || null,
    author: draft.author.trim() || null,
    subject: draft.subject.trim() || null,
    keywords: draft.keywords.trim() || null,
  });
}
</script>

<template>
  <div class="layout">
    <form class="form" @submit.prevent="save">
      <label v-for="key in EDITABLE" :key="key" class="row">
        <span class="name">{{ key }}</span>
        <input v-model="draft[key]" class="field" :disabled="!canUpdate" placeholder="not set" />
      </label>
      <div class="actions">
        <button type="submit" class="button primary" :disabled="!canUpdate">Save</button>
        <button
          type="button"
          class="button"
          :disabled="!canUpdate"
          @click="metadata.update({ modifiedAt: new Date() })"
        >
          Set the modified date to now
        </button>
      </div>
    </form>
    <section class="changes" aria-live="polite">
      <h3 class="changes-title">Changes</h3>
      <p class="modified">
        Last changed: {{ modifiedAt ? new Date(modifiedAt).toLocaleString() : 'not set' }}
      </p>
      <p v-if="changes.length === 0" class="empty">Edit a field and save.</p>
      <ul v-else class="log">
        <li v-for="(change, index) in changes" :key="changes.length - index">{{ change }}</li>
      </ul>
    </section>
  </div>
</template>
```

## 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:

**`custom-fields.vue`**

```vue
<script setup lang="ts">
import { useMetadataState } from '@embedpdf/vue/metadata';

const { custom } = useMetadataState(); // { contractId: 'C-2026-114', reviewedBy: 'dana' }
</script>

<template>
  <span>Contract {{ custom?.contractId }}</span>
</template>
```

```ts
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.

**`App.vue`**

```vue
<script setup lang="ts">
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { metadataPlugin } from '@embedpdf/vue/metadata';
import { localEngine } from '@embedpdf/engine';
import CustomFields from './CustomFields.vue';

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>

<template>
  <Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: ebook }]">
    <DocumentGate>
      <template #fallback><p class="loading">Loading…</p></template>
      <CustomFields />
    </DocumentGate>
  </Viewer>
</template>
```

**`custom-fields.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --danger: #c62a2a;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The fields, in a card */
.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
  overflow: hidden;
}

.fields {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.fields th {
  padding: 10px 16px;
  font-size: 12px;
  font-weight: 600;
  text-align: left;
  color: var(--muted);
  background: var(--canvas);
  border-bottom: 1px solid var(--line);
}

.fields td {
  padding: 8px 16px;
  color: var(--ink);
  border-bottom: 1px solid var(--line);
  overflow-wrap: anywhere;
}

.fields td:last-child {
  width: 1%;
  text-align: right;
}

.key {
  font-family: ui-monospace, monospace;
  font-size: 12.5px;
}

/* A new field: name, value and the button, wrapping on a phone */
.add {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 12px 16px;
}

.field {
  appearance: none;
  flex: 1 1 150px;
  min-width: 0;
  box-sizing: border-box;
  font: inherit;
  font-size: 13px;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 11px;
}

.field::placeholder {
  color: var(--muted);
}

.field:focus-visible {
  outline: none;
  border-color: var(--focus);
  box-shadow: 0 0 0 3px rgb(125 182 255 / 0.35);
}

.button {
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  line-height: 1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 14px;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 1px 2px var(--shadow);
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.button.quiet {
  padding: 6px 10px;
  font-size: 12px;
  color: var(--danger);
  box-shadow: none;
}

.error {
  margin: 0;
  padding: 0 16px 12px;
  font-size: 12.5px;
  color: var(--danger);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

**`CustomFields.vue`**

```vue
<!-- Your own fields, kept in the document: add one, change one, remove one. -->
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { isPluginError } from '@embedpdf/vue/runtime';
import { useMetadata, useMetadataState } from '@embedpdf/vue/metadata';

const metadata = useMetadata();
const { custom } = useMetadataState();
const name = ref('');
const value = ref('');
const error = ref<string | null>(null);

// Two fields of our own, written on load. Setting the same values again changes nothing.
onMounted(() => {
  void metadata.custom.update({ contractId: 'C-2026-114', reviewedBy: 'dana' });
});

const canUpdate = computed(() => metadata.canUpdate());

async function add() {
  try {
    await metadata.custom.update({ [name.value.trim()]: value.value });
    name.value = '';
    value.value = '';
    error.value = null;
  } catch (failure) {
    // A name the PDF can't take, such as one of the standard fields.
    if (isPluginError(failure, 'invalid-input')) error.value = failure.message;
    else throw failure;
  }
}
</script>

<template>
  <section class="panel">
    <table class="fields">
      <thead>
        <tr>
          <th>Name</th>
          <th>Value</th>
          <th aria-label="Remove" />
        </tr>
      </thead>
      <tbody>
        <tr v-for="[key, text] in Object.entries(custom ?? {})" :key="key">
          <td class="key">{{ key }}</td>
          <td>{{ text }}</td>
          <td>
            <!-- null removes a field; the others stay as they are. -->
            <button
              type="button"
              class="button quiet"
              :disabled="!canUpdate"
              @click="metadata.custom.update({ [key]: null })"
            >
              Remove
            </button>
          </td>
        </tr>
      </tbody>
    </table>
    <form class="add" @submit.prevent="add">
      <input
        v-model="name"
        class="field"
        aria-label="Name"
        placeholder="Name, such as approvedOn"
      />
      <input v-model="value" class="field" aria-label="Value" placeholder="Value" />
      <button type="submit" class="button" :disabled="!canUpdate || !name.trim()">Add</button>
    </form>
    <p v-if="error" class="error">{{ error }}</p>
  </section>
</template>
```

## 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](#change)        |
| `custom.update(changes)`        | Changes your own fields: a string sets one, `null` removes it, the rest stay. Resolves `{ custom }`. [Your own fields](#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](#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](#permissions)                                           |

Every method that returns a promise also takes a `signal` in its options, to cancel it.

## State

Everything `useMetadataState()` gives you, as refs: your template updates when a value changes. 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](#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

**`title-notice.vue`**

```vue
<script setup lang="ts">
import { ref } from 'vue';
import { useMetadataEvent } from '@embedpdf/vue/metadata';

const notice = ref('');

useMetadataEvent(
  (metadata) => metadata.onUpdated,
  ({ metadata, origin }) => {
    if (origin.kind === 'remote') notice.value = `The title is now "${metadata.title}"`;
  },
);
</script>

<template>
  <p role="status">{{ notice }}</p>
</template>
```

| 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](https://www.embedpdf.com/docs/engine/documents/metadata) for the details.
