How to Share a Quarto or R Markdown Report Behind Company Auth
Render the report to a single self-contained HTML file, then publish that file behind your company login. The analysts keep writing in Quarto or R Markdown. The stakeholders just open a link.
Render the report to a single self-contained HTML file, then publish that file behind your company login. The analysts keep writing in Quarto or R Markdown. The stakeholders just open a link.
Why this is harder than it should be
Quarto and R Markdown produce excellent HTML reports: cross-referenced figures, tabsets, interactive htmlwidgets, a floating table of contents. The format is right. The distribution is the problem.
A report with revenue projections, churn analysis, or an unreleased experiment readout cannot go on a public URL. That rules out the two easiest paths:
- RPubs publishes R Markdown to a public page. Anyone with the link reads it.
- Quarto Pub does the same for Quarto. Public by default.
The private paths cost more than the report is worth:
- Posit Connect (formerly RStudio Connect) handles auth and scheduling well, but it's a licensed server your team has to buy, deploy, and administer. Heavy infrastructure for "let six people read this quarter's analysis."
- GitHub Pages needs a repo, and a private one needs every viewer to have a GitHub account with access.
- Email the HTML file works until the report references a
_filesfolder, at which point the charts break the moment the file moves.
So a polished report ends up as a screenshot in a thread, and the cross-references, the interactive widgets, and the table of contents all get lost.
Step 1: Render to one self-contained file
The key is embedding every dependency (CSS, JavaScript, fonts, images) into a single HTML file so it survives being moved or uploaded.
Quarto. Set embed-resources in the document header, then render:
---
title: "Q2 Retention Analysis"
format:
html:
embed-resources: true
toc: true
---quarto render retention.qmdR Markdown. The html_document format already defaults to self-contained output, so a plain render gives you one file:
rmarkdown::render("retention.Rmd")If you've overridden the default, confirm self_contained: true is set under html_document in the YAML header.
The check is the same either way: move the .html to another folder, open it, and make sure every figure, table, and widget still renders. One file, no sidecar folder.
Step 2: Publish it behind company auth
display.dev publishes a single HTML file to a permanent URL and gates it with your identity provider. Viewers authenticate with the Google Workspace or Microsoft 365 account they already use.
npm install -g @displaydev/cli
dsp login
dsp publish ./retention.htmlDrop that URL into Slack, the board doc, or an email. Anyone with a company email opens it and reads the full report with its table of contents, tabsets, and interactive widgets intact. People outside your domain get a login wall.
That's gated publishing: publish in one action, gate by company identity, keep a stable link. Readers don't get charged per seat, so circulating the report widely costs nothing extra, unlike the per-seat model that quietly punishes broad sharing.
How the options compare
| Method | Private to your company | Viewer needs an account | Server to run | Interactive widgets survive |
|---|---|---|---|---|
| RPubs | No (public) | No | No | Yes |
| Quarto Pub | No (public) | No | No | Yes |
| Posit Connect | Yes | Yes | Yes (licensed) | Yes |
| GitHub Pages (private) | Yes | Yes (GitHub) | No | Yes |
| Email the HTML | Yes | No | No | Only if self-contained |
| Quarto/RMarkdown + display.dev | Yes (SSO) | Company email only | No | Yes |
Refreshing a scheduled report
Most Quarto and R Markdown reports run on a cadence: weekly retention, monthly board numbers. Re-render and re-publish to the same URL so every saved link updates together:
quarto render retention.qmd
dsp publish ./retention.html --id <shortId> --base-version <currentVersion>The --id keeps the URL stable; dsp get <shortId> shows the current version to pass as the baseline. Wire the same two commands into the job that already runs the render, and the published report stays current without anyone re-sending a link. The GitHub Actions guide covers running it from CI with an API key.
Where this fits
A Quarto report is one shape of a recurring problem: someone produces rich HTML and the audience lives outside the tool that made it. The same flow publishes Jupyter notebooks, Plotly and other data dashboards, and internal docs behind the same company login.
FAQ
Do viewers need R, Python, or Quarto installed?
No. The rendered HTML carries everything it needs. Readers open a URL in a browser with nothing installed.
Will `htmlwidgets`, Leaflet maps, and Plotly charts still work?
Yes, as long as the report is rendered self-contained so the widget JavaScript is embedded in the file. Quarto does this with embed-resources: true; R Markdown's html_document does it by default. Widgets that need a live R or Python process behind them (like a running Shiny app) won't, because a static file has no server.
Isn't this what Posit Connect is for?
Posit Connect handles auth, scheduling, and Shiny hosting, and it's the right call if you need all of that. For the common case of sharing a rendered report behind company login, it's a licensed server to deploy and maintain. Gated publishing covers the read-only sharing case in one command with no server.
Can I keep the same URL when the data changes?
Yes. Re-publish with --id <shortId> and the current --base-version. The content updates and the link stays the same, so bookmarks and pasted URLs keep working.
What about a Shiny app, not a static report?
Shiny needs a running R server, so it can't be published as a static file. If the app can export its current state as a self-contained HTML report, publish that. For a live, interactive Shiny app, you need server hosting.
Free tier. No credit card. One-time password auth for viewers on free, Google + Microsoft SSO on Pro (€49/month flat).