felixleopold1k downloadsExecute code inside your notes: Shiki syntax highlighting with 65+ themes, live streaming output, inline Matplotlib and Plotly graphs, shared variables, and styled HTML and PDF export with outputs.
Execute code inside your notes — a notebook that lives in plain Markdown.
VS Code–quality syntax highlighting, live code execution with streaming output, shared variables across blocks, inline graphs, and styled HTML/PDF export — all in a plain-text .md file you can version, diff, and edit anywhere. No kernel, no .ipynb, no server.

| Feature | What it does | |
|---|---|---|
| 🎨 | Syntax highlighting | Shiki (the VS Code engine) — 65+ themes, import any VS Code .json theme, in Reading view, Live Preview and Source mode |
| ▶️ | Live code execution | Run Python, JS/TS, Bash, Go, Ruby, PHP, and 8 more — output streams live, with interactive stdin and cancel |
| 📈 | Inline graphs | plt.show() / fig.show() render below the block — Matplotlib as images, Plotly as interactive widgets |
| 🔗 | Notebook variables | Shared state across blocks; declare note-wide vars; reference any value inline in prose with `$varname` |
| 📎 | Embedded files | ![[script.py]] becomes a collapsible, highlighted, runnable block; open vault code files in a lightweight editor |
| 🔁 | Jupyter import/export | Convert .ipynb notebooks to/from notes — round-trips multi-language blocks correctly |
| 📄 | HTML / PDF export | Export a note with its code outputs (text, images, plots) to a self-contained, theme-matched file |
Powered by Shiki — the exact same engine VS Code uses internally.
.json file from vscodethemes.com or exported directly from VS Codepy, js, ts, rb, …)![[file.py]] embeds render with the same header, Run/Copy buttons, live output, line numbers, and collapse as Reading view. The block your cursor is in reveals its raw source for editing; every other block shows the rendered chrome, with running output preserved as you move aroundRun code directly from a code block — no terminal, no switching apps.
| Language | Command | Notes |
|---|---|---|
| Python | python3 |
Matplotlib & Plotly graph capture, venv support |
| JavaScript | node |
|
| TypeScript | npx tsx |
|
| Bash | bash |
Shared variable state across blocks |
| Zsh | zsh |
Shared variable state across blocks |
| Shell | sh (POSIX) |
shell and sh fences both run POSIX sh; source-file startup support |
| PowerShell | pwsh |
macOS/Linux/Windows when PowerShell 7+ is installed |
| Go | go run |
|
| Ruby | ruby |
|
| Lua | lua |
|
| Perl | perl |
|
| R | Rscript |
|
| PHP | php |
Automatically prepends <?php for snippets that omit the opening tag |
| Swift | swift |
input() or reads from stdinsudo is detected automatically; the input bar masks characters for sensitive promptsplt.show() and fig.show() are intercepted without a display server. Matplotlib figures render as static images; Plotly figures render as interactive HTML widgets (zoom, pan, hover, legend toggles). Click a plot for full-screen view; hover an image for copy/download buttons. Toggle interactivity and offline Plotly.js embedding in settings. By default plots use Matplotlib's own look — to theme every plot (e.g. to match a dark vault), set Settings → Python → Matplotlib style to a built-in style name such as dark_background or seaborn-v0_8-darkgrid, or an absolute path to a .mplstyle fileVIRTUAL_ENV and prepends bin/ to PATH so all venv packages are available across every language block<?php tag; CodeSuite adds it only to the temporary execution fileshell blocks at a modern bash.env file with per-vault overrides, source shell startup files, run Bash/Zsh as a login shell, or pin exact interpreter pathsEach note maintains an in-memory execution session — notebook-style shared state, scoped per note and held in memory.
count = 42 in Python and a later Bash block sees 42; change it in Bash and the next Python block sees the new value. Scalars and JSON structures cross languages; rich objects (functions, DataFrames) stay within their language. See Variable typing & the execution model.$varname substitution — write `$result` anywhere in your note; it updates live in Reading view after each runskip fence tag or a codesuite:skip comment markervars blocks & code_vars: frontmatter — typed, note-scoped variablesvars blocks declare note-scoped variables once; they are injected into every run as native, correctly-typed literals:
```vars
threshold = 0.85 # float
crawl_depth = 5 # int
download_assets = True # bool
base_url = "https://x" # string (one layer of quotes stripped)
dataset = sales_q4.csv # bare text is a string too
```
Types are inferred from how each value is written, so in Python crawl_depth is an int, download_assets is a bool, and base_url is a clean string.
code_vars: frontmatter declares the same variables in YAML when you prefer note metadata over a fenced block:
---
code_vars:
threshold: 0.85
dataset: sales_q4.csv
---
A nested mapping like this shows an "unsupported property type" warning in Obsidian's Properties panel. CodeSuite hides that warning and, by default, renders the values as a read-only CodeSuite variables list just below the Properties widget. Turn the CodeSuite variables panel setting off to hide the list too, or write code_vars as a list of key = value strings (- threshold = 0.85), which Obsidian renders natively.
A vars block in the body wins if both define the same key.
State is per-note, lives only in memory, and resets when Obsidian is closed. For the full details on variable types, :type hints, multiline strings, cross-language propagation, and the execution model, see docs/variables-and-execution.md.
Embedded code files — embed any code file from your vault with ![[file.py]] and get a full syntax-highlighted, interactive block instead of plain text:
Vault code files & external aliases — enable Settings → CodeSuite → Show code files in the file explorer (on by default) and Obsidian surfaces every supported code extension (.py, .js, .ts, .sh, .go, .rb, …) in the file explorer:
CodeSuiteImports/) — edits write through to the real file on diskhtml blocks as live HTML in a sandboxed iframeRender an html code block as live HTML instead of showing its source.
```html preview
<div style="padding:8px;border-radius:6px;background:#83a598;color:#1d2021">
Rendered inline ✨
</div>
```
preview (or render) forces live preview; source (or raw / code) forces the source viewhtml block automatically.html files — pass the flag in the embed alias: ![[page.html|preview]] or ![[page.html|source]]<head>, <style>, and <script> all work; a <!DOCTYPE html> page renders as-is, a bare fragment inherits your Obsidian themeThe HTML renders in a sandboxed iframe (
sandbox="allow-scripts", no same-origin). Scripts run and styles apply, but only inside the frame — they cannot reach your vault, the rest of the app, or Obsidian's API.
Export a block to PDF (invoices, reports, certificates) — turn on PDF export for HTML blocks in settings and rendered html blocks get a PDF pill that opens a menu:
<note name>.pdf), ideal for archiving an invoice into its folderBoth honour the block's own CSS, including @media print rules, and lay it out on A4 with comfortable margins. Opt a single block in or out with a pdf (or nopdf) fence flag — pdf alone also renders the block — so you don't need the global setting on:
```html pdf
<div class="invoice">…</div>
```
Desktop only (both paths need Electron).
Data-driven documents (invoices, reports) with template — add a template flag and an html block becomes a template: it interpolates the note's frontmatter, includes a shared layout/CSS partial, and loops over a list. Keep one invoice.html partial and fill it per note — then the PDF pill exports it.
```html template pdf
{{> invoice }}
```
invoice_number: "2026-002"
client_name: "Muster GmbH"
items:
- { description: "Wartung & Server", amount: 120 }
amount_net: 120
template_context:
biz: "[[Business Config]]"
{{ field }} interpolates (HTML-escaped; {{{ }}} or | raw for trusted markup); {{ amount | eur }} and | date format while frontmatter stays raw and Dataview-queryable{{> partial }} pulls in shared layout/CSS from your imports folder; {{#each items}}…{{/each}} loops; {{#if}} drops empty rowstemplate_context: exposes another note's frontmatter under a namespace ({{ biz.biz_iban }}) — change Business Config.md once, every invoice updatestemplate flag, or the HTML block templating setting for any {{-containing block. Off by default so framework demos that use {{ }} render literally…or template an embedded file — keep the layout in a standalone .html file and embed it; the file is templated against the embedding note. Cleanest for one-file documents like invoices — the note holds only data and one line, no fenced block needed:
![[invoice.html|template pdf]]
The same engine, flags (template / notemplate / pdf), and global settings apply.
Full syntax reference: HTML Templating. A copy-paste starter lives in examples/invoice/.
Move work between CodeSuite and the Jupyter ecosystem, or share a polished copy of a note. All four commands live in the command palette (desktop only).
Jupyter notebooks (.ipynb)
metadata.vscode.languageId tag so VS Code renders each cell in the right language and the notebook round-trips correctly on re-import. Cells export without outputs.Styled HTML & PDF (with outputs)
Both produce a self-contained file that matches what you see in Obsidian — same Shiki theme, CodeSuite styling, code outputs (text, images, plots), and native Obsidian features including callouts (theme colours, icons, and custom callout types) and LaTeX math (inline and block). Each export opens a small options dialog (last choices remembered):
Outputs come from the live render. Open the note in reading view and run the blocks you want shown (individually or with Run All), then export. Whatever output is on screen is captured as-is; nothing is written back into your
.md. If the note isn't in reading view, the command is unavailable.
Bake outputs into the note (for sharing) (advanced — off by default)
HTML/PDF export produces a separate file. Baking instead writes the output into the note's markdown, so it survives anywhere the raw .md is read — most importantly in shared notes (e.g. via NoteColab), where the recipient opens the note in a web viewer that has no CodeSuite to re-run your code. Without baking, a shared note shows your code but no output.
Enable it under Settings → CodeSuite → Sharing (baked outputs). Most users never need this; leaving it off changes nothing. Once on, two commands appear:
```codesuite-output block placed right after it. Run your blocks first, then bake.CodeSuite renders baked blocks as normal output panels (reading view and live preview); other markdown renderers fall back to a labelled code block. Two things are handled deliberately:
CodeSuite/baked-outputs) and referenced by name, not inlined as base64. A Inline images instead of files toggle is available if you'd rather keep the note self-contained at the cost of size. (Interactive Plotly widgets have no static image form, so they're always inlined.)stale badge when the code above it has changed since the output was baked — re-run and re-bake to refresh.Community Plugins (recommended) — Settings → Community Plugins → Browse → search CodeSuite → Install → Enable.
main.js, manifest.json, and styles.css from the latest release<vault>/.obsidian/plugins/code-suite/Open Settings → CodeSuite — organized into Appearance, Execution, Languages, Files, and Advanced tabs — to configure themes, code execution, environment variables, and embedded file behaviour.
| Variables & Execution | How to run code, declare variables, use $varname, cross-language propagation, practical patterns |
| HTML Templating | Data-driven html blocks — interpolation, filters, partials, loops; the invoice/report workflow |
| Configuration Reference | Every setting, option, and environment knob |
Active-line highlight bleeds into code blocks (editor mode) — when the cursor is inside a code block in Live Preview or Source mode, Obsidian's active-line highlight shows through the block background. This is inherent to how Obsidian's active-line extension works.
Workaround: Enable Auto-switch theme and choose a theme whose background matches Obsidian's active-line color — the bleed becomes invisible.
Track progress or vote on the linked GitHub issues.
| # | Feature | Issue |
|---|---|---|
| 1 | Per-block code formatting — line highlighting {1,5-10}, diff highlighting ins/del, per-block titles, showLineNumbers override, and inline code syntax highlighting |
#13 |
Recent releases
@typescript-eslint/no-unnecessary-type-assertion warnings from the plugin review (#43) — removed redundant setTimeout/loadTheme casts, turned the file-picker cast into a type annotation, and routed frontmatter narrowing through a toRecord() helper that's valid in every type environment. No functional changes.@typescript-eslint/no-unsafe-* warnings from the Obsidian plugin review (#43) by giving the Node built-ins (fs/path/os/child_process) self-contained type definitions instead of as typeof import(...) casts that collapsed to any without @types/node. No functional changes.C:\…) it couldn't resolve; it's now translated to its /mnt/c/… form. A new WSL path translation setting (Auto-detect / On / Off) lets you override the detection for Git Bash, Cygwin, or MSYS.!important on the frontmatter-vars hide rule in favor of higher selector specificity, and removed leftover demo-GIF planning comments from the README. No functional changes.code_vars: / template_context: below the Properties widget) is now reliable and on by default. It lives inside the note header next to Properties, so it stays put as you scroll instead of flickering, and it refreshes when the frontmatter changes. Turn it off in settings to just hide Obsidian's "unsupported property type" warning and render nothing (#34).code_vars: / template_context: frontmatter renders in preview instead of Obsidian's "unsupported property type" warning, and code_vars accepts a key = value list form (#34); html-block previews now appear in HTML/PDF exports (#33). Fixes: re-running a block now sees upstream state instead of compounding its own output (#36), and dead output-toolbar buttons no longer show in exports (#35).html template block — or an embedded .html file (![[invoice.html|template pdf]]) — becomes a data-driven document. {{ frontmatter }} interpolation with eur/date/number filters, {{> partials }} for shared layout/CSS, {{#each}} loops, {{#if}} conditionals, and template_context: notes for single-source shared data. Built for invoices/reports; pairs with the PDF pill. Opt in per block/embed with a template flag or globally with HTML block templating. See HTML Templating.html blocks get a PDF pill to save or print just that block on an A4 page (great for invoices and reports). Opt in globally with PDF export for HTML blocks, or per block with a pdf / nopdf fence flag.NameError, the spurious trailing blank line in output is gone, and output-less runs collapse to a slim Output (none) header. New: clicking Run All again while it runs cancels the pass.obsidianmd/ui/sentence-case rule disables with an ignoreRegex allowlist, removed a CSS !important in favor of higher selector specificity.metadata.vscode.languageId so VS Code renders and round-trips them. Quality: cancellation polish, skip-badge alignment by code hash, fence-attribute isolation, Matplotlib style default corrected..ipynb import/export and styled HTML/PDF export with outputs (#5); HTML live preview in a sandboxed iframe.![[file.py]] embeds.vars/code_vars injection with :type hints and multiline strings (#16), live cross-language variable propagation, experimental data tables.sh fence now runs POSIX sh..env file support, codesuite:skip, code_vars: frontmatter, in-vault code editor, import-as-alias command.Found a bug or have a feature request? Open an issue on GitHub. Want to contribute code? See CONTRIBUTING.md.
Apache 2.0 © Felix Leopold