DayuanJiang/next-ai-draw-io · error · Error

Page id "${id}" already exists

Error message

Page id "${id}" already exists

What it means

addPageToDoc refuses to create a page whose id collides with an existing page in the same document. Page ids must be unique inside an <mxfile> because draw.io addresses pages by id for navigation and links. The check compares opts.id against listPagesFromDoc(doc).

Source

Thrown at packages/mcp-server/src/pages.ts:238

 * Append a new <diagram> to the mxfile doc. The new page's model defaults to
 * an empty <mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel>.
 *
 * `opts.xml` must be a BARE <mxGraphModel> — passing a full <mxfile> would
 * end up nested inside <diagram>, which is malformed. We reject the mxfile
 * shape explicitly and strip any <?xml ?> declaration (only valid at
 * document start, never inside <diagram>).
 *
 * Returns the new PageInfo. Throws if the requested id collides or the xml
 * shape is wrong.
 */
export function addPageToDoc(
    doc: Document,
    opts: { id?: string; name?: string; xml?: string } = {},
): PageInfo {
    const existing = listPagesFromDoc(doc)
    const id = opts.id || generatePageId()
    if (existing.some((p) => p.id === id)) {
        throw new Error(`Page id "${id}" already exists`)
    }
    const name = opts.name || `Page-${existing.length + 1}`

    let inner: string
    if (opts.xml?.trim()) {
        const trimmed = stripXmlDeclaration(opts.xml.trim())
        if (isMxFile(trimmed)) {
            throw new Error(
                "addPageToDoc: opts.xml must be a bare <mxGraphModel>; received a full <mxfile>. Extract the target diagram's <mxGraphModel> first.",
            )
        }
        if (!isMxGraphModel(trimmed)) {
            throw new Error(
                "addPageToDoc: opts.xml must be a bare <mxGraphModel>.",
            )
        }
        inner = trimmed
    } else {

View on GitHub (pinned to 155ef4f7ac)

Solutions

  1. Omit opts.id and let generatePageId() assign a unique id
  2. If the id matters, check first: listPagesFromDoc(doc).some(p => p.id === myId) and skip or use a suffix
  3. If importing pages, prefix or remap ids to avoid collisions
  4. If the script may re-run, make it idempotent by checking for the page before adding

Example fix

// before
addPageToDoc(doc, { id: 'spec', name: 'Spec' }) // throws if 'spec' exists

// after
if (!listPagesFromDoc(doc).some(p => p.id === 'spec')) {
  addPageToDoc(doc, { id: 'spec', name: 'Spec' })
}
Defensive patterns

Strategy: type-guard

Validate before calling

import { listPagesFromDoc, addPageToDoc } from './pages'
const ids = new Set(listPagesFromDoc(doc).map(p => p.id))
if (ids.has(wantedId)) throw new Error(`page id ${wantedId} taken; choose another`)

Type guard

function pageIdAvailable(doc: Document, id: string): boolean {
  return !listPagesFromDoc(doc).some(p => p.id === id)
}

Try / catch

try { addPageToDoc(doc, { id }) } catch (e) { if ((e as Error).message.includes('already exists')) addPageToDoc(doc, { id: `${id}-${Date.now()}` }) else throw e }

Prevention

When it happens

Trigger: Calling addPageToDoc(doc, { id: 'page-1' }) when a page with id 'page-1' already exists; using a fixed/hardcoded id in a loop; re-running a script against a document it already mutated.

Common situations: Import scripts that copy pages from one file to another preserving ids; idempotent-looking scripts run twice; deterministic id generation (e.g. slug of page name) colliding with existing pages.

Related errors


AI-assisted analysis of DayuanJiang/next-ai-draw-io@155ef4f7ac (2026-08-27). Data as JSON: /api/errors/692a14dfb2da7e29. Report an issue: GitHub.