# Fill the service agreement

The same six tagged text fields as the homepage. Fill the form, update every matching occurrence, then export the agreement. This sample has one occurrence per tag; the helper also handles repeated tags in your templates.

## Run

Requires Node.js 22 or newer. `npm install` then `npm run dev`. Open the localhost URL Vite prints. To check and build the project, run `npm run check` and `npm run build`.

Browser editor: 2.30.0. Direct dependencies are pinned in package.json.

## Try it

The application values replace the placeholders in the DOCX. A missing tag or failed update produces an error instead of a success message.

The included public DOCX is the original homepage sample. It is not your uploaded document or the current edited demo state. Export a separate DOCX, then reopen it in Word or SuperDoc. Built-in and custom review examples use Suggesting mode. The application form fills text fields directly.

## Files

- package.json
- examples/template/main.ts
- examples/template/index.html
- examples/shared/browser.ts
- examples/shared/style.css
- examples/shared/fields.ts
- vite.config.ts
- tsconfig.json
- public/testkit_docs/template-service-agreement.docx

## Continue

[Current documentation](https://docs.superdoc.dev/editor/content-controls/fill-a-docx-template/)
[Try the homepage demo](https://www.superdoc.dev/?doc=template#editor)
[SuperDoc licensing](https://docs.superdoc.dev/editor/license/)

The shared browser.ts file contains the export button, status messages and CSS imports. Document setup and task logic are in main.ts.

## Complete source

### package.json

```json
{
  "name": "superdoc-template",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "engines": {
    "node": ">=22"
  },
  "scripts": {
    "dev": "vite --open /examples/template/",
    "build": "vite build",
    "check": "tsc --noEmit"
  },
  "dependencies": {
    "superdoc": "2.30.0"
  },
  "devDependencies": {
    "vite": "6.4.1",
    "typescript": "5.8.3"
  }
}
```

### examples/template/main.ts

```javascript
import { SuperDoc } from 'superdoc';
import { element, enableExport, report, showError } from '../shared/browser';
import { fillTextFields } from '../shared/fields';

const form = element<HTMLFormElement>('fields');
const apply = element<HTMLButtonElement>('apply');

new SuperDoc({
  selector: '#editor',
  document: '/testkit_docs/template-service-agreement.docx',
  ui: { toolbar: { container: '#toolbar', responsiveTo: 'container' }, contentControls: true },
  onReady: ({ superdoc }) => {
    enableExport(superdoc);
    apply.disabled = false;
    form.addEventListener('submit', async event => {
      event.preventDefault();
      if (apply.disabled) return;
      apply.disabled = true;
      element<HTMLButtonElement>('export').disabled = true;
      try {
        const doc = superdoc.activeEditor?.doc;
        if (!doc) throw new Error('The document is not ready.');
        // Validate first, then update every distinct occurrence of each tag.
        const values = [...new FormData(form)].map(([tag, value]): [string, string] => [tag, String(value)]);
        const updated = await fillTextFields(doc, values);
        report(`Updated ${updated} field occurrences. Export to check the saved agreement.`);
      } catch (error) { showError(error); }
      finally {
        apply.disabled = false;
        element<HTMLButtonElement>('export').disabled = false;
      }
    });
  },
  onContentError: ({ error }) => showError(error),
  onException: ({ error }) => showError(error),
});
```

### examples/template/index.html

```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fill the service agreement | SuperDoc example</title>
<meta name="description" content="Fill the service agreement using SuperDoc and the homepage sample.">
<link rel="canonical" href="https://www.superdoc.dev/examples/template/index.html">
<link rel="alternate" type="text/markdown" href="https://www.superdoc.dev/examples/template.md">
</head>
<body>
<header>
<h1>Fill the service agreement</h1>
<p>The same six tagged text fields as the homepage. Fill the form, update every matching occurrence, then export the agreement. This sample has one occurrence per tag; the helper also handles repeated tags in your templates.</p>
<div class="actions">
<button id="export" type="button" disabled>Export DOCX</button>
<output id="status" role="status">Opening document…</output>
</div>
<p>
<a href="https://docs.superdoc.dev/editor/content-controls/fill-a-docx-template/">Current documentation</a> · <a href="https://www.superdoc.dev/examples/template.zip">Download this project</a> · <a href="https://www.superdoc.dev/examples/template.md">Read the complete source</a>
</p>
</header>
<div id="toolbar">
</div>
<main>
<div id="editor">
</div>
<aside aria-labelledby="fields-heading">
<h2 id="fields-heading">Your application form</h2>
<form id="fields">
<label>Client legal name<input name="client_legal_name" value="Example Company LLC" required>
</label>
<label>Effective date<input name="effective_date" value="November 11, 2026" required>
</label>
<label>Project name<input name="project_name" value="Customer portal" required>
</label>
<label>Project description<input name="project_description" value="Build a customer portal with in-product DOCX editing." required>
</label>
<label>Completion date<input name="completion_date" value="December 15, 2026" required>
</label>
<label>Project fee (USD)<input name="project_fee" value="$24,000" required>
</label>
<button id="apply" type="submit" disabled>Fill agreement</button>
</form>
</aside>
</main>
<script type="module" src="./main.ts">
</script>
</body>
</html>
```

### examples/shared/browser.ts

```javascript
import type { SuperDoc } from 'superdoc';
import 'superdoc/style.css';
import './style.css';

export function element<T extends HTMLElement>(id: string): T {
  const node = document.getElementById(id);
  if (!node) throw new Error(`Missing element: ${id}`);
  return node as T;
}

const status = element<HTMLOutputElement>('status');
const exportButton = element<HTMLButtonElement>('export');
export function report(message: string) { status.value = message; }
export function showError(error: unknown) {
  report(error instanceof Error ? error.message : String(error));
}

export function enableExport(superdoc: SuperDoc) {
  report('Ready. Edit the document, then export a copy.');
  exportButton.disabled = false;
  exportButton.addEventListener('click', async () => {
    exportButton.disabled = true;
    try {
      await superdoc.export({ exportType: ['docx'], exportedName: 'edited-example', triggerDownload: true });
      report('DOCX exported. Open the saved file to check your changes.');
    } catch (error) { showError(error); }
    finally { exportButton.disabled = false; }
  });
}
```

### examples/shared/style.css

```css
:root { font-family: system-ui, sans-serif; color: #242424; background: #f5f5f5; }
#editor { --sd-caret-color: #242424; }
input, textarea, [contenteditable="true"] { caret-color: #242424; }
* { box-sizing: border-box; }
body { margin: 0; }
header { padding: 1rem; border-bottom: 1px solid #ddd; background: white; }
h1 { font-size: 1.2rem; margin: 0 0 .5rem; }
p { line-height: 1.5; }
button, input { font: inherit; padding: .5rem .7rem; min-height: 44px; }
button { cursor: pointer; }
button:disabled { cursor: wait; }
label { display: grid; gap: .3rem; margin-bottom: .7rem; }
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: .7rem; }
main { display: grid; grid-template-columns: minmax(0, 1fr) 300px; }
main.solo { display: block; }
#editor { height: 76vh; overflow: auto; min-width: 0; }
aside { padding: 1rem; background: white; overflow: auto; max-height: 76vh; }
ul { list-style: none; padding: 0; }
li { border-bottom: 1px solid #ddd; padding: .7rem 0; }
li button { margin: .2rem; }
input { width: 100%; }
@media (max-width: 760px) { main { grid-template-columns: minmax(0, 1fr); } aside { max-height: none; } }
```

### examples/shared/fields.ts

```javascript
import type { BrowserDocumentApi } from 'superdoc/ui';

export async function fillTextFields(doc: Pick<BrowserDocumentApi, 'contentControls'>, values: Iterable<[string, string]>) {
  const matches = await Promise.all([...values].map(async ([tag, value]) => {
    const { items } = await doc.contentControls.selectByTag({ tag });
    if (!items.length || items.some(item => item.controlType !== 'text')) {
      throw new Error(`Expected text fields for ${tag}. Check the template.`);
    }
    return { items, value };
  }));
  let completed = 0;
  for (const { items, value } of matches) {
    for (const { target } of items) {
      const result = await doc.contentControls.text.setValue({ target, value });
      if (!result.success) throw new Error(`Field update failed after ${completed} completed updates. Review before exporting.`);
      completed++;
    }
  }
  return completed;
}
```

### vite.config.ts

```javascript
import { defineConfig } from 'vite';
import { resolve } from 'node:path';
export default defineConfig({
  optimizeDeps: { exclude: ['@superdoc/docx-engine'], include: ['vue'] },
  resolve: { dedupe: ['vue'] },
  build: { rollupOptions: { input: resolve('examples/template/index.html') } },
});
```

### tsconfig.json

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "lib": [
      "ES2022",
      "DOM",
      "DOM.Iterable"
    ],
    "strict": true,
    "skipLibCheck": true,
    "noEmit": true,
    "types": []
  },
  "include": [
    "examples/**/*.ts"
  ]
}
```

[Download the project](https://www.superdoc.dev/examples/template.zip)
[Download the public sample](https://www.superdoc.dev/testkit_docs/template-service-agreement.docx)
