Images and drawings

Insert, read, and edit DOCX pictures and text boxes from React or Vue, and replace unsupported drawing previews.

The editor displays inline and floating DrawingML pictures (w:drawing with pic:pic). React and Vue ship controls to insert pictures, change wrapping, resize, and edit alternative text. Saving preserves the picture markup and media.

Insert an image

Check the bytes with normalizeImageBytes, then run insertImage. Import from @docx-editor.dev/vue in Vue.

import { normalizeImageBytes } from '@docx-editor.dev/react';

const result = normalizeImageBytes(bytes);
if (result.ok) {
  await editor.executeImageCommand({
    type: 'insertImage',
    data: result.bytes,
    mime: result.mime,
    widthPoints: result.widthPoints,
    heightPoints: result.heightPoints,
  });
}

widthPoints and heightPoints are maximums. An image that does not fit its cell, column, or page scales down proportionally. Read the committed size from snapshot().image.

FormatInsertDisplay
PNG, JPEG, GIFYesYes
BMP, WebP, SVGNoYes, at the authored wp:extent
TIFF, EMF, WMFNoRasterized when conversion succeeds, otherwise a labeled placeholder
External r:link or TargetMode="External"NoPlaceholder; never loaded automatically
  • The editor refuses insertion while another picture is selected. Place the caret in text first. The toolbar shows this reason when it disables insertion.
  • PDF export of EMF and WMF needs the drawing module. For more information, see Advanced drawing rendering.
  • To use your own image decoder, pass an ImageDecodePort through the imageDecodePort prop on DocxEditor or the adapter's root component.

Read and change an image

Read the selected image from the editor state. In Vue, useEditorState returns a ShallowRef.

import { useEditorState } from '@docx-editor.dev/react';

const image = useEditorState((s) => s.image); // SelectedImageState | null

Outside a component, call editor.getSelectedImage() or read editor.snapshot().image. Both return the same SelectedImageState.

FieldMeaning
widthEmu, heightEmuExtent in EMUs
cropCrop inset per edge, in percent (0 to 100)
wrapWrap target (see the next table)
position, rotationDegrees, locks, hiddenPlacement and lock state
title, description, hyperlinkAlternative text and link
resourceStatuspending, ready, unrenderable, external, or missing
canResize, canMoveFalse for locked or hidden drawings, rotated legacy VML text boxes, and viewing mode. canMove is also false for inline drawings.
canChangeWrap, canCropTrue for pictures only, never text boxes

Check canResize, canMove, canChangeWrap, and canCrop before you offer the matching control.

wrap valueOOXML
inlinewp:inline
square, squareLeft, squareRightwrapSquare with bothSides, left, or right
tight, throughwrapTight, wrapThrough
topAndBottomwrapTopAndBottom
behind, inFrontwrapNone with @behindDoc 1 or 0

This example wraps text around the selected picture:

if (editor.can({ type: 'setImageWrapType', target: 'square' }).ok) {
  editor.exec({ type: 'setImageWrapType', target: 'square' });
}
CommandEffect
setImageWrapTypeChanges the wrap target
setImagePositionMoves a floating drawing, in EMUs
setImagePropertiesChanges size, crop, alternative text, hyperlink, and border
transformImageRotates or flips: rotateCW, rotateCCW, flipH, flipV
replaceImageReplaces the picture bytes through executeImageCommand
deleteImageDeletes the picture

Each completed drag or resize gesture is one undo step. Alternative text reads @descr, then @title. A picture with neither is decorative.

Configure image controls

Both adapters export the same parts. The image toolbar group appears when a picture is selected.

SlotComponent or hook
image.insertImageInsertProvider, ImageInsertTrigger
image.wrapImageWrap, useEditorValueCommand('image.wrap')
image.altTextImageAltText
image.propertiesImagePropertiesTrigger, DocxEditorImagePropertiesDialog

The packaged Insert menu has an Image row. Hide or move it through DocxEditor.Menu.ImageInsert. For layout and overrides, see Toolbar customization and Customize popups.

Edit text boxes

Run insertTextBox with the caret in a body paragraph. The editor adds an empty rectangular box and places the caret in it.

if (editor.can({ type: 'insertTextBox' }).ok) {
  editor.exec({ type: 'insertTextBox' });
}

The toolbar slot is insert.textBox. Insertion in tables, headers, and footers is unavailable.

  • Click a body text box to edit it. Drag the frame to move an anchored box, or drag its handles to resize. An inline box resizes but moves only with its line.
  • Double-click a header or footer text box to open the header or footer with the caret in the box. Press Escape once to select the box and twice to close the header or footer.
  • Pasting into a text box inserts plain text. Table insertion is unavailable inside a text box.
  • Rotated or vertical text, and boxes that hold a table, take no caret. Clicking selects the box and its frame states the reason.
  • Rotated legacy VML boxes, and boxes sized as a percentage of the page or margin, cannot move or resize.
  • Find and useDocumentSearch search text boxes in the body, headers, and footers, not in notes.

Custom unsupported-content components

Register a component to replace the placeholder for unsupported content, a drawing, or an equation. Match on the OOXML namespace and element name.

<DocxEditor document={bytes}>
  <DocxEditor.UnsupportedElement
    match={(element) => element.namespaceUri === 'urn:my-widgets'}
    layout={{ width: 180, height: 48 }}
  >
    {({ element }) => <MyUnsupportedContent element={element} />}
  </DocxEditor.UnsupportedElement>
</DocxEditor>
PropDescription
matchSelects elements. Omit it for a catch-all. The first mounted matching declaration wins. Prop updates keep its priority; a remounted declaration moves last.
layoutWidth and height in points for opaque content: finite, positive, and at most 100,000. Defaults: 120 × 24 inline, 240 × 96 block. Drawing and equation placeholders keep their own size.
includePartialAlso matches partially rendered drawings. Default false.

The UnsupportedElement record has these fields:

FieldDescription
nodeId, ownerPartNameElement identity and its document part. For mc:AlternateContent, nodeId is the drawing.
namespaceUri, localNameOOXML element name. For mc:AlternateContent, the wrapper, which is also source.
sourceRead-only canonical source, not an XML string. Treat its values as untrusted. null when a standalone painter has no source lookup.
placementinline or block
labelText of the built-in placeholder
drawing, equationOptional engine metadata, such as support, drawingIssues, graphicKind, and drawing.chart
boundsOptional box after layout
  • To keep the built-in placeholder, return empty content from the render function or slot itself: null, false, an empty fragment, or whitespace. The editor does not try the next declaration.
  • A returned component that later renders null still replaces the placeholder, with an empty box.
  • Components can unmount when their page leaves the viewport. Keep durable state in your application.
  • Custom components do not change the saved DOCX or PDF export.
  • The component slot stops keyboard, clipboard, context-menu, and drop events from reaching the editor.
  • Properties, metadata, and the internal content of a drawing, equation, or field are never separate replacement targets.
  • To match chart and DrawingML diagram placeholders, see Replace a placeholder. That recipe works without the EigenPal Pro License.

Replace a partially rendered drawing

Set includePartial to show your own preview for drawings that render without some features. Shapes with editable text, fully supported drawings, hidden drawings, and drawings without a layout position stay excluded.

<DocxEditor document={bytes}>
  <DocxEditor.UnsupportedElement includePartial match={(element) => !!element.drawing}>
    {({ element }) => <DrawingPreview element={element} />}
  </DocxEditor.UnsupportedElement>
</DocxEditor>

In Vue, set :include-partial="true". Size the preview to element.bounds. A click on the preview selects the drawing.

Preview with your rendering service

Call drawingPreviewDocx() to create a DOCX that holds one drawing, including unsaved edits. Send it to your own renderer.

import { drawingPreviewDocx } from '@docx-editor.dev/core/editor';

const result = drawingPreviewDocx(editor, element);
if (result.ok) {
  // Send result.docx, result.pageFitsDrawing, and element.bounds to your renderer.
}

The editor never uploads documents or calls a rendering service. On a server, use inspectDrawingSupport() and createDrawingPreviewPackage() from @docx-editor.dev/core/store.

Failure reasonCause
drawing-not-foundThe drawing is not in the package
too-many-partsMore than 64 copied parts, by default
too-largeMore than 64 MiB, by default
  • Use the limits argument to change the defaults. The preview excludes external relationships, embedded objects, macros, and templates.
  • If pageFitsDrawing is true, use the first page. Otherwise crop from (0, 0) to bounds.width * dpi / 72 by bounds.height * dpi / 72. Without bounds, trim the white space around the drawing.
  • Run the renderer without network access and with time and memory limits. Treat the DOCX and returned images as untrusted.
  • Show returned images through an image element. Never insert returned SVG or HTML strings into the page.
  • Cache previews outside the component, for example in a WeakMap keyed by element.source. Cancel stale requests. Invalidate previews when your application changes related chart data, diagram data, or themes.
  • drawingPreviewDocx() is synchronous. Call it in an effect or event handler, and queue large batches.
  • inspectDrawingSupport() covers body, header, footer, footnote, and endnote drawings. If report.complete is false, the inventory is incomplete. It skips chart data, diagram data, inherited theme effects, and image bytes, so an empty issue list does not prove full support.
  • Pass an inspection entry with the same package object to createDrawingPreviewPackage(). Node IDs do not persist in saved XML.

Limits

  • External images never load automatically. Open, layout, paint, and save make no network request. Only an explicit user action embeds an external target.
  • Embedded image bytes pass signature checks and size limits before decoding. SVG paints in a static image mode: its scripts never run and its external references never load.
  • Drawing hyperlinks (a:hlinkClick) need an explicit gesture and pass through sanitizeHref.
  • With tracking on, image insertion and deletion record tracked changes. Property edits (size, position, wrap, crop, alt text, and links) apply directly, because they have no tracked form. Replacing a picture is refused.
  • PDF export does not apply brightness, contrast, grayscale, or black-and-white adjustments.
  • Picture commands do not address members of a shape group.
  • Copy and cut refuse selections that contain a chart or an embedded object.
  • Charts, DrawingML diagrams without a saved drawing, and drawing effects need the drawing module. Core renders saved diagram drawings. For the module, see Advanced drawing rendering.
  • Wrap geometry, VML previews, shape groups, and alternative content follow the rules in DOCX feature support.

See also