insert, insert_before and insert_after
The insert family adds new nodes to the tree at a precise location relative to an existing tag: inside it at a given index (insert), or immediately before or after it as a sibling.
.insert(index, element)
from bs4 import BeautifulSoup
soup = BeautifulSoup("<ul><li>a</li></ul>", "html.parser")
new = soup.new_tag("li")
new.string = "b"
soup.ul.insert(1, new)
print(soup) # <ul><li>a</li><li>b</li></ul>
Insert at index 0 to prepend; a negative index works like list insertion.
.insert_before(element)
soup = BeautifulSoup("<p>one</p><p>two</p>", "html.parser")
second = soup.find_all("p")[1]
second.insert_before(soup.new_tag("hr"))
print(soup) # <p>one</p><hr/><p>two</p>
.insert_after(element)
soup = BeautifulSoup("<div>x</div>", "html.parser")
soup.div.insert_after(soup.new_tag("footer"))
print(soup) # <div>x</div><footer/>
What the Methods Accept
Any of the methods accepts a Tag, a NavigableString, or a plain string (which becomes a text node):
soup = BeautifulSoup("<p></p>", "html.parser")
soup.p.insert(0, "hello ")
soup.p.append(soup.new_tag("b"))
soup.p.b.string = "world"
print(soup) # <p>hello <b>world</b></p>
insert vs append
The append method is a shortcut for insert at the end of a tag's children, and there is a matching prepend for index 0. Reach for the explicit insert only when the exact position in the middle matters, for example injecting a tooltip right after the first column of a table row, and use the before/after variants whenever the new node belongs next to a sibling instead of inside it.
Summary
insert() places a node inside a tag at a given child index, while insert_before() and insert_after() place it as a sibling immediately around the tag. All three accept tags, NavigableStrings, or plain strings, which makes them the basic building blocks for document transformation.