BabylonJS/Babylon.js · error

att() after start tag closed for <${ctx.name}>

Error message

att() after start tag closed for <${ctx.name}>

What it means

XmlBuilder.att() throws this error when the current element's start tag has already been closed (children or text started, or the tag was self-closed). XML attributes must appear inside the start tag, so once ctx.closed is true the attribute cannot be added and the builder refuses to emit invalid XML.

Source

Thrown at packages/dev/serializers/src/3MF/core/xml/xml.builder.ts:138

        }
        this._w.write(XmlSyntax.Question, XmlSyntax.CloseTag);
        return this;
    }

    /**
     *
     * @param ns
     * @param n
     * @param v
     * @returns
     */
    public att(ns: string | null, n: string, v: string): IXmlBuilder {
        const ctx = this._peekContext();
        if (!ctx) {
            throw new Error("att() without open element");
        }
        if (ctx.closed) {
            throw new Error(`att() after start tag closed for <${ctx.name}>`);
        }

        // explicit namespace declaration: xmlns or xmlns:prefix
        if (this._isXmlnsDecl(ns, n)) {
            if (n === XmlSyntax.Xmlns) {
                // default namespace
                ctx.defaultNs = v;
                // you can store default as empty prefix if you want
                this._registerNamespace(ctx, "", v);
                this._writeAttStr(XmlSyntax.Xmlns, v);
            } else {
                if (!ns) {
                    const prefix = n.slice(6); // "xmlns:"
                    this._registerNamespace(ctx, prefix, v);
                    this._writeAttStr(n, v);
                } else {
                    this._registerNamespace(ctx, n, v);
                    this._writeAttStr(`${ns}:${n}`, v);

View on GitHub (pinned to 0592b347b8)

Solutions

  1. Move all att() calls immediately after the element is opened and before text()/child elements.
  2. Restructure the code to collect attributes first, then open the element with them.
  3. Ensure no intermediate text() or child el() call occurs before all attributes are written.
  4. If using helper wrappers, make sure they do not close the start tag implicitly before attributes are added.

Example fix

// before
b.el("item").text("x").att(null, "id", "1"); // throws
// after
b.el("item").att(null, "id", "1").text("x");
Defensive patterns

Strategy: validation

Validate before calling

function canAddAttribute(b: XmlBuilder): boolean {
    const ctx = (b as any)._peekContext?.();
    return !!ctx && !ctx.closed;
}
if (canAddAttribute(b)) b.att(ns, name, value);

Try / catch

try {
    b.att(ns, name, value);
} catch (e) {
    if (e instanceof Error && e.message.startsWith('att() after start tag closed')) {
        // collect attributes before opening the element and rewrite the section
    } else throw e;
}

Prevention

When it happens

Trigger: Calling att() after text(), after a child element was opened, or after the element was closed/self-closed — anything that set ctx.closed before the attribute call.

Common situations: Streaming-style builder code where attributes are appended after content; accumulating attributes in a loop that runs after el()/text(); framework callbacks firing out of order (content written before attribute collection finished).

Related errors


AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30). Data as JSON: /api/errors/30d0180593a2eb21. Report an issue: GitHub.