Path: blob/main/llm-docs/publishing-architecture.md
6832 views
------Quarto Publishing Architecture
How quarto publish works internally. Covers the provider interface, publish patterns, account management, and the end-to-end publish flow.
Provider Interface
File: src/publish/provider-types.ts
Every publish target implements PublishProvider:
Key types:
AccountToken— stored credential withname,server?,token(generic string or structured object)AccountTokenType—"authorized"(user-stored) or"environment"(env var)PublishRecord— entry in_publish.ymlwithid,url,code?PublishFiles—{ baseDir: string, rootFile: string, files: string[], metafiles?: string[] }
Provider Registration
File: src/publish/provider.ts
Providers are imported and added to kPublishProviders:
Discovery functions: publishProviders(), findProvider(name).
There is also a deprecation warning for the old posit-cloud provider (removed in 142a8791f) that now suggests using posit-connect-cloud as an alternative.
Two Publish Patterns
Pattern A: File-by-file upload
Used by: quarto-pub, netlify
File: src/publish/common/publish.ts
Uses handlePublish() which:
SHA-1 hashes each file in the rendered output
Creates a deploy with a file manifest (listing all files + checksums)
Server responds with which files it needs (doesn't already have)
Uploads only changed files individually
Activates the deploy
Providers using this pattern implement PublishHandler:
Pattern B: Bundle upload
Used by: rsconnect (Posit Connect), posit-connect-cloud (Posit Connect Cloud)
File: src/publish/common/bundle.ts
Uses createBundle() which:
Creates a
manifest.jsonwith file checksums,appmode,content_category, etc.Packages all files + manifest into a
.tar.gzarchiveReturns
{ bundlePath: string, manifest: object }
The provider then uploads the entire bundle as a single blob and triggers deployment. Each provider using this pattern has its own publish logic (not via handlePublish()).
createBundle() signature:
End-to-End Publish Flow
1. CLI Entry
File: src/command/publish/cmd.ts
quarto publish [provider] [path] invokes publishAction().
2. Resolve Deployment Target
File: src/publish/config.ts
Reads _publish.yml to find existing publish targets. Format:
3. Select Provider
If not specified on CLI, user is prompted to choose from available providers.
4. Resolve Account
File: src/publish/common/account.ts
Account resolution order:
Environment variable tokens (via provider's
accountTokens())Stored tokens in
~/.quarto/publish/accounts/{provider}/accounts.jsonInteractive authorization (via provider's
authorizeToken())
Token storage functions:
readAccessTokens<T>(provider)— reads stored tokenswriteAccessToken<T>(provider, token, compareFn)— writes/updates tokenreadAccessTokenFile(provider)— raw file path
5. Publish
File: src/publish/publish.ts
publishSite() or publishDocument() coordinates:
Calls provider's
publish(), passing arendercallbackProvider calls
renderForPublish()(orrender()directly for sites)Provider uploads and deploys
Returns
[PublishRecord, URL]
Important: Providers publishing documents should call renderForPublish() instead of render() directly. renderForPublish() wraps render() and stages the output: for HTML documents it copies document.html → index.html, for PDFs it creates a pdf.js viewer wrapper as index.html. Without this staging, the primary file name won't match index.html and bundle-based providers will fail.
6. Update _publish.yml
File: src/publish/config.ts
The returned PublishRecord is written back to _publish.yml for future republishing.
Account / Token Management
File: src/publish/common/account.ts
Storage
Tokens stored at: ~/.quarto/publish/accounts/{provider}/accounts.json
Format: JSON array of AccountToken objects. The token field is provider-specific (can be a string or structured object).
Authorization Patterns
Ticket-based auth (quarto-pub): authorizeAccessToken() in src/publish/common/account.ts
Opens browser to auth URL
Polls a ticket endpoint until user completes auth
Exchanges ticket for access token
API key auth (rsconnect): User provides API key directly or via env var.
OAuth Device Code (posit-connect-cloud): Uses RFC 8628 Device Code flow. See src/publish/posit-connect-cloud/api/index.ts for implementation.
Environment Variables
Each provider can check for env vars in accountTokens(). Convention:
QUARTO_PUB_AUTH_TOKENCONNECT_SERVER+CONNECT_API_KEY(rsconnect)NETLIFY_AUTH_TOKENPOSIT_CONNECT_CLOUD_ACCESS_TOKEN+POSIT_CONNECT_CLOUD_REFRESH_TOKEN+POSIT_CONNECT_CLOUD_ACCOUNT_ID(posit-connect-cloud)
Existing Providers Summary
| Provider | Pattern | Auth | requiresServer |
|---|---|---|---|
quarto-pub | A (file-by-file) | Ticket-based OAuth | false |
netlify | A (file-by-file) | API key / env var | false |
rsconnect | B (bundle) | API key (Key <key>) | true |
ghpages | Custom (git push) | Git credentials | false |
confluence | Custom | API token | true |
posit-connect-cloud | B (bundle) | OAuth Device Code (RFC 8628) | false |
huggingface | Custom | HF token | false |
Reusable Utilities
| Utility | File | Purpose |
|---|---|---|
createBundle() | src/publish/common/bundle.ts | tar.gz bundle with manifest.json |
readAccessTokens<T>() | src/publish/common/account.ts | Read stored tokens |
writeAccessToken<T>() | src/publish/common/account.ts | Write/update token |
authorizeAccessToken() | src/publish/common/account.ts | Ticket-based auth flow |
renderForPublish() | src/publish/common/publish.ts | Render + stage documents (HTML→index.html, PDF→pdf.js wrapper) |
handlePublish() | src/publish/common/publish.ts | File-by-file upload orchestration |
withSpinner() | src/core/console.ts | Progress spinner display |
completeMessage() | src/core/console.ts | Success/failure messages |
createTempContext() | src/core/temp.ts | Temp file management |
openUrl() | src/core/shell.ts | Open URL in browser |
isServerSession() | src/core/platform.ts | Detect headless/CI environment |
ApiError | src/publish/types.ts | HTTP error type with status code |