diff --git a/apps/docs/resources/telemetry.mdx b/apps/docs/resources/telemetry.mdx
index 80ffdb7b45..05aa09d0c7 100644
--- a/apps/docs/resources/telemetry.mdx
+++ b/apps/docs/resources/telemetry.mdx
@@ -91,6 +91,10 @@ const editor = await Editor.open(file, {
Custom key-value pairs included with every event. Use this to attach your own identifiers (customer ID, environment, etc.).
+
+ **Deprecated.** Use the root-level `licenseKey` instead. If both are provided, the root-level key takes priority.
+
+
### Disabling telemetry
Set `enabled: false` to turn telemetry off entirely:
@@ -107,6 +111,28 @@ const superdoc = new SuperDoc({
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
+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.
+
+
+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.
+
+
## License key
The license key identifies your organization. It's sent as an `X-License-Key` header with every telemetry request.
diff --git a/packages/super-editor/src/core/Editor.telemetry.test.ts b/packages/super-editor/src/core/Editor.telemetry.test.ts
index 9a9fcc2e7d..4f27803d80 100644
--- a/packages/super-editor/src/core/Editor.telemetry.test.ts
+++ b/packages/super-editor/src/core/Editor.telemetry.test.ts
@@ -12,7 +12,12 @@ vi.mock('@superdoc/common', () => ({
// Test the telemetry initialization logic in isolation
// This mirrors the #initTelemetry method in Editor.ts
function initTelemetry(options: {
- telemetry?: { enabled: boolean; endpoint?: string; metadata?: Record } | null;
+ telemetry?: {
+ enabled: boolean;
+ endpoint?: string;
+ metadata?: Record;
+ licenseKey?: string | null;
+ } | null;
licenseKey?: string;
}): Telemetry | null {
const { telemetry: telemetryConfig, licenseKey } = options;
@@ -22,11 +27,15 @@ function initTelemetry(options: {
return null;
}
+ // Root-level licenseKey has a priority; fall back to deprecated telemetry.licenseKey
+ const resolvedLicenseKey =
+ licenseKey !== undefined ? licenseKey : (telemetryConfig.licenseKey ?? COMMUNITY_LICENSE_KEY);
+
try {
return new Telemetry({
enabled: true,
endpoint: telemetryConfig.endpoint,
- licenseKey: licenseKey === undefined ? COMMUNITY_LICENSE_KEY : licenseKey,
+ licenseKey: resolvedLicenseKey,
metadata: telemetryConfig.metadata,
});
} catch {
@@ -122,6 +131,51 @@ describe('Editor Telemetry Integration', () => {
});
});
+ describe('deprecated telemetry.licenseKey', () => {
+ it('uses telemetry.licenseKey when root licenseKey is not provided', () => {
+ const result = initTelemetry({
+ telemetry: { enabled: true, licenseKey: 'deprecated-key' },
+ });
+
+ expect(result).not.toBeNull();
+ expect(Telemetry).toHaveBeenCalledWith({
+ enabled: true,
+ endpoint: undefined,
+ licenseKey: 'deprecated-key',
+ metadata: undefined,
+ });
+ });
+
+ it('root licenseKey wins over telemetry.licenseKey', () => {
+ const result = initTelemetry({
+ telemetry: { enabled: true, licenseKey: 'deprecated-key' },
+ licenseKey: 'root-key',
+ });
+
+ expect(result).not.toBeNull();
+ expect(Telemetry).toHaveBeenCalledWith({
+ enabled: true,
+ endpoint: undefined,
+ licenseKey: 'root-key',
+ metadata: undefined,
+ });
+ });
+
+ it('falls back to COMMUNITY_LICENSE_KEY when both are absent', () => {
+ const result = initTelemetry({
+ telemetry: { enabled: true },
+ });
+
+ expect(result).not.toBeNull();
+ expect(Telemetry).toHaveBeenCalledWith({
+ enabled: true,
+ endpoint: undefined,
+ licenseKey: 'community-and-eval-agplv3',
+ metadata: undefined,
+ });
+ });
+ });
+
describe('telemetry with custom endpoint', () => {
it('passes custom endpoint to Telemetry', () => {
const customEndpoint = 'https://custom.telemetry.com/v1/events';
diff --git a/packages/super-editor/src/core/Editor.ts b/packages/super-editor/src/core/Editor.ts
index 2175be551e..4e18b8770c 100644
--- a/packages/super-editor/src/core/Editor.ts
+++ b/packages/super-editor/src/core/Editor.ts
@@ -339,8 +339,8 @@ export class Editor extends EventEmitter {
// header/footer editors may have parent(main) editor set
parentEditor: null,
- // License key (defaults to community license)
- licenseKey: COMMUNITY_LICENSE_KEY,
+ // License key (resolved in #initTelemetry; undefined means "not explicitly set")
+ licenseKey: undefined,
// Telemetry configuration
telemetry: { enabled: true },
@@ -493,11 +493,15 @@ export class Editor extends EventEmitter {
return;
}
+ // Root-level licenseKey has a priority; fall back to deprecated telemetry.licenseKey
+ const resolvedLicenseKey =
+ licenseKey !== undefined ? licenseKey : (telemetryConfig.licenseKey ?? COMMUNITY_LICENSE_KEY);
+
try {
this.#telemetry = new Telemetry({
enabled: true,
endpoint: telemetryConfig.endpoint,
- licenseKey: licenseKey === undefined ? COMMUNITY_LICENSE_KEY : licenseKey,
+ licenseKey: resolvedLicenseKey,
metadata: telemetryConfig.metadata,
});
console.debug('[super-editor] Telemetry: enabled');
diff --git a/packages/super-editor/src/core/types/EditorConfig.ts b/packages/super-editor/src/core/types/EditorConfig.ts
index 478b022514..50bbbb56d3 100644
--- a/packages/super-editor/src/core/types/EditorConfig.ts
+++ b/packages/super-editor/src/core/types/EditorConfig.ts
@@ -432,5 +432,9 @@ export interface EditorOptions {
endpoint?: string;
/** Custom metadata to include with telemetry events (optional) */
metadata?: Record;
+ /**
+ * @deprecated Use root-level `licenseKey` instead. If both are provided, root-level has priority.
+ */
+ licenseKey?: string | null;
} | null;
}
diff --git a/packages/superdoc/src/SuperDoc.vue b/packages/superdoc/src/SuperDoc.vue
index 7a8966dd24..379f415302 100644
--- a/packages/superdoc/src/SuperDoc.vue
+++ b/packages/superdoc/src/SuperDoc.vue
@@ -527,6 +527,7 @@ const editorOptions = (doc) => {
enabled: true,
endpoint: proxy.$superdoc.config.telemetry?.endpoint,
metadata: proxy.$superdoc.config.telemetry?.metadata,
+ licenseKey: proxy.$superdoc.config.telemetry?.licenseKey,
}
: null,
};
diff --git a/packages/superdoc/src/core/SuperDoc.js b/packages/superdoc/src/core/SuperDoc.js
index 1e1ac28d03..770a7f59cf 100644
--- a/packages/superdoc/src/core/SuperDoc.js
+++ b/packages/superdoc/src/core/SuperDoc.js
@@ -4,7 +4,7 @@ import { EventEmitter } from 'eventemitter3';
import { v4 as uuidv4 } from 'uuid';
import { HocuspocusProviderWebsocket } from '@hocuspocus/provider';
-import { DOCX, PDF, HTML, COMMUNITY_LICENSE_KEY } from '@superdoc/common';
+import { DOCX, PDF, HTML } from '@superdoc/common';
import { SuperToolbar, createZip } from '@superdoc/super-editor';
import { SuperComments } from '../components/CommentsLayer/commentsList/super-comments-list.js';
import { createSuperdocVueApp } from './create-app.js';
@@ -79,8 +79,8 @@ export class SuperDoc extends EventEmitter {
modules: {}, // Optional: Modules to load. Use modules.ai.{your_key} to pass in your key
permissionResolver: null, // Optional: Override for permission checks
- // License key for organization identification (defaults to community key)
- licenseKey: COMMUNITY_LICENSE_KEY,
+ // License key (resolved downstream; undefined means "not explicitly set")
+ licenseKey: undefined,
// Telemetry settings
telemetry: { enabled: true },
diff --git a/packages/superdoc/src/core/types/index.js b/packages/superdoc/src/core/types/index.js
index 88d857adc2..6074268928 100644
--- a/packages/superdoc/src/core/types/index.js
+++ b/packages/superdoc/src/core/types/index.js
@@ -193,6 +193,12 @@
* @property {boolean} [isDebug=false] Whether to enable debug mode
* @property {ViewOptions} [viewOptions] Document view options (OOXML ST_View compatible)
* @property {string} [cspNonce] Content Security Policy nonce for dynamically injected styles
+ * @property {string} [licenseKey] License key for organization identification
+ * @property {{ enabled: boolean, endpoint?: string, metadata?: Record, licenseKey?: string }} [telemetry] Telemetry configuration
+ * @property {boolean} telemetry.enabled Whether telemetry is enabled
+ * @property {string} [telemetry.endpoint] Custom telemetry endpoint
+ * @property {Record} [telemetry.metadata] Custom metadata to include with telemetry events
+ * @property {string} [telemetry.licenseKey] @deprecated Use root-level `licenseKey` instead
*/
export {};
diff --git a/packages/superdoc/src/telemetry.test.ts b/packages/superdoc/src/telemetry.test.ts
index 8830cb3064..7d67e70cf4 100644
--- a/packages/superdoc/src/telemetry.test.ts
+++ b/packages/superdoc/src/telemetry.test.ts
@@ -7,10 +7,16 @@ function transformTelemetryConfig(superdocConfig: {
enabled?: boolean;
endpoint?: string;
metadata?: Record;
+ licenseKey?: string | null;
} | null;
licenseKey?: string | null;
}): {
- telemetry: { enabled: boolean; endpoint?: string; metadata?: Record } | null;
+ telemetry: {
+ enabled: boolean;
+ endpoint?: string;
+ metadata?: Record;
+ licenseKey?: string | null;
+ } | null;
licenseKey?: string | null;
} {
return {
@@ -20,6 +26,7 @@ function transformTelemetryConfig(superdocConfig: {
enabled: true,
endpoint: superdocConfig.telemetry?.endpoint,
metadata: superdocConfig.telemetry?.metadata,
+ licenseKey: superdocConfig.telemetry?.licenseKey,
}
: null,
};
@@ -144,6 +151,31 @@ describe('SuperDoc Telemetry Configuration', () => {
});
});
+ describe('deprecated telemetry.licenseKey passthrough', () => {
+ it('passes deprecated telemetry.licenseKey to editor config', () => {
+ const result = transformTelemetryConfig({
+ telemetry: { enabled: true, licenseKey: 'deprecated-key' },
+ });
+
+ expect(result.telemetry).toEqual({
+ enabled: true,
+ endpoint: undefined,
+ metadata: undefined,
+ licenseKey: 'deprecated-key',
+ });
+ });
+
+ it('passes both root licenseKey and deprecated telemetry.licenseKey', () => {
+ const result = transformTelemetryConfig({
+ telemetry: { enabled: true, licenseKey: 'deprecated-key' },
+ licenseKey: 'root-key',
+ });
+
+ expect(result.licenseKey).toBe('root-key');
+ expect(result.telemetry?.licenseKey).toBe('deprecated-key');
+ });
+ });
+
describe('full configuration flow', () => {
it('transforms complete SuperDoc config to Editor config', () => {
const superdocConfig = {