Working with comments
Presentation comments are stored outside the slide XML. A slide can have a comments part, and the presentation can have a comment authors part that identifies authors used by comments.
Comment structure
Classic PresentationML comments use a comment list rooted at <p:cmLst/>. Each comment has an author id, creation time, position, and text.
<p:cmLst xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
<p:cm authorId="0" dt="2026-05-04T10:00:00Z" idx="1">
<p:pos x="10" y="20"/>
<p:text>Review this slide.</p:text>
</p:cm>
</p:cmLst>
The author list is a separate presentation-level part:
<p:cmAuthorLst xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
<p:cmAuthor id="0" name="Ada" initials="AL" lastIdx="1" clrIdx="0"/>
</p:cmAuthorLst>
Important p:cm children and attributes are:
| Item | Meaning |
|---|---|
p:pos | Position of the comment on the slide surface |
p:text | Unformatted comment text |
p:extLst | Extension data for future features |
authorId | ID of an entry in the comment author list |
dt | Last modified date and time |
idx | Comment index unique for that author |
The position point is the upper-left point in left-to-right UI layouts and the upper-right point in right-to-left UI layouts. The display size is application-defined; comments do not appear during slide show playback.
Rust workflow
Use package relationships to find the relevant parts. Start with the presentation part for author data, and with each slide part for slide-specific comments:
#![allow(unused)] fn main() { pub fn add_comment_to_slide( path: &Path, slide_index: usize, author_name: &str, initials: &str, text: &str, ) -> Result<Vec<u8>, Box<dyn std::error::Error>> { let mut document = PresentationDocument::new_from_file_with_settings(path, lazy_settings())?; let presentation_part = document.presentation_part()?; let authors_part = if let Some(part) = presentation_part.comment_authors_part(&document) { part } else { presentation_part.add_new_part_auto_id::<_, CommentAuthorsPart>(&mut document)? }; let authors_xml = authors_part .data_as_str(&document)? .map(str::to_string) .filter(|xml| !xml.trim().is_empty()) .unwrap_or_else(empty_comment_authors_xml); let (updated_authors_xml, author_id, comment_index) = upsert_comment_author(&authors_xml, author_name, initials)?; authors_part.set_data(&mut document, updated_authors_xml.into_bytes())?; let Some(slide_part) = presentation_part.slide_parts(&document).nth(slide_index) else { return Err("slide index out of range".into()); }; let comments_part = if let Some(part) = slide_part.slide_comments_part(&document) { part } else { slide_part.add_new_part_auto_id::<_, SlideCommentsPart>(&mut document)? }; let comments_xml = comments_part .data_as_str(&document)? .map(str::to_string) .filter(|xml| !xml.trim().is_empty()) .unwrap_or_else(empty_slide_comments_xml); let updated_comments_xml = append_slide_comment(&comments_xml, author_id, comment_index, text)?; comments_part.set_data(&mut document, updated_comments_xml.into_bytes())?; let mut buffer = Cursor::new(Vec::new()); document.save(&mut buffer)?; Ok(buffer.into_inner()) } }
Delete comments by matching author ids from the author list:
#![allow(unused)] fn main() { pub fn delete_comments_by_author( path: &Path, author_name: &str, ) -> Result<Vec<u8>, Box<dyn std::error::Error>> { let mut document = PresentationDocument::new_from_file_with_settings(path, lazy_settings())?; let presentation_part = document.presentation_part()?; let Some(authors_part) = presentation_part.comment_authors_part(&document) else { let mut buffer = Cursor::new(Vec::new()); document.save(&mut buffer)?; return Ok(buffer.into_inner()); }; let authors_xml = authors_part.data_as_str(&document)?.unwrap_or_default(); let author_ids = comment_author_ids_by_name(authors_xml, author_name); let updated_authors_xml = remove_comment_authors_by_id(authors_xml, &author_ids); authors_part.set_data(&mut document, updated_authors_xml.into_bytes())?; let slide_parts: Vec<_> = presentation_part.slide_parts(&document).collect(); for slide_part in slide_parts { let Some(comments_part) = slide_part.slide_comments_part(&document) else { continue; }; let comments_xml = comments_part.data_as_str(&document)?.unwrap_or_default(); let updated_comments_xml = remove_slide_comments_by_author_id(comments_xml, &author_ids); comments_part.set_data(&mut document, updated_comments_xml.into_bytes())?; } let mut buffer = Cursor::new(Vec::new()); document.save(&mut buffer)?; Ok(buffer.into_inner()) } }
Practical guidance
For read-only tooling, inspect existing comment parts and author parts. For editing modern threaded comments, prefer starting from a real presentation that already contains those comments, then make a small schema-aware change and verify that PowerPoint can open the result.