Yuto Takagi415 downloadsOpen, edit and inspect PowerPoint (.pptx) decks natively in Obsidian: a PowerPoint-style editing surface, an OOXML property inspector, slide embeds, Markdown extraction and PNG export.
View, edit and save PowerPoint decks without leaving Obsidian.
yut0takagi.github.io/obsidian-pptx-studio — what it does, in English and Japanese.
Obsidian natively opens Markdown, images, audio, video, PDF and canvas — .pptx
is not on that list, so a deck in your vault is invisible in the file explorer
and cannot be linked to. This plugin registers the extension and renders slides
directly from the OOXML, with no external converter and nothing to install
alongside it.
Settings → Community plugins → Browse, search for PPTX Studio, then install and enable it.
To run a build that has not been released yet, point
BRAT at
yut0takagi/obsidian-pptx-studio, or do it by hand: download main.js,
manifest.json and styles.css from the
latest release
into <vault>/.obsidian/plugins/pptx-studio/.
.pptx in the file explorer to open it. Thumbnail
rail, ↑/↓ (or k/j) to page, zoom, speaker notes.Cmd/Ctrl+S to save.
Only the slide XML you touched is rewritten; every other part of the file is
repacked byte for byte, so animations, transitions, embedded fonts and any
formatting this plugin does not model survive untouched.![[deck.pptx]] embeds the deck, ![[deck.pptx#3]]
pins a single slide.The interface follows Obsidian's own display language, so a Japanese vault gets a Japanese ribbon with nothing to configure. Settings → PPTX Studio → Language can pin it to English or Japanese instead.
The ribbon collapses with the chevron at the right of the tab strip, and remembers whether it was collapsed. In a narrow pane it drops its labels and group titles rather than scrolling sideways. Selecting a table brings up the Table tab automatically and deselecting it goes back to where you were.
A small toolbar follows the selection with the things you reach for constantly — fill, outline, bold, arrange, duplicate, delete, and crop on a picture. Nothing lives only there; it is a shortcut to what the ribbon already has.
Clicking in the ribbon never takes focus away from the slide, so the arrow keys keep working immediately afterwards.
The side pane (View → Selection) lists every shape on the slide, and underneath
it shows what the file actually says about the selected one: its p:cNvPr id,
the element it is (p:sp, p:pic, p:graphicFrame), the part it lives in, its
placeholder type, and its position and size as both pixels on screen and the raw
EMU stored in a:off / a:ext. A shape with no a:xfrm of its own says so,
rather than quietly reporting the frame it inherited from the layout.
The frame, the rotation and the name are fields rather than labels: type over
one and press Enter. A length takes pixels, or a number with a unit — emu,
pt, in, cm, mm — so a value read out of another file can be pasted in as
it stands. Everything a field writes goes through the same commands the ribbon
uses, so it undoes with Cmd/Ctrl+Z like any other edit. The shape id, the
part and the element stay read-only.
Below that is the shape's XML, indented and scrollable, with a button that copies it.
Drag the pane's left edge to resize it, and the line between the shape list and the properties to change how the height is shared. Either section folds away by clicking its title, and whichever is left open takes the whole column. Widths, heights and folds are all remembered; double-clicking a divider puts it back.
A box is in one of two states, and its border says which. A solid border means you are holding the box: dragging moves it, the handles resize it, the arrow keys nudge it, and typing replaces its text. A dashed border means you are inside the text: there is a caret, and the keys go to it.
Double-click, Enter or F2 goes from holding to typing; Esc or Cmd/Ctrl
+Enter goes back, keeping what was typed. While you are typing, the border and
the handles still belong to the box — dragging one leaves the text and moves or
resizes the box in the same gesture — and so does a press on any other shape,
which simply selects it.
↑ ↓ / k j / PageUp PageDown |
previous / next slide |
Home End |
first / last slide |
+ - / 0 |
zoom in, out, fit to pane |
Cmd/Ctrl + wheel |
zoom |
N |
toggle speaker notes |
| double-click | edit a text box |
Esc |
leave a text edit, keeping what was typed, or clear the selection |
Cmd/Ctrl + Enter |
finish an edit |
Tab / Shift+Tab (nothing being edited) |
walk through the shapes on the slide |
Enter or F2 |
edit the selected shape's text |
| typing with a shape selected | replace the shape's text with what you type |
| drag the edited box's border or a handle | leave the text, and move or resize the box |
| arrow keys (with a shape selected) | nudge 1px, Shift for 10px |
click / Shift+click / drag on empty space |
select, extend, marquee — the drag can start on the slide or in the space around it |
Cmd/Ctrl + A |
select every shape on the slide |
Cmd/Ctrl + C / X / V / D |
copy, cut, paste, duplicate |
Delete |
delete the selection |
Alt while dragging |
drag a copy |
Shift while dragging |
constrain to one axis |
Shift while resizing a corner |
keep the aspect ratio |
Shift while rotating |
snap to 15° |
Cmd/Ctrl while dragging |
ignore snapping |
Tab / Shift+Tab while editing text |
change the list level |
Cmd/Ctrl + Z / Shift+Z |
undo / redo |
Cmd/Ctrl + F |
find and replace |
Cmd/Ctrl + S |
save |
The deck is unzipped in memory and each slide is walked into a small model, then rendered as absolutely-positioned DOM at the deck's native pixel size and scaled with a CSS transform — so zooming is free and text stays crisp and selectable.
The parser covers what real decks actually use: theme colour schemes and the
lumMod/lumOff/tint/shade transforms, placeholder inheritance from slide
to layout to master, list styles and auto-numbered bullets, gradients, images
with cropping, tables with merged cells, groups, connectors, and SmartArt (via
the plain-shapes fallback drawing PowerPoint stores alongside it).
Charts are drawn as SVG — column, bar, stacked and percent-stacked, line, area, pie, doughnut and scatter — from the values every chart part caches alongside its embedded workbook. That cache is what the deck was showing when it was saved, so no spreadsheet has to be opened to plot it. Series take their colours from the deck's theme accents unless the chart names its own, and a missing value stays a gap in the line rather than becoming a zero.
Extracting a deck to Markdown turns each chart into its own data table, since the numbers behind a chart are what someone would want to search for later.
Text and shapes inherited from a layout or master are drawn but not editable — changing them would silently rewrite every slide built on that template.
Animations are written as the tree PowerPoint itself emits, because a timing tree that merely satisfies the schema is one PowerPoint refuses to open. A sequence holding an effect this plugin cannot write back is read and can be removed, but is never rebuilt — reordering it would mean dropping the effect it does not understand.
Editing a run keeps the runs around it intact, so typing inside a bold word leaves it bold. Only restructuring a paragraph — merging runs, deleting across them — collapses it onto the formatting of its first run.
Moving a placeholder that inherited its position writes a new a:xfrm onto the
slide, which is what PowerPoint does too.
Undo and redo work on whole package parts rather than on individual elements. An edit that adds a part — inserting a picture, adding a slide — cannot be expressed as a change to an element, so recording parts is what lets one mechanism cover every command and guarantees undo lands on a state the parser has already accepted. The test suite asserts exactly that: after running all sixteen editor commands and undoing them, every part is byte-identical to where it started.
The first time you save a deck, a one-off copy is written beside it as
<name>.pptx.bak. Binary files do not go through Obsidian's file recovery, so
that copy is your undo of last resort.
If the file changed on disk since you opened it, the save is refused rather than overwriting someone else's work.
An edit re-derives only the slide it touched. DeckEditor reports which package
parts a command changed, and a change confined to one slide's part and its
relationships rebuilds that slide alone; only a change to the presentation, a
layout or a master rebuilds the deck. On a 27-slide deck that is 1.7ms instead of
61ms, and one slide re-rendering instead of all of them.
Rendered slides are cached per index and only the edited one is dropped. Thumbnails render lazily as they scroll into view, and refresh individually. The ribbon, selection pane, floating toolbar and status bar all refresh together in one animation frame rather than each reacting to every event.
whole deck one slide render one slide
21 slides, 702 shapes 30.0ms 0.58ms 4.5ms
27 slides, 233 shapes 60.6ms 1.67ms 6.1ms
npm run smoke -- --bench prints these for any deck (set PPTX_SMOKE_BENCH=1).
npm install
npm run dev # watch build
npm run build # typecheck + production bundle
npm run lint # Obsidian plugin rules over src
npm test # unit tests for the parser's pure pieces
npm run smoke -- ~/Downloads/*.pptx # parse + save round trip against real decks
Linting covers src only. That is the code that ships into Obsidian, and the
rules worth enforcing — no Node built-ins, createEl over innerHTML — are
about that runtime. The tests and build scripts run under Node, where the same
rules only produce noise, and tsc already typechecks them. Errors fail the
build; warnings are left visible rather than blocking.
The unit tests cover the layers the rest of the parser is built on — the XML helpers, EMU-to-pixel frames and preset geometry, and theme and colour resolution — where a mistake changes what a slide looks like without breaking anything loudly enough for a round trip to notice.
The smoke test runs the parser and the renderer under Node with jsdom, then drives the same code paths the UI does: it edits a run in the rendered DOM, deletes a paragraph, moves and resizes shapes, inserts text boxes, shapes, tables and pictures, groups and ungroups, reorders, aligns, formats, adds, duplicates, reorders and deletes slides — then undoes the lot and checks the package came back byte-for-byte.
npm version patch --no-git-tag-version # or minor / major
That moves package.json, and the version script carries manifest.json and
versions.json along with it, so the three cannot drift apart. Commit the bump on
a release/x.y.z branch and open a pull request.
Merging it publishes the release. The workflow reads the version out of
manifest.json, notices it has not been released, checks that all three files
agree, rebuilds, runs the unit and smoke tests again — the merge commit is not
necessarily one CI has already seen — and attaches main.js, manifest.json
and styles.css to a release named after the version. The tag is created by
the release itself, against the commit that was built. There is nothing left to
do by hand, and no window in which a tag exists without a release behind it.
Every other push to main ends at a job that finds the current version already released, and stops.
Pushing a tag still works, for releasing a commit that is not the tip of main:
git tag 0.1.5 <commit> && git push origin 0.1.5
Tags are the bare version with no v, because Obsidian looks for a release
whose name matches the manifest.
Each of those files is attested, so a download can be checked against the workflow run that produced it:
gh attestation verify main.js --repo yut0takagi/obsidian-pptx-studio