diff --git a/apps/docs/extensions/structured-content.mdx b/apps/docs/extensions/structured-content.mdx
index fe7dd0c19c..35163304c9 100644
--- a/apps/docs/extensions/structured-content.mdx
+++ b/apps/docs/extensions/structured-content.mdx
@@ -46,11 +46,140 @@ Node attributes that can be set and retrieved:
Display name for the block
+
+ Controls editing and deletion restrictions on the structured content node. See [Lock modes](#lock-modes) below.
+
+
+## Lock modes
+
+Structured content nodes support four lock modes based on the OOXML [`w:lock` element](https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.wordprocessing.lock) (ISO/IEC 29500 §17.5.2.23). Lock modes control whether users can edit the content inside a field and whether they can delete the field wrapper itself.
+
+| Lock mode | Wrapper | Content | Use case |
+|-----------|---------|---------|----------|
+| `unlocked` | Deletable | Editable | Default — no restrictions |
+| `sdtLocked` | Protected | Editable | Protect field structure, allow value changes |
+| `contentLocked` | Deletable | Read-only | Display a computed value users can remove |
+| `sdtContentLocked` | Protected | Read-only | Fully protected field (e.g., system-generated ID) |
+
+Set the lock mode when inserting:
+
+
+```javascript Usage
+editor.commands.insertStructuredContentInline({
+ attrs: {
+ id: '3',
+ alias: 'Account ID',
+ lockMode: 'sdtContentLocked',
+ },
+ text: 'ACC-00042',
+});
+```
+
+```javascript Full Example
+import { SuperDoc } from 'superdoc';
+import 'superdoc/style.css';
+
+const superdoc = new SuperDoc({
+ selector: '#editor',
+ document: yourFile,
+ onReady: (superdoc) => {
+ const editor = superdoc.activeEditor;
+ editor.commands.insertStructuredContentInline({
+ attrs: {
+ id: '3',
+ alias: 'Account ID',
+ lockMode: 'sdtContentLocked',
+ },
+ text: 'ACC-00042',
+ });
+ },
+});
+```
+
+
+Change the lock mode on an existing field:
+
+
+```javascript Usage
+editor.commands.updateStructuredContentById('1', {
+ attrs: { lockMode: 'contentLocked' },
+});
+```
+
+```javascript Full Example
+import { SuperDoc } from 'superdoc';
+import 'superdoc/style.css';
+
+const superdoc = new SuperDoc({
+ selector: '#editor',
+ document: yourFile,
+ onReady: (superdoc) => {
+ const editor = superdoc.activeEditor;
+ editor.commands.updateStructuredContentById('1', {
+ attrs: { lockMode: 'contentLocked' },
+ });
+ },
+});
+```
+
+
+
+Lock modes are enforced at the editor plugin level using a three-layer defense:
+
+1. **Key interception** — Delete, Backspace, and Cut are blocked before a transaction is created, preventing cursor jumps
+2. **Text input blocking** — Typing is silently blocked in content-locked nodes
+3. **Transaction filter** — Safety net that catches paste, drag-drop, and programmatic edits
+
+Users can still place their cursor inside locked content and select text for copying. Only modifications are blocked.
+
+
+
+ Lock modes round-trip through DOCX. A document with `w:lock` elements in
+ its SDT properties will import with the correct lock mode and export the
+ `w:lock` element back to the saved file.
+
+
## Commands
### `insertStructuredContentInline`
-Inserts a structured content inline at selection.
+Inserts a structured content inline at the current selection.
+
+**Example:**
+
+
+```javascript Usage
+editor.commands.insertStructuredContentInline({
+ attrs: {
+ id: '1',
+ alias: 'Customer Name',
+ lockMode: 'sdtLocked', // optional, defaults to 'unlocked'
+ },
+ text: 'John Doe',
+});
+```
+
+```javascript Full Example
+import { SuperDoc } from 'superdoc';
+import 'superdoc/style.css';
+
+const superdoc = new SuperDoc({
+ selector: '#editor',
+ document: yourFile,
+ onReady: (superdoc) => {
+ const editor = superdoc.activeEditor;
+ editor.commands.insertStructuredContentInline({
+ attrs: {
+ id: '1',
+ alias: 'Customer Name',
+ lockMode: 'sdtLocked',
+ },
+ text: 'John Doe',
+ });
+ },
+});
+```
+
**Parameters:**
@@ -62,7 +191,43 @@ Inserts a structured content inline at selection.
### `insertStructuredContentBlock`
-Inserts a structured content block at selection.
+Inserts a structured content block at the current selection.
+
+**Example:**
+
+
+```javascript Usage
+editor.commands.insertStructuredContentBlock({
+ attrs: {
+ id: '2',
+ alias: 'Terms Section',
+ lockMode: 'sdtContentLocked', // optional, defaults to 'unlocked'
+ },
+ html: 'These terms are non-negotiable.
',
+});
+```
+
+```javascript Full Example
+import { SuperDoc } from 'superdoc';
+import 'superdoc/style.css';
+
+const superdoc = new SuperDoc({
+ selector: '#editor',
+ document: yourFile,
+ onReady: (superdoc) => {
+ const editor = superdoc.activeEditor;
+ editor.commands.insertStructuredContentBlock({
+ attrs: {
+ id: '2',
+ alias: 'Terms Section',
+ lockMode: 'sdtContentLocked',
+ },
+ html: 'These terms are non-negotiable.
',
+ });
+ },
+});
+```
+
**Parameters:**
@@ -78,6 +243,53 @@ Updates a single structured content field by its unique ID.
IDs are unique identifiers, so this will update at most one field.
If the updated node does not match the schema, it will not be updated.
+Pass `attrs` alone to change attributes (like `lockMode`) without replacing content:
+
+**Example:**
+
+
+```javascript Usage
+// Update content
+editor.commands.updateStructuredContentById('1', {
+ text: 'Jane Smith',
+});
+
+// Update lock mode only (preserves content)
+editor.commands.updateStructuredContentById('1', {
+ attrs: { lockMode: 'contentLocked' },
+});
+
+// Update both content and attributes
+editor.commands.updateStructuredContentById('1', {
+ text: 'New value',
+ attrs: { alias: 'Updated Label', lockMode: 'sdtLocked' },
+});
+```
+
+```javascript Full Example
+import { SuperDoc } from 'superdoc';
+import 'superdoc/style.css';
+
+const superdoc = new SuperDoc({
+ selector: '#editor',
+ document: yourFile,
+ onReady: (superdoc) => {
+ const editor = superdoc.activeEditor;
+
+ // Update content
+ editor.commands.updateStructuredContentById('1', {
+ text: 'Jane Smith',
+ });
+
+ // Update lock mode only (preserves content)
+ editor.commands.updateStructuredContentById('1', {
+ attrs: { lockMode: 'contentLocked' },
+ });
+ },
+});
+```
+
+
**Parameters:**
@@ -456,6 +668,12 @@ const superdoc = new SuperDoc({
## Types
+### `StructuredContentLockMode`
+
+```typescript
+type StructuredContentLockMode = 'unlocked' | 'sdtLocked' | 'contentLocked' | 'sdtContentLocked'
+```
+
### `StructuredContentInlineInsert`
@@ -466,7 +684,7 @@ const superdoc = new SuperDoc({
ProseMirror JSON
- Node attributes
+ Node attributes (`id`, `alias`, `tag`, `lockMode`, `group`)
@@ -480,7 +698,7 @@ const superdoc = new SuperDoc({
ProseMirror JSON
- Node attributes
+ Node attributes (`id`, `alias`, `tag`, `lockMode`, `group`)
@@ -497,7 +715,7 @@ const superdoc = new SuperDoc({
Replace content with ProseMirror JSON (overrides html)
- Update attributes only (preserves content)
+ Update attributes only (preserves content). Supports `lockMode`, `alias`, `tag`.
diff --git a/apps/docs/snippets/extensions/structured-content.mdx b/apps/docs/snippets/extensions/structured-content.mdx
index 3ec575f151..c1b3fe36a7 100644
--- a/apps/docs/snippets/extensions/structured-content.mdx
+++ b/apps/docs/snippets/extensions/structured-content.mdx
@@ -2,74 +2,77 @@ Native Word SDT (w:sdt) fields for documents. Supports inline and block structur
## Use case
-- Form templates - Create fillable documents with inline text fields and block content areas
-- Contract generation - Dynamic clauses and terms that map to Word content controls
-- Document automation - Programmatically update specific sections while preserving structure
+- Form templates — Create fillable documents with inline text fields and block content areas
+- Contract generation — Dynamic clauses and terms that map to Word content controls
+- Document automation — Programmatically update specific sections while preserving structure
+- Protected fields — Lock modes control which fields users can edit or delete (ECMA-376 `w:lock`)
import { SuperDocEditor } from '/snippets/components/superdoc-editor.jsx'
"People will forget what you said, people will forget what you did, but
-people will never forget how you made them feel."
Maya Angelou
`}
- height="300px"
- customButtons={[
- [
- {
- label: 'Insert inline field',
- onClick: (superdoc) => {
- const editor = superdoc?.activeEditor || superdoc?.editor
- if (!editor?.commands) return
+ html={`Service Agreement
This agreement is entered into by and between Acme Corp ("Company") and the client identified below.
Client name: ________
The Company agrees to provide consulting services under the following terms:
________
`}
+ height="350px"
+ onReady={(superdoc) => {
+ const editor = superdoc?.activeEditor || superdoc?.editor
+ if (!editor?.commands) return
- editor.commands.insertStructuredContentInline({
- attrs: {
- id: '1',
- alias: 'Customer Name',
- },
- text: 'Enter your name',
- });
- }
- },
- {
- label: 'Insert block field',
- onClick: (superdoc) => {
- const editor = superdoc?.activeEditor || superdoc?.editor
- if (!editor?.commands) return
+ // Find "________" in "Client name:" paragraph and replace with inline field
+ let inlinePos = null
+ editor.state.doc.descendants((node, pos) => {
+ if (node.isText && node.text.includes('________') && inlinePos === null) {
+ inlinePos = pos + node.text.indexOf('________')
+ }
+ })
+ if (inlinePos !== null) {
+ editor.commands.setTextSelection({ from: inlinePos, to: inlinePos + 8 })
+ editor.commands.insertStructuredContentInline({
+ attrs: { id: '1', alias: 'Client Name' },
+ text: 'John Doe',
+ })
+ }
- editor.commands.insertStructuredContentBlock({
- attrs: {
- id: '2',
- alias: 'Terms & Conditions',
- },
- json: { type: 'paragraph', content: [{ type: 'text', text: 'Legal content...' }] }
- });
- },
- },
+ // Find the second "________" paragraph and replace with block field
+ let blockPos = null
+ editor.state.doc.descendants((node, pos) => {
+ if (node.isText && node.text.includes('________')) {
+ blockPos = pos + node.text.indexOf('________')
+ }
+ })
+ if (blockPos !== null) {
+ editor.commands.setTextSelection({ from: blockPos, to: blockPos + 8 })
+ editor.commands.insertStructuredContentBlock({
+ attrs: { id: '2', alias: 'Payment Terms' },
+ json: { type: 'paragraph', content: [{ type: 'text', text: 'Payment is due within 30 days of invoice date. Late payments incur a 1.5% monthly fee.' }] },
+ })
+ }
+ }}
+ customButtons={[
+ [
{
- label: 'Update inline field',
+ label: 'Update client name',
onClick: (superdoc) => {
const editor = superdoc?.activeEditor || superdoc?.editor
if (!editor?.commands) return
-
- editor.commands.updateStructuredContentById('1', { text: 'Jane Doe' });
+ editor.commands.updateStructuredContentById('1', { text: 'Jane Smith' });
}
},
{
- label: 'Update block field',
+ label: 'Update payment terms',
onClick: (superdoc) => {
const editor = superdoc?.activeEditor || superdoc?.editor
if (!editor?.commands) return
-
editor.commands.updateStructuredContentById('2', {
- json: { type: 'paragraph', content: [{ type: 'text', text: 'Updated legal content...' }] }
+ json: { type: 'paragraph', content: [{ type: 'text', text: 'Net-15 payment terms apply. A 2% early payment discount is available for invoices settled within 7 days.' }] }
});
}
},
+ ],
+ [
{
label: 'Delete all fields',
onClick: (superdoc) => {
const editor = superdoc?.activeEditor || superdoc?.editor
if (!editor?.commands) return
-
const fields = editor.helpers.structuredContentCommands.getStructuredContentTags(editor.state);
editor.commands.deleteStructuredContent(fields);
}
@@ -81,33 +84,44 @@ people will never forget how you made them feel."
Maya Angelou
## Quick start
```javascript
-// Insert inline field for customer name
+// Insert inline field with sdtLocked — users can edit content but not delete the field
editor.commands.insertStructuredContentInline({
attrs: {
id: '1',
- alias: 'Customer Name'
+ alias: 'Customer Name',
+ lockMode: 'sdtLocked',
},
text: 'John Doe'
});
-// Insert block field for terms section
+// Insert a read-only system field
+editor.commands.insertStructuredContentInline({
+ attrs: {
+ id: '3',
+ alias: 'Account ID',
+ lockMode: 'sdtContentLocked', // fully protected — no edits, no deletion
+ },
+ text: 'ACC-00042'
+});
+
+// Insert block field with sdtLocked — content is editable, wrapper is protected
editor.commands.insertStructuredContentBlock({
attrs: {
id: '2',
- alias: 'Terms & Conditions'
+ alias: 'Terms & Conditions',
+ lockMode: 'sdtLocked',
},
html: 'Please review the terms...
'
});
-// Update inline field content
+// Update field content
editor.commands.updateStructuredContentById('1', {
text: 'Jane Smith'
});
-// Update block field content and attributes
-editor.commands.updateStructuredContentById('2', {
- html: 'Updated terms and conditions...
',
- attrs: { alias: 'Legal Terms' }
+// Change lock mode without changing content
+editor.commands.updateStructuredContentById('1', {
+ attrs: { lockMode: 'contentLocked' }
});
// Get all structured content tags