> ## Documentation Index
> Fetch the complete documentation index at: https://docs.superdoc.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Telemetry

export const DocCounter = ({height = '350px'}) => {
  const [documents, setDocuments] = useState(new Map());
  const [currentDoc, setCurrentDoc] = useState(null);
  const [ready, setReady] = useState(false);
  const [log, setLog] = useState([]);
  const superdocRef = useRef(null);
  const containerIdRef = useRef(`editor-${Math.random().toString(36).substr(2, 9)}`);
  const addLog = message => {
    setLog(prev => [...prev, {
      time: new Date().toLocaleTimeString(),
      message
    }]);
  };
  useEffect(() => {
    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = 'https://cdn.jsdelivr.net/npm/superdoc@latest/dist/style.css';
    document.head.appendChild(link);
    const bufferScript = document.createElement('script');
    bufferScript.src = 'https://cdn.jsdelivr.net/npm/buffer@6/index.min.js';
    bufferScript.onload = () => {
      window.Buffer = window.buffer.Buffer;
      const script = document.createElement('script');
      script.src = 'https://cdn.jsdelivr.net/npm/superdoc@latest/dist/superdoc.min.js';
      script.onload = () => setTimeout(() => initializeSuperdoc(), 100);
      document.body.appendChild(script);
    };
    document.body.appendChild(bufferScript);
    return () => superdocRef.current?.destroy?.();
  }, []);
  const getEditor = () => {
    return superdocRef.current?.activeEditor || superdocRef.current?.editor;
  };
  const trackDocument = async name => {
    const editor = getEditor();
    if (!editor) {
      setCurrentDoc({
        name,
        identifier: null,
        hasGuid: false
      });
      return;
    }
    let identifier = null;
    let guid = null;
    try {
      identifier = await editor.getDocumentIdentifier();
      guid = editor.getDocumentGuid();
    } catch (e) {
      addLog(`Error: ${e?.message || e}`);
    }
    if (identifier) {
      setDocuments(prev => {
        const next = new Map(prev);
        if (next.has(identifier)) {
          const existing = next.get(identifier);
          next.set(identifier, {
            ...existing,
            opens: existing.opens + 1
          });
          addLog(`Re-opened "${name}": same identifier, still counts as 1`);
        } else {
          next.set(identifier, {
            name,
            opens: 1,
            hasGuid: !!guid
          });
          addLog(`New document "${name}": identifier: ${identifier.slice(0, 12)}...`);
        }
        return next;
      });
    }
    setCurrentDoc({
      name,
      identifier,
      hasGuid: !!guid
    });
  };
  const initializeSuperdoc = (file = null) => {
    if (superdocRef.current) {
      superdocRef.current.destroy?.();
    }
    setReady(false);
    const config = {
      selector: `#${containerIdRef.current}`,
      telemetry: {
        enabled: false
      },
      onReady: async () => {
        setReady(true);
        if (file) {
          await trackDocument(file.name);
        }
      }
    };
    if (file) {
      config.document = {
        data: file,
        type: 'docx'
      };
    } else {
      config.html = '<p>Upload a DOCX file to see how document counting works.</p>';
    }
    if (window.SuperDoc) {
      superdocRef.current = new window.SuperDoc(config);
    }
  };
  const handleFileUpload = async e => {
    const file = e.target.files[0];
    if (!file?.name.endsWith('.docx')) return;
    addLog(`Uploading "${file.name}"...`);
    initializeSuperdoc(file);
    e.target.value = '';
  };
  const handleExportAndReimport = async () => {
    if (!superdocRef.current?.export) return;
    addLog('Exporting document...');
    const blob = await superdocRef.current.export();
    if (!blob) {
      addLog('Export returned no data');
      return;
    }
    const name = currentDoc?.name || 'document.docx';
    const file = new File([blob], name, {
      type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
    });
    addLog('Re-importing exported document...');
    initializeSuperdoc(file);
  };
  const uniqueCount = documents.size;
  return <div className="border rounded-lg bg-white overflow-hidden">
      {}
      <div style={{
    background: 'linear-gradient(135deg, #3b82f6, #1d4ed8)',
    color: 'white',
    padding: '12px 16px',
    display: 'flex',
    justifyContent: 'space-between',
    alignItems: 'center'
  }}>
        <div>
          <div style={{
    fontSize: '28px',
    fontWeight: 700,
    lineHeight: 1
  }}>{uniqueCount}</div>
          <div style={{
    fontSize: '11px',
    opacity: 0.85,
    marginTop: '2px'
  }}>
            Unique document{uniqueCount !== 1 ? 's' : ''} counted
          </div>
        </div>
        <div style={{
    display: 'flex',
    gap: '8px'
  }}>
          {ready && currentDoc && <button onClick={handleExportAndReimport} style={{
    padding: '6px 12px',
    background: 'rgba(255,255,255,0.2)',
    color: 'white',
    fontSize: '12px',
    borderRadius: '6px',
    cursor: 'pointer',
    border: '1px solid rgba(255,255,255,0.3)'
  }}>
              Export & re-import
            </button>}
          <label style={{
    padding: '6px 12px',
    background: 'white',
    color: '#2563eb',
    fontSize: '12px',
    borderRadius: '6px',
    cursor: 'pointer',
    fontWeight: 500
  }}>
            Upload DOCX
            <input type="file" accept=".docx" onChange={handleFileUpload} style={{
    display: 'none'
  }} />
          </label>
        </div>
      </div>

      {}
      {documents.size > 0 && <div style={{
    padding: '8px 16px',
    background: '#f9fafb',
    borderBottom: '1px solid #e5e7eb'
  }}>
          <div style={{
    fontSize: '11px',
    color: '#6b7280',
    marginBottom: '4px'
  }}>
            Documents counting toward usage:
          </div>
          <div style={{
    display: 'flex',
    gap: '6px',
    flexWrap: 'wrap'
  }}>
            {Array.from(documents.entries()).map(([id, doc]) => <div key={id} style={{
    fontSize: '11px',
    padding: '3px 8px',
    background: 'white',
    border: '1px solid #e5e7eb',
    borderRadius: '4px'
  }}>
                <span style={{
    fontWeight: 500
  }}>{doc.name}</span>
                {doc.opens > 1 && <span style={{
    color: '#3b82f6',
    marginLeft: '4px'
  }}>(opened {doc.opens}x: still 1)</span>}
              </div>)}
          </div>
        </div>}

      {}
      <div id={containerIdRef.current} style={{
    minHeight: height,
    paddingLeft: '5px',
    overflow: 'scroll'
  }} />

      {}
      {log.length > 0 && <div style={{
    padding: '8px 16px',
    background: '#1e293b',
    borderTop: '1px solid #e5e7eb',
    maxHeight: '120px',
    overflowY: 'auto',
    fontFamily: 'monospace'
  }}>
          {log.map((entry, i) => <div key={i} style={{
    fontSize: '11px',
    color: '#94a3b8',
    lineHeight: 1.6
  }}>
              <span style={{
    color: '#64748b'
  }}>{entry.time}</span>{' '}
              <span style={{
    color: '#e2e8f0'
  }}>{entry.message}</span>
            </div>)}
        </div>}

      <style jsx>{`
        #${containerIdRef.current} .superdoc__layers {
          max-width: 660px !important;
        }
        #${containerIdRef.current} .super-editor-container {
          min-width: unset !important;
          min-height: unset !important;
          width: 100% !important;
        }
        #${containerIdRef.current} .super-editor {
          max-width: 100% !important;
          width: 100% !important;
          color: #000;
        }
        #${containerIdRef.current} .editor-element {
          min-height: ${height} !important;
          width: 100% !important;
          min-width: unset !important;
          transform: none !important;
        }
        #${containerIdRef.current} .editor-element {
          h1,
          h2,
          h3,
          h4,
          h5,
          strong {
            color: #000;
          }
        }
      `}</style>
    </div>;
};

SuperDoc collects lightweight telemetry to track document opens for usage-based billing. Telemetry is enabled by default and runs silently in the background: it never blocks rendering or breaks your app.

## What gets collected

Each time a document is opened or imported, SuperDoc sends a single event containing:

| Field               | Description                                           | Example                                |
| ------------------- | ----------------------------------------------------- | -------------------------------------- |
| `documentId`        | A hashed document identifier (not the content itself) | `a1b2c3d4...`                          |
| `documentCreatedAt` | The document's original creation timestamp            | `2024-01-15T10:30:00Z`                 |
| `superdocVersion`   | The version of the SuperDoc library                   | `1.15.0`                               |
| `browserInfo`       | User agent, hostname, and screen size                 | `{ hostname: "app.example.com", ... }` |
| `metadata`          | Custom key-value pairs you optionally provide         | `{ customerId: "123" }`                |

SuperDoc does **not** collect:

* Document content or text
* User identities or personal data
* Keystrokes, edits, or change history
* Cookies or session tokens (`credentials: 'omit'`)

## How it works

1. You open or import a document
2. SuperDoc generates a **document identifier**: a hash derived from the file's metadata (not its content)
3. A single `POST` request fires to the telemetry endpoint
4. If the request fails (network error, blocked by firewall), it fails silently

```
Document opened → Generate identifier hash → POST to endpoint → Done
```

<Note>
  The telemetry request is non-blocking and fire-and-forget. Your editor loads regardless of the outcome.
</Note>

## Configuration

Pass `licenseKey` and `telemetry` in your SuperDoc or SuperEditor config:

<CodeGroup>
  ```javascript SuperDoc theme={null}
  import { SuperDoc } from 'superdoc';

  const superdoc = new SuperDoc({
    selector: '#editor',
    document: yourFile,
    licenseKey: 'your-license-key',
    telemetry: {
      enabled: true,
    },
  });
  ```

  ```javascript SuperEditor theme={null}
  import { Editor } from 'superdoc/super-editor';

  const editor = await Editor.open(file, {
    element: document.querySelector('#editor'),
    licenseKey: 'your-license-key',
    telemetry: {
      enabled: true,
    },
  });
  ```
</CodeGroup>

### Options

<ParamField path="licenseKey" type="string">
  Your organization's license key. Links document opens to your account for billing.
  Defaults to `community-and-eval-agplv3` if not provided.
</ParamField>

<ParamField path="telemetry.enabled" type="boolean">
  Enable or disable telemetry. Default: `true`.
</ParamField>

<ParamField path="telemetry.endpoint" type="string">
  Override the telemetry endpoint. Default: `https://ingest.superdoc.dev/v1/collect`. Useful for proxying through your own infrastructure.
</ParamField>

<ParamField path="telemetry.metadata" type="Record<string, unknown>">
  Custom key-value pairs included with every event. Use this to attach your own identifiers (customer ID, environment, etc.).
</ParamField>

<ParamField path="telemetry.licenseKey" type="string" deprecated>
  **Deprecated.** Use the root-level `licenseKey` instead. If both are provided, the root-level key takes priority.
</ParamField>

### Disabling telemetry

Set `enabled: false` to turn telemetry off entirely:

```javascript theme={null}
const superdoc = new SuperDoc({
  selector: '#editor',
  document: yourFile,
  telemetry: {
    enabled: false,
  },
});
```

No network requests will be made. Document metadata (GUID and timestamp) is still generated locally so files export correctly.

### Self-reporting

You can route telemetry through your own server using `telemetry.endpoint`. SuperDoc sends the same JSON payload to whatever URL you provide: inspect it, store it, forward it, or drop it.

```javascript theme={null}
const superdoc = new SuperDoc({
  selector: '#editor',
  document: yourFile,
  licenseKey: 'your-license-key',
  telemetry: {
    enabled: true,
    endpoint: 'https://your-server.com/superdoc/telemetry',
  },
});
```

Your server receives a `POST` with the payload described in [Payload example](#payload-example). From there you can aggregate usage on your side: count document opens per customer, track adoption across teams, or feed the data into your own billing pipeline.

<Note>
  A dedicated self-reporting API with dashboards and usage summaries is under development and will be available by Feb 28, 2026. In the meantime, proxying via `telemetry.endpoint` gives you full control over the raw data.
</Note>

## License key

The license key identifies your organization. It's sent as an `X-License-Key` header with every telemetry request.

| License type           | Key                                   | How to get it                                 |
| ---------------------- | ------------------------------------- | --------------------------------------------- |
| Community / evaluation | `community-and-eval-agplv3` (default) | No action needed: used automatically          |
| Commercial             | Your organization key                 | Email [q@superdoc.dev](mailto:q@superdoc.dev) |

If you're evaluating SuperDoc or using it under AGPLv3, you don't need to configure a license key. The community key is applied automatically.

For a commercial license key, email [q@superdoc.dev](mailto:q@superdoc.dev). You'll receive a key tied to your organization that unlocks usage tracking for your account.

## How document uniqueness works

SuperDoc needs to identify each document uniquely so that opening the same file twice counts as one document, not two. This matters for accurate billing.

### Identifier strategies

SuperDoc uses two strategies depending on what metadata the file contains:

**1. Metadata hash: file already has a GUID and timestamp**

DOCX files created by Microsoft Word (and other tools) typically contain a unique GUID and a creation timestamp in their internal metadata (`docProps/` and `word/settings.xml`). When both are present, SuperDoc hashes them together:

```
documentId = hash(GUID | creationTimestamp)   →   "HASH-A1B2C3D4"
```

This is the most stable strategy. The same file always produces the same identifier regardless of content edits, because the hash is derived from metadata, not content.

**2. Generated identifier: file is missing metadata**

Some tools produce DOCX files without a GUID or timestamp. When either is missing, SuperDoc does two things:

1. **For the current open**: generates a content hash of the raw file bytes so the same file still produces a consistent identifier
2. **For future opens**: generates the missing GUID and/or timestamp and embeds them into the document's metadata

The generated metadata is saved when you export. On the next import, the file now has both a GUID and timestamp, so SuperDoc switches to the stable metadata hash automatically.

```
First open:  no GUID in file → content hash used, GUID + timestamp generated
Export:      GUID + timestamp written into the file
Next open:   GUID + timestamp found → metadata hash (stable from now on)
```

This self-healing behavior means document identification improves automatically as files pass through SuperDoc. After one export cycle, every document gets a permanent, edit-resistant identifier.

## Payload example

Here's what a telemetry request looks like on the wire:

```
POST https://ingest.superdoc.dev/v1/collect
Content-Type: application/json
X-License-Key: your-license-key
```

```json theme={null}
{
  "superdocVersion": "1.15.0",
  "browserInfo": {
    "userAgent": "Mozilla/5.0...",
    "currentUrl": "https://app.example.com/doc/123",
    "hostname": "app.example.com",
    "screenSize": { "width": 1920, "height": 1080 }
  },
  "metadata": {
    "customerId": "cust-456"
  },
  "events": [
    {
      "timestamp": "2026-02-12T14:30:00.000Z",
      "documentId": "a1b2c3d4e5f6...",
      "documentCreatedAt": "2024-01-15T10:30:00Z"
    }
  ]
}
```

## Try it: document counting demo

See document uniqueness in action. Upload a DOCX file and watch the counter: then try the steps below.

<DocCounter height="300px" />

<Steps>
  <Step title="Upload a DOCX file">
    Click **Upload DOCX** and pick any `.docx` file. The counter goes to **1**. SuperDoc reads the file's internal metadata and generates a document identifier.
  </Step>

  <Step title="Edit the document">
    Make some changes in the editor: add text, delete a paragraph, change formatting. The identifier stays the same because it's based on metadata, not content.
  </Step>

  <Step title="Export and re-import">
    Click **Export & re-import**. SuperDoc exports the document to DOCX and immediately re-imports it. The counter stays at **1**: same metadata, same identifier.
  </Step>

  <Step title="Upload a different file">
    Upload a second DOCX file. The counter goes to **2**. Each file with distinct metadata gets its own identifier.
  </Step>

  <Step title="Re-upload the first file">
    Upload the original file again. The counter stays at **2**. SuperDoc recognizes it as the same document.
  </Step>
</Steps>

<Info>
  The event log at the bottom shows exactly what SuperDoc sees: the identifier hash, whether a document is new or recognized, and how many times each has been opened.
</Info>

## FAQ

<AccordionGroup>
  <Accordion title="Does telemetry affect performance?">
    No. The request is non-blocking and fire-and-forget. Your editor loads and renders regardless of the telemetry outcome.
  </Accordion>

  <Accordion title="What happens if telemetry fails?">
    Nothing. Errors are caught silently. No retries, no queuing, no user-visible errors. Your app continues to work normally.
  </Accordion>

  <Accordion title="Can I route telemetry through my own server?">
    Yes. Set `telemetry.endpoint` to your proxy URL. The payload format stays the same.
  </Accordion>

  <Accordion title="Does the same document get counted twice?">
    Not if it has metadata. SuperDoc hashes the document's GUID and creation timestamp, so the same file produces the same identifier every time. See [How document uniqueness works](#how-document-uniqueness-works) above.
  </Accordion>

  <Accordion title="Do I need a license key for open-source use?">
    No. The community key (`community-and-eval-agplv3`) is applied automatically when you don't provide one.
  </Accordion>
</AccordionGroup>
