deck: writing slides in Markdown and letting Google Slides own the design
deck is a tool for creating deck using Markdown and Google Slides.
At a glance
- What is it?
- deck is a Go command line tool that turns a Markdown file into a Google Slides presentation, with a watch mode for continuous rebuilding and a CEL-based conditional action system for per-slide automation. It keeps the design in Google Slides and the content in a text file, which is either exactly what you want or exactly the wrong shape of tool.
- Who is it for?
- Adopt deck if your team already lives in Google Slides and wants slide content in a reviewable Markdown file, or if you want CI to publish decks from the same source you edit. Do not adopt it if you need a self-contained PPTX export, offline rendering, or a design that is fully specified in the repository rather than held in a Slides template.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The two key concepts, and why they pull in opposite directions
The README states two key concepts, and they are the whole design.
The first is continuous deck building: generate and modify deck iteratively. The second is separate content and design: Markdown for content, Google Slides for design.
The second is the one that surprises people. deck does not render slides itself, and it does not produce a PPTX file. It talks to the Google Slides API and edits a real presentation in a real Google account. The Markdown file is the content, and the Slides document is the theme, the master layouts, and the placeholders that content gets poured into. That is a genuinely different architecture from Marp, from reveal.js, or from any tool that produces a self-contained artefact, and the difference shows up in what you can and cannot do.
What you gain is that the design lives in Slides. If your organisation has a brand template with a specific master, specific fonts and a set of approved layouts, deck inherits all of it for free, and the person who maintains the template maintains it in the tool your designers already use. The README's base presentation mechanism is built around this: you can point deck new at an existing presentation with the --base flag, or set a default basePresentationID in config.yml, and the new deck inherits that theme.
What you give up is portability and reproducibility in the usual sense. The output of a deck build is a Google Slides document, not a file in your repository. There is no offline rendering, and the rendering that matters happens in Google's infrastructure, on Google's template, with Google's permissions model.
The audience is therefore a team whose slides are already in Google Slides, and who want the text of those slides under version control. That is a narrower audience than it sounds, and it includes, for instance, organisations that are required to keep a material library in Google Workspace and want the prose in a repo alongside the code.
OAuth, service accounts, and a doctor command that tells you what is wrong
Getting a tool to edit your Google Slides is a permissions exercise before it is a workflow exercise, and the README spends good space on it.
For interactive use, the path is: create or reuse a developer project at console.cloud.google.com, enable the Google Slides API and the Google Drive API at the API & Services page, go to the Credentials page and click + CREATE CREDENTIALS, then create an OAuth client ID of type Desktop app. Because there is no need to publish the app, you add your own email address as a test user from the Google Auth Platform Audience page. The credentials file then goes to ${XDG_DATA_HOME:-~/.local/share}/deck/credentials.json.
For CI/CD automation, the README points to a separate Service Account Setup Guide at docs/setup-service-account.md, which is the right thing to point at, because the interactive OAuth flow with a browser consent screen does not work unattended.
Then there is deck doctor, and this is the detail that shows the project is written for people who hit setup problems. The command verifies whether deck is ready to use and diagnoses any configuration issues. A tool that can edit a document on your behalf, driving a remote API with your credentials, has a lot of ways to be misconfigured, and a diagnostic subcommand turns a confusing failure into a list of missing pieces.
The dependency list explains the shape of the implementation. The Google API client is google.golang.org/api, with golang.org/x/oauth2 for the token handling and cloud.google.com/go/auth and oauth2adapt for the newer credential paths. The CLI itself is built on spf13/cobra, with Songmu/prompter for interactive prompts, briandowns/spinner for progress feedback, fatih/color and mattn/go-colorable for terminal output, and k1LoW/slog-multi and lestrrat-go/backoff for structured logging and retry backoff. The OAuth dance is handled by pkg/browser, which is how the desktop app flow opens a browser for consent.
The command set: new, ls, apply, open, and watch mode
The workflow is four commands, and the sequence is the natural order of the task.
deck new creates a presentation and a markdown file together. It takes the filename and a title:
$ deck new deck.md --title "Talk about deck"
Applied frontmatter to deck.md
xxxxxXXXXxxxxxXXXXxxxxxxxxxxThe output shows both halves of the result: the frontmatter has been applied to the markdown file, and the presentation ID is printed. The markdown file now carries the presentation ID, which is what lets subsequent commands work on it without you passing the ID every time. If you want the new deck to inherit a theme, you either pass the base presentation with --base:
$ deck new deck.md --base yyyyyyyYYYYyYYYYYYYyyyyyyyyy --title "Talk about deck"or set it once in the configuration file at ~/.config/deck/config.yml:
# ~/.config/deck/config.yml
basePresentationID: "yyyyyyyYYYYyYYYYYYYyyyyyyyyy"with the --base flag taking precedence when both are present.
deck ls lists the presentations you can reach, printing the ID and the name:
$ deck ls
xxxxxXXXXxxxxxXXXXxxxxxxxxxx My Presentation
yyyyyYYYYyyyyyYYYYyyyyyyyyyy Team Project SlidesThe README notes that deck fully supports Google Shared Drives or Team Drives, and that presentations stored in shared drives are automatically included in listings and can be operated on just like personal drive presentations. That is the difference between a tool that works for one person and a tool that works for a company with a shared slide library, and it is not a trivial detail.
deck apply pushes the markdown content into the presentation:
$ deck apply deck.mdand deck open opens the presentation in your default web browser:
$ deck open deck.mdThe feature that changes the feel of the tool is the watch flag:
$ deck apply --watch deck.mdThis continuously monitors changes to the markdown file and automatically applies them to the presentation, so you see your changes reflected in the real deck as you edit. The one constraint the README calls out is that the --watch flag cannot be used together with the --page flag, which is the sensible restriction, since watching a single page while the file changes would be incoherent.
The markdown format, and what the frontmatter can carry
The Markdown that deck accepts consists of YAML frontmatter and a body section, and the frontmatter rules are stated precisely.
It must be at the very beginning of the file, enclosed between --- delimiters, valid YAML, and use camelCase for the fields used in deck settings. The example:
---
presentationID: xxxxxXXXXxxxxxXXXXxxxxxxxxxx
title: Talk about deck
---
# First Slide
Content...Four fields are documented in the visible part of the README, and each has a purpose beyond a simple setting.
presentationID is the Google Slides presentation ID, and when it is specified you can use the simplified command syntax, meaning you can run deck apply deck.md without also passing --presentation-id. title does the same for the presentation title.
breaks is a boolean that controls how line breaks are rendered. The default, false or omitted, renders line breaks as spaces, and when true the line breaks in the markdown are rendered as actual line breaks in the slides. That default is worth understanding, because it means a single newline in your source does not become a line break on the slide, which surprises people coming from a plain Markdown renderer. It can also be configured globally in config.yml.
codeBlockToImageCommand is a string giving a command to convert code blocks to images, and when specified, code blocks in the presentation are converted to images using that command. This is a small feature with a large effect on how a technical deck looks, because a syntax-highlighted image of code reads better on a projected slide than pasted text does, and the README links a CodeMirror-based implementation in its Special Thanks. It too can be set globally in config.yml.
defaults is an array that defines conditional actions using CEL, and this is the most interesting of the four because it is a general mechanism rather than a formatting switch. The repository ships a schema.yml, which is the contract for that configuration, and the dependency list includes github.com/google/cel-go, the Common Expression Language implementation. CEL is a small, sandboxed expression language designed for exactly this kind of configuration, and using it here means you can express rules like apply this action only when a certain field has a certain value, without writing a plugin or a script.
The body of the file is separated into slides by three or more consecutive hyphens at the beginning of a line, for example ---. The README points to its own Markdown file format section for the details, and the repository has an md/ directory with a parse implementation, which the Makefile fuzzes.
The engineering in the repository: fuzzing, goldens, and a similarity check
The top-level file listing of this repository tells you more about how the project is built than the README does, and it is a Go project with an unusual amount of test infrastructure for a command line tool.
There are golden-file tests, with tenntenn/golden as the dependency and files such as slide_test.go, style_snapshot.go and style_snapshot_test.go, table_comparison_test.go and similarity_test.go. Golden tests mean the expected output is stored in a file and compared, and for a tool that turns Markdown into a structured sequence of Slides API operations, that is the only practical way to assert on the output. The similarity test is interesting: it suggests the tool checks how close a re-applied deck is to the previous one, which is a way of testing idempotence, that applying the same content twice does not produce a different result.
There are integration tests, with integration_test.go, integration_action_test.go and integration_apply_test.go, plus a testdata/ directory. The Makefile has a fulltest target that sets TEST_INTEGRATION=1, and a separate integration target that runs a specific set of test names with a 30 minute timeout:
integration:
env TEST_INTEGRATION=1 go test -v . -test.failfast -run \
'TestApply$$|TestRoundTripSlidesToGoogleSlidesPresentationAndBack$$|TestApplyMarkdown$$|TestAction$$' \
-timeout 30mTestRoundTripSlidesToGoogleSlidesPresentationAndBack is the interesting name in that list. A round trip means the test applies markdown to a real presentation, reads the presentation back, and compares, which means the integration suite runs against the actual Google API rather than a mock. That is the right test for this kind of tool, and it is also why the integration target needs credentials.
There is a fuzz target as well:
fuzz:
go test -fuzz=FuzzParse -fuzztime=1m ./md/.
go test -fuzz=FuzzGenerateActions -fuzztime=1m .Fuzzing the markdown parser and the action generator is exactly right for a tool that takes untrusted text and turns it into API calls.
Around that there is the usual Go project furniture, and some of it is worth noting. The coverage and code-to-test ratio badges come from octocovs, and there are two octocov configuration files, .octocov.yml and .octocov-full.yml, which suggests the unit coverage and the integration coverage are tracked separately. The linting is golangci-lint with a .golangci.yml, and there is a separate .gostyle.config.yml. The release is automated with goreleaser, with a .goreleaser.yml, and the version is stamped into the binary at build time with -ldflags. The CREDITS file is generated with gocredits, and the Makefile has a prerelease_for_tagpr target that regenerates it. The .tagpr file is the tag-driven release tool, and the CI badge points at a ci.yml workflow. The version package carries the revision stamp that the ldflags set.
The main dependency for the Google API is google.golang.org/api, and the Go version required is 1.26.0. The markdown parsing is yuin/goldmark, the YAML is goccy/go-yaml, and the retries use hashicorp/go-retryablehttp, which is appropriate given that a deck build makes a lot of API calls and can hit rate limits.
Where deck is the wrong tool
Three limits, and the first one is architectural rather than fixable.
The first is that deck produces a Google Slides document, not a file. There is no PPTX export in the visible documentation, and the export.go file in the repository tree is not documented in the README, so the existence of an export path is plausible but its format and fidelity are not stated. For a team that needs to hand a deck to someone without a Google account, or that needs the deck to live in the repository alongside the code, that is a blocker rather than an inconvenience. A tool like Marp produces a self-contained artefact; deck produces a remote object with a URL.
The second is the Google dependency, which is a privacy and availability issue as well as a technical one. The content of your slides, which for a technical talk is often the most sensitive part of the work, is pushed to a Google API. That may be entirely fine inside a company that has chosen Workspace, and entirely wrong for a company that has not. There is also the rate limit and the quota story, which is why the retry and backoff dependencies are there, and why a large deck build over a slow connection is a different experience from a small one.
The third is the coupling to the template's placeholders. Because deck applies content to an existing presentation, the layout placeholders in that presentation determine what the markdown can address. A slide that expects a title placeholder and a body placeholder behaves differently from one with a picture placeholder, a table placeholder and a chart placeholder, and the README does not document the placeholder contract. The markdown file format section is where that contract would be specified, and the repository has slide.go, table.go, image.go, blockquote.go and style.go as the files that implement it, but the visible documentation does not enumerate the supported elements. In practice this means the first real deck you build will involve reading those files or experimenting, and a mismatch surfaces at apply time.
There is a fourth, smaller point about the CEL defaults. The mechanism is powerful, but a CEL expression that is wrong does not fail at configuration load, it fails when a slide matches or fails to match the condition, which can be a silent omission. A rule that never fires looks the same as a rule that correctly does not fire.
deck against the other Markdown-to-slides tools
There are two distinct alternatives, and the difference in approach is between rendering slides yourself and editing someone else's slides.
The first alternative is a self-contained renderer, of which Marp is the canonical example. Marp takes Markdown and produces HTML, PDF or PPTX, with the rendering done locally by your own toolchain, and the design comes from CSS you write or a theme you choose. The advantages are substantial: the output is a file, the build works offline, CI can produce an artefact per commit, and there is no vendor in the loop. The disadvantages are that the rendering is yours to configure, that what you get is a document rather than an editable presentation, and that PowerPoint or Slides importers are approximations rather than the real thing.
The second alternative is a different kind of tool: a script against the Slides API that you write yourself, or a commercial product that does the same. The difference from deck is control over the pipeline. deck gives you a Markdown file, a watch mode, a CEL rule system and a CLI, and it decides that Google Slides is where the design lives. Writing it yourself gives you exactly the operations you need and no more, and it costs you the auth handling, the retry logic, the diffing, the formatting rules and the whole edge-case surface that deck has already filled in.
The third option is the one deck is a version of: keep editing the deck by hand in Google Slides, and skip the tool. For a deck that is written once and presented once, that is the least work, and it is a real answer. The value of deck appears when a deck is revised repeatedly, when several people contribute slides, or when the deck is generated as part of a build, for instance a status deck assembled from a repository's contents. In those cases the Markdown file becomes reviewable, diffable and scriptable, which is what the repository's own round-trip and idempotence tests are aimed at.
The deciding question is therefore where the design lives and where the artefact lives. If the design belongs to a team that maintains a Google Slides template and the artefact belongs in Google Drive, deck is doing something no local renderer can. If you need a file in the repository and a render on any machine, a local renderer is the better shape.
Editorial conclusion
Adopt deck if your team already lives in Google Slides and wants slide content in a reviewable Markdown file, or if you want CI to publish decks from the same source you edit. Do not adopt it if you need a self-contained PPTX export, offline rendering, or a design that is fully specified in the repository rather than held in a Slides template. Verify first that your OAuth client or service account can reach the Slides and Drive APIs, that the presentation you point at has the layout placeholders your markdown expects, and that the CEL expressions in your defaults match the schema.yml contract, since a mismatch there fails at apply time rather than at build time.
Frequently asked questions
How do I install deck?
The README gives three routes: `brew install deck` for Homebrew, `go install github.com/k1LoW/deck/cmd/deck@latest` for a Go install, and downloading the binary from the releases page. Released archives can be verified with the GitHub CLI using `gh attestation verify` against the release-build workflow.
What credentials does deck need to edit Google Slides?
For interactive use, an OAuth client ID of type Desktop app with your email added as a test user, and the credentials file placed at ${XDG_DATA_HOME:-~/.local/share}/deck/credentials.json, with the Google Slides API and Google Drive API enabled. For CI/CD the README points to a separate service account setup guide at docs/setup-service-account.md.
How does deck apply markdown to a presentation continuously?
The `--watch` flag on `deck apply` continuously monitors changes to the markdown file and automatically applies them to the presentation, so edits show up in the real deck as you make them. The README notes that `--watch` cannot be combined with the `--page` flag.
Can deck reuse the theme of an existing presentation?
Yes, in two ways: pass the base presentation with `deck new deck.md --base <id> --title "..."`, or set basePresentationID in ~/.config/deck/config.yml so every new deck inherits it. When both are present the --base flag takes precedence.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/k1low-deck)