Using Quarto extensions
Overview
Extensions add capabilities to Quarto projects and documents. An extension lives in a directory under _extensions/ at your project root, described by an _extension.yml manifest:
my-project/
_quarto.yml
_extensions/
acme/ # optional organization directory
fancy-docs/
_extension.yml # the extension manifest
theme.scss # files bundled with the extension
assets/...
The manifest declares what the extension contributes:
_extension.yml
title: Fancy Docs
author: Acme
version: 1.0.0
contributes:
# one or more of:
shortcodes: [...]
filters: [...]
formats: {...}
project: {...}
metadata: {...}This page covers installing extensions, project types, and metadata contributions. Shortcodes and filters have their own pages (Shortcodes, Lua filters).
Installing extensions
q2 add installs an extension from GitHub, an archive URL, or a local folder or archive:
q2 add quarto-ext/fontawesome # GitHub: <org>/<repo>
q2 add quarto-ext/fontawesome@v1.2.0 # a tag or branch
q2 add org/repo/subdir # an extension in a repository subfolder
q2 add https://example.com/ext.zip # a .zip or .tar.gz URL
q2 add ./my-extension # a local folder or archiveIt prints what it will do ([Install], [Update] 1.0.0 -> 1.1.0, [Revert], or [No Change]) and asks before continuing. Extensions from GitHub go under the organization’s folder, as in _extensions/quarto-ext/fontawesome/.
q2 add works like Quarto 1’s quarto add, with these differences:
- Remote sources must be trusted. Extensions can run code when documents render, so
q2 addasks whether you trust the authors, and the default answer is no. When it cannot ask (--no-prompt,--json, CI), a remote source is refused unless you pass--trust. - It installs at the project root, even when you run it from a subfolder. Outside a project it installs in the current folder.
--dir <folder>overrides both. - It records the install. Each extension gets a
.quarto-source.ymlnext to its_extension.yml, with the source and a hash of every file. Don’t edit this file. Commit it along with the extension. - It protects your local changes. If you edited an installed extension’s files,
q2 addrefuses to replace them and lists what changed. Pass--forceto replace them anyway.--forcealso reinstalls an extension that reports[No Change]. Extensions installed by Quarto 1 or copied by hand have no record, so their changes can’t be detected.q2 addupdates them and prints a note. [No Change]writes nothing.
Other options: --dry-run shows the table without writing, and --json prints a machine-readable result.
A dry run of a remote source still downloads it to build the table, so it needs the same trust as a real install. In a terminal, q2 add --dry-run asks the trust question before it downloads. Without a terminal, pass --trust with --dry-run, for example q2 add quarto-ext/fontawesome --dry-run --trust --no-prompt. --force cannot be combined with --dry-run, because a dry run never overwrites anything.
Custom project types
An extension can define a custom project type: a named bundle of configuration that projects opt into by setting project.type. This is how a team ships a house style — theme, navigation defaults, analytics — as one reusable package.
In your project:
_quarto.yml
project:
type: fancy-docs
website:
title: "My Site"In the extension:
_extensions/acme/fancy-docs/_extension.yml
title: Fancy Docs
contributes:
project:
project:
type: website # the built-in base type
website:
bread-crumbs: true
navbar:
logo: assets/logo.svg
format:
html:
theme: [cosmo, theme.scss]
include-in-header:
- assets/analytics.htmlEverything under contributes: project: is an ordinary _quarto.yml fragment. When Quarto sees type: fancy-docs, it:
- finds the extension by name (use the full
acme/fancy-docsform intype:if two extensions share a name); - reads the base type from the fragment’s
project.type— the built-in type the project actually renders as (websiteordefault); - merges the fragment underneath your
_quarto.yml.
The project then behaves exactly as if you had written the combined configuration yourself.
Merge rules
Quarto’s standard configuration merging applies, uniformly:
- Your value wins wherever both you and the extension set the same option.
- Lists combine: the extension’s entries come first, then yours. In the example above, a project-level
include-in-headeris appended after the extension’s analytics include. To replace an extension-provided list instead of appending, tag your value with
!prefer:_quarto.yml
format: html: include-in-header: !prefer - only-mine.html
Bundled files
Files the extension ships next to its manifest — SCSS themes, CSS, header includes, favicons, logos, scripts — are referenced with paths relative to the extension directory, as in the example above. Quarto resolves them automatically; your project does not need to spell out _extensions/acme/fancy-docs/theme.scss.
A path that does not name a bundled file keeps its _quarto.yml meaning: it is relative to the project root. A navbar or sidebar entry with href: index.qmd links to the project’s own index.qmd, not to a file inside the extension.
The same resolution applies to format contributions (contributes: formats:): a format extension can bundle theme SCSS, CSS, or header includes next to its manifest and reference them by bare name. A theme: entry that names a .scss/.css file Quarto cannot find — bundled or otherwise — fails the render with Q-14-4.
Errors you might see
- Unknown project type (Q-5-17):
project.typeis neither built-in nor provided by any extension in_extensions/. The message lists the project-type extensions that were found — check for typos, and check whether the extension failed to load. - Invalid project type contribution (Q-16-7): the extension declares a base type that is not available. Custom types must use a built-in base (
websiteordefault);bookandmanuscriptbases are not supported yet, and custom types cannot chain to other custom types.
Metadata contributions
An extension can also contribute configuration unconditionally — applied whenever the extension is installed, without being named in project.type. This is contributes: metadata::
_extensions/posit-dev/quarto-openapi/_extension.yml
title: quarto-openapi
contributes:
metadata:
project:
pre-render:
- openapi-to-markdown.ts- Keys under
metadata.projectmerge into your project configuration — the example adds a pre-render script that runs before every project render. - Other keys under
metadatamerge into every document’s metadata.
The same merge rules apply: your own configuration always wins over extension contributions, and lists combine with the extension’s entries first.