Python ParseError Unbound Prefix in XML and How to Fix It
Fix ElementTree ParseError unbound prefix and lxml Namespace prefix is not defined: fragments cut from a document, auto-declared prefixes, recover.

You are reading XML in Python and the parser stops with:
xml.etree.ElementTree.ParseError: unbound prefix: line 1, column 0or, from lxml:
lxml.etree.XMLSyntaxError: Namespace prefix dc on creator is not definedBoth mean the same thing, and the cause is nearly always the same: you handed the parser a piece of an XML document, and the declarations that gave the prefix its meaning stayed behind in the part you did not pass.
I recreated the situation with an RSS-style fragment and tried the fixes.
What a prefix needs
In XML with namespaces, a prefixed name such as dc:creator is a short form. Its meaning comes from a declaration, xmlns:dc="http://purl.org/dc/elements/1.1/", on the element or on an ancestor. The parser resolves each prefix as it reads. If it meets dc: with no declaration in scope, it cannot say what namespace the name belongs to, and it stops.
ET.fromstring('<dc:creator>Ana</dc:creator>')
# ParseError: unbound prefix: line 1, column 0It fails at column 0 because the first thing in the fragment is the unbound name. Three situations produce this in practice.
A fragment cut out of a document. You took <dc:creator>...</dc:creator> from an RSS feed, from a database column that stores part of a message, or from a string a template built. The xmlns:dc declaration lived on the <rss> root.
Text copied from a bigger file. A developer pastes a chunk from an XML file into a test, and the chunk uses prefixes the original root declared.
A serializer that dropped the declaration. Some code removes xmlns attributes to tidy output, which breaks every prefixed name below it. I confirmed it: serializing a child element of a namespaced SOAP body gave <m:Id xmlns:m="urn:ex" xmlns:soap="...">7</m:Id>, with the declarations it needed; removing xmlns:m="urn:ex" from that string made the parse fail with Namespace prefix m on Id is not defined, column 61. Libraries add declarations on purpose, and string edits undo them.
The error also applies to attributes: <a xsi:type="x"/> failed with the same message, since xsi: is a prefix like any other. The only prefixes that need no declaration are xml (so xml:lang="en" always works) and xmlns itself.
Fix one: parse a wrapper that declares them
The most direct fix is to supply the declarations yourself, in a wrapper root:
wrapped = ('<w xmlns:dc="http://purl.org/dc/elements/1.1/" '
'xmlns:media="http://search.yahoo.com/mrss/">' + frag + '</w>')
root = ET.fromstring(wrapped)
[c.tag for c in root]
# ['{http://purl.org/dc/elements/1.1/}creator', '{http://search.yahoo.com/mrss/}thumbnail',
# '{http://purl.org/dc/elements/1.1/}date']The fragment parsed, and each tag shows its namespace in curly braces. A wrapper without declarations (<w> and nothing else) failed the same way (unbound prefix: line 1, column 3), because the declarations are the point and not the wrapper. This also solves the multiple-roots problem: a fragment of several sibling elements is not a document, and the wrapper makes it one.
The URI is what matters, not the prefix. Declare xmlns:d="http://purl.org/dc/elements/1.1/" instead, and the fragment would still carry {http://purl.org/dc/elements/1.1/}creator once the prefix dc is re-bound to the same URI. What you must get right is the URI, and you can usually find it in the original document's root, in the vocabulary's documentation, or in the producer's schema.
Fix two: find the prefixes and declare them automatically
If you process fragments from many sources, write the wrapper from the fragment's own content:
KNOWN = {"dc": "http://purl.org/dc/elements/1.1/",
"media": "http://search.yahoo.com/mrss/",
"atom": "http://www.w3.org/2005/Atom"}
def wrap(fragment, root="w"):
used = (set(re.findall(r"<(?:/)?([A-Za-z_][\w.-]*):[A-Za-z_]", fragment))
| set(re.findall(r"\s([A-Za-z_][\w.-]*):[A-Za-z_][\w.-]*=", fragment)))
used -= {"xml", "xmlns"}
decl = " ".join(f'xmlns:{p}="{KNOWN.get(p, "urn:unbound:" + p)}"' for p in sorted(used))
return f"<{root} {decl}>{fragment}</{root}>", usedFor the test fragment it found {'dc', 'media'} and the result parsed. The first regex finds prefixes on element names (start and end tags) and the second on attribute names. Names in comments and CDATA would be false positives for these simple expressions, but declaring an extra prefix is harmless. Two cautions. A table of known prefixes is only as good as the table: if the URI for dc in your data is a different version of the vocabulary, the parsed names will carry the wrong namespace, and a later search by the right URI finds nothing. And a prefix that is not in the table gets the invented URI urn:unbound:foo. That makes the parse succeed, and the data it produces has a namespace that means nothing. Treat that as a signal to find the real declaration and log it.
Fix three: do not cut the document in the first place
The best fix is to avoid the cut. If you need a part of a document, parse the whole and select the part with XPath, then work with the element:
tree = etree.parse("feed.xml")
for item in tree.xpath("//item"):
creator = item.findtext("{http://purl.org/dc/elements/1.1/}creator")The element keeps its namespace context, because it is still part of the tree. If you must serialize the part, use the library (etree.tostring(item) adds the declarations that the element needs), not string slicing. If you store fragments in a database, store the declarations with them, or store the whole document, or keep the prefixes in a mapping table of your own.
What does not work
A few things people try, from the same experiment.
A declaration on a sibling. <r><a xmlns:p="urn:p"/><p:b/></r> failed with unbound prefix: line 1, column 23: a declaration applies to the element that carries it and its descendants, not to its siblings.
**recover=True in lxml.** It parsed the wrapped fragment and kept the names as literal text:
etree.tostring(etree.fromstring(('<w>' + frag + '</w>').encode(), etree.XMLParser(recover=True)))
# b'<w><dc:creator>Ana</dc:creator><media:thumbnail url="u"/><dc:date>2026</dc:date></w>'The output looks right, and the elements have the names dc:creator as plain tags with no namespace, so {http://purl.org/dc/elements/1.1/}creator finds nothing. Recovery hides the problem and breaks the lookups that follow.
Ignoring the prefix. Searching with the wildcard {*}creator or local-name() can find the elements in a tree where the prefix was bound, and does nothing for the parse error itself.
A prefix in text. p:z in element text, a comment or a CDATA section never needed a declaration. The parser only resolves prefixes that appear on element and attribute names.
Diagnosing it in five steps
Is the input a whole document? If it starts with something other than the root, it is a fragment.
Which prefixes does the message name? Find their URIs in the original or the vocabulary.
Wrap the fragment with those declarations (by hand, or with the function above and a real table).
Check the resulting tags: the URIs in curly braces should be the ones you expect.
Fix the source of the cut if you can, so you stop needing the wrapper.
This is one of the failure modes covered in fixing XML parse errors fast. The namespace rules behind it are in how XML namespaces work, and selecting elements by namespace afterwards is in selecting elements in a namespace with XPath and handling XML namespaces in ElementTree. The behavior is specified by the Namespaces in XML recommendation and described in the ElementTree documentation. To view a namespaced document with its declarations, use the XML formatter.
When the prefix really is wrong
The producer forgot the declaration. A feed that uses dc:creator without xmlns:dc anywhere is broken at the source, and no wrapper on your side is more than a patch. Tell the producer, and keep the patch in one adapter.
A typo in the prefix. dx:creator where dc:creator was meant. The message names the prefix, so compare it with the declarations in the root.
A different vocabulary version. If a document declares xmlns:dc="http://purl.org/dc/terms/" (the other Dublin Core namespace) and your code looks for the elements namespace, the parse succeeds and the lookup is empty. That is a mapping problem, not an unbound prefix.
The input is HTML. Pages that use og:title in attributes use prefixes in a way XML parsers do not accept without declarations. Use an HTML parser.


