Plugins
Plugins are ways of adding new languages or formatting rules to Prettier. Prettier’s own implementations of all languages are expressed using the plugin API. The coreprettier
package contains JavaScript and other web-focused languages built in. For additional languages you’ll need to install a plugin.
Using Plugins
You can load plugins with:
TheCLI, via
--plugin
:prettier--write main.foo--plugin=prettier-plugin-foo
tipYou can set
--plugin
options multiple times.TheAPI, via the
plugins
options:await prettier.format("code",{
parser:"foo",
plugins:["prettier-plugin-foo"],
});{
"plugins":["prettier-plugin-foo"]
}
Strings provided toplugins
are ultimately passed toimport()
expression, so you can provide a module/package name, a path, or anything elseimport()
takes.
Official Plugins
Community Plugins
prettier-plugin-apex
by@dangmaiprettier-plugin-astro
by@withastro contributorsprettier-plugin-elm
by@giCentreprettier-plugin-erb
by@adamzapasnikprettier-plugin-gherkin
by@mapadoprettier-plugin-glsl
by@NaridaLprettier-plugin-go-template
by@NiklasPorprettier-plugin-java
by@JHipsterprettier-plugin-jinja-template
by@davidodenwaldprettier-plugin-jsonata
by@Stediprettier-plugin-kotlin
by@Angry-Potatoprettier-plugin-motoko
by@dfinityprettier-plugin-nginx
by@joedeandevprettier-plugin-prisma
by@umidbekkprettier-plugin-properties
by@eemeliprettier-plugin-rust
by@jinxdashprettier-plugin-sh
by@JounQinprettier-plugin-sql
by@JounQinprettier-plugin-sql-cst
by@neneprettier-plugin-solidity
by@mattiaerreprettier-plugin-svelte
by@sveltejsprettier-plugin-toml
by@JounQin and@so1ve
Developing Plugins
Prettier plugins are regular JavaScript modules with the following five exports or default export with the following properties:
languages
parsers
printers
options
defaultOptions
languages
Languages is an array of language definitions that your plugin will contribute to Prettier. It can include all of the fields specified inprettier.getSupportInfo()
.
Itmust includename
andparsers
.
exportconst languages=[
{
// The language name
name:"InterpretedDanceScript",
// Parsers that can parse this language.
// This can be built-in parsers, or parsers you have contributed via this plugin.
parsers:["dance-parse"],
},
];
parsers
Parsers convert code as a string into anAST.
The key must match the name in theparsers
array fromlanguages
. The value contains a parse function, an AST format name, and two location extraction functions (locStart
andlocEnd
).
exportconst parsers={
"dance-parse":{
parse,
// The name of the AST that the parser produces.
astFormat:"dance-ast",
hasPragma,
hasIgnorePragma,
locStart,
locEnd,
preprocess,
},
};
The signature of theparse
function is:
functionparse(text:string, options: object):Promise<AST>|AST;
The location extraction functions (locStart
andlocEnd
) return the starting and ending locations of a given AST node:
functionlocStart(node: object):number;
(Optional) The pragma detection function (hasPragma
) should return if the text contains the pragma comment.
functionhasPragma(text:string):boolean;
(Optional) The "ignore pragma" detection function (hasIgnorePragma
) should return if the text contains a pragma indicating the text should not be formatted.
functionhasIgnorePragma(text:string):boolean;
(Optional) The preprocess function can process the input text before passing intoparse
function.
functionpreprocess(text:string, options: object):string;
printers
Printers convert ASTs into a Prettier intermediate representation, also known as a Doc.
The key must match theastFormat
that the parser produces. The value contains an object with aprint
function. All other properties (embed
,preprocess
, etc.) are optional.
exportconst printers={
"dance-ast":{
print,
embed,
preprocess,
getVisitorKeys,
insertPragma,
canAttachComment,
isBlockComment,
printComment,
getCommentChildNodes,
handleComments:{
ownLine,
endOfLine,
remaining,
},
},
};
The printing process
Prettier uses an intermediate representation, called a Doc, which Prettier then turns into a string (based on options likeprintWidth
). Aprinter's job is to take the AST generated byparsers[<parser name>].parse
and return a Doc. A Doc is constructed usingbuilder commands:
import*as prettierfrom"prettier";
const{ join, line, ifBreak, group}= prettier.doc.builders;
The printing process consists of the following steps:
- AST preprocessing (optional). See
preprocess
. - Comment attachment (optional). SeeHandling comments in a printer.
- Processing embedded languages (optional). The
embed
method, if defined, is called for each node, depth-first. While, for performance reasons, the recursion itself is synchronous,embed
may return asynchronous functions that can call other parsers and printers to compose docs for embedded syntaxes like CSS-in-JS. These returned functions are queued up and sequentially executed before the next step. - Recursive printing. A doc is recursively constructed from the AST. Starting from the root node:
- If, from the step 3, there is an embedded language doc associated with the current node, this doc is used.
- Otherwise, the
print(path, options, print): Doc
method is called. It composes a doc for the current node, often by printing child nodes using theprint
callback.
print
Most of the work of a plugin's printer will take place in itsprint
function, whose signature is:
functionprint(
// Path to the AST node to print
path: AstPath,
options: object,
// Recursively print a child node
print:(selector?:string|number|Array<string|number>| AstPath)=> Doc,
): Doc;
Theprint
function is passed the following parameters:
path
: An object, which can be used to access nodes in the AST. It’s a stack-like data structure that maintains the current state of the recursion. It is called “path” because it represents the path to the current node from the root of the AST. The current node is returned bypath.node
.options
: A persistent object, which contains global options and which a plugin may mutate to store contextual data.print
: A callback for printing sub-nodes. This function contains the core printing logic that consists of steps whose implementation is provided by plugins. In particular, it calls the printer’sprint
function and passes itself to it. Thus, the twoprint
functions – the one from the core and the one from the plugin – call each other while descending down the AST recursively.
Here’s a simplified example to give an idea of what a typical implementation ofprint
looks like:
import*as prettierfrom"prettier";
const{ group, indent, join, line, softline}= prettier.doc.builders;
functionprint(path, options, print){
const node= path.node;
switch(node.type){
case"list":
returngroup([
"(",
indent([softline,join(line, path.map(print,"elements"))]),
softline,
")",
]);
case"pair":
returngroup([
"(",
indent([softline,print("left"), line,". ",print("right")]),
softline,
")",
]);
case"symbol":
return node.name;
}
thrownewError(`Unknown node type:${node.type}`);
}
Check outprettier-python's printer for some examples of what is possible.
(optional)embed
A printer can have theembed
method to print one language inside another. Examples of this are printing CSS-in-JS or fenced code blocks in Markdown. The signature is:
functionembed(
// Path to the current AST node
path: AstPath,
// Current options
options: Options,
):
|((
// Parses and prints the passed text using a different parser.
// You should set `options.parser` to specify which parser to use.
textToDoc:(text:string, options: Options)=>Promise<Doc>,
// Prints the current node or its descendant node with the current printer
print:(
selector?:string|number|Array<string|number>| AstPath,
)=> Doc,
// The following two arguments are passed for convenience.
// They're the same `path` and `options` that are passed to `embed`.
path: AstPath,
options: Options,
)=>Promise<Doc|undefined>| Doc|undefined)
| Doc
|undefined;
Theembed
method is similar to theprint
method in that it maps AST nodes to docs, but unlikeprint
, it has power to do async work by returning an async function. That function's first parameter, thetextToDoc
async function, can be used to render a doc using a different plugin.
If a function returned fromembed
returns a doc or a promise that resolves to a doc, that doc will be used in printing, and theprint
method won’t be called for this node. It's also possible and, in rare situations, might be convenient to return a doc synchronously directly fromembed
, howevertextToDoc
and theprint
callback aren’t available at that case. Return a function to get them.
Ifembed
returnsundefined
, or if a function it returned returnsundefined
or a promise that resolves toundefined
, the node will be printed normally with theprint
method. Same will happen if a returned function throws an error or returns a promise that rejects (e.g., if a parsing error has happened). Set thePRETTIER_DEBUG
environment variable to a non-empty value if you want Prettier to rethrow these errors.
For example, a plugin that has nodes with embedded JavaScript might have the followingembed
method:
functionembed(path, options){
const node= path.node;
if(node.type==="javascript"){
returnasync(textToDoc)=>{
return[
"<script>",
hardline,
awaittextToDoc(node.javaScriptCode,{parser:"babel"}),
hardline,
"</script>",
];
};
}
}
If the--embedded-language-formatting
option is set tooff
, the embedding step is entirely skipped,embed
isn’t called, and all nodes are printed with theprint
method.
(optional)preprocess
Thepreprocess
method can process the AST from the parser before passing it into theprint
method.
functionpreprocess(ast:AST, options: Options):AST|Promise<AST>;
(optional)getVisitorKeys
This property might come in handy if the plugin uses comment attachment or embedded languages. These features traverse the AST iterating through all the own enumerable properties of each node starting from the root. If the AST hascycles, such a traverse ends up in an infinite loop. Also, nodes might contain non-node objects (e.g., location data), iterating through which is a waste of resources. To solve these issues, the printer can define a function to return property names that should be traversed.
Its signature is:
functiongetVisitorKeys(node, nonTraversableKeys: Set<string>):string[];
The defaultgetVisitorKeys
:
functiongetVisitorKeys(node, nonTraversableKeys){
returnObject.keys(node).filter((key)=>!nonTraversableKeys.has(key));
}
The second argumentnonTraversableKeys
is a set of common keys and keys that prettier used internal.
If you have full list of visitor keys
const visitorKeys={
Program:["body"],
Identifier:[],
// ...
};
functiongetVisitorKeys(node/* , nonTraversableKeys*/){
// Return `[]` for unknown node to prevent Prettier fallback to use `Object.keys()`
return visitorKeys[node.type]??[];
}
If you only need exclude a small set of keys
const ignoredKeys=newSet(["prev","next","range"]);
functiongetVisitorKeys(node, nonTraversableKeys){
returnObject.keys(node).filter(
(key)=>!nonTraversableKeys.has(key)&&!ignoredKeys.has(key),
);
}
(optional)insertPragma
A plugin can implement how a pragma comment is inserted in the resulting code when the--insert-pragma
option is used, in theinsertPragma
function. Its signature is:
functioninsertPragma(text:string):string;
Handling comments in a printer
Comments are often not part of a language's AST and present a challenge for pretty printers. A Prettier plugin can either print comments itself in itsprint
function or rely on Prettier's comment algorithm.
By default, if the AST has a top-levelcomments
property, Prettier assumes thatcomments
stores an array of comment nodes. Prettier will then use the providedparsers[<plugin>].locStart
/locEnd
functions to search for the AST node that each comment "belongs" to. Comments are then attached to these nodesmutating the AST in the process, and thecomments
property is deleted from the AST root. The*Comment
functions are used to adjust Prettier's algorithm. Once the comments are attached to the AST, Prettier will automatically call theprintComment(path, options): Doc
function and insert the returned doc into the (hopefully) correct place.
(optional)getCommentChildNodes
By default, Prettier searches all object properties (except for a few predefined ones) of each node recursively. This function can be provided to override that behavior. It has the signature:
functiongetCommentChildNodes(
// The node whose children should be returned.
node:AST,
// Current options
options: object,
):AST[]|undefined;
Return[]
if the node has no children orundefined
to fall back on the default behavior.
(optional)printComment
Called whenever a comment node needs to be printed. It has the signature:
functionprintComment(
// Path to the current comment node
commentPath: AstPath,
// Current options
options: object,
): Doc;
(optional)canAttachComment
functioncanAttachComment(node:AST):boolean;
This function is used for deciding whether a comment can be attached to a particular AST node. By default,all AST properties are traversed searching for nodes that comments can be attached to. This function is used to prevent comments from being attached to a particular node. A typical implementation looks like
functioncanAttachComment(node){
return node.type&& node.type!=="comment";
}
(optional)isBlockComment
functionisBlockComment(node:AST):boolean;
Returns whether or not the AST node is a block comment.
(optional)handleComments
ThehandleComments
object contains three optional functions, each with signature
(
// The AST node corresponding to the comment
comment:AST,
// The full source code text
text:string,
// The global options object
options: object,
// The AST
ast:AST,
// Whether this comment is the last comment
isLastComment:boolean,
)=>boolean;
These functions are used to override Prettier's default comment attachment algorithm.ownLine
/endOfLine
/remaining
is expected to either manually attach a comment to a node and returntrue
, or returnfalse
and let Prettier attach the comment.
Based on the text surrounding a comment node, Prettier dispatches:
ownLine
if a comment has only whitespace preceding it and a newline afterwards,endOfLine
if a comment has a newline afterwards but some non-whitespace preceding it,remaining
in all other cases.
At the time of dispatching, Prettier will have annotated each AST comment node (i.e., created new properties) with at least one ofenclosingNode
,precedingNode
, orfollowingNode
. These can be used to aid a plugin's decision process (of course the entire AST and original text is also passed in for making more complicated decisions).
Manually attaching a comment
Theprettier.util.addTrailingComment
/addLeadingComment
/addDanglingComment
functions can be used to manually attach a comment to an AST node. An exampleownLine
function that ensures a comment does not follow a "punctuation" node (made up for demonstration purposes) might look like:
import*as prettierfrom"prettier";
functionownLine(comment, text, options, ast, isLastComment){
const{ precedingNode}= comment;
if(precedingNode&& precedingNode.type==="punctuation"){
prettier.util.addTrailingComment(precedingNode, comment);
returntrue;
}
returnfalse;
}
Nodes with comments are expected to have acomments
property containing an array of comments. Each comment is expected to have the following properties:leading
,trailing
,printed
.
The example above usesprettier.util.addTrailingComment
, which automatically setscomment.leading
/trailing
/printed
to appropriate values and adds the comment to the AST node'scomments
array.
The--debug-print-comments
CLI flag can help with debugging comment attachment issues. It prints a detailed list of comments, which includes information on how every comment was classified (ownLine
/endOfLine
/remaining
,leading
/trailing
/dangling
) and to which node it was attached. For Prettier’s built-in languages, this information is also available on the Playground (the 'show comments' checkbox in the Debug section).
options
options
is an object containing the custom options your plugin supports.
Example:
exportdefault{
// ... plugin implementation
options:{
openingBraceNewLine:{
type:"boolean",
category:"Global",
default:true,
description:"Move open brace for code blocks onto new line.",
},
},
};
defaultOptions
If your plugin requires different default values for some of Prettier’s core options, you can specify them indefaultOptions
:
exportdefault{
// ... plugin implementation
defaultOptions:{
tabWidth:4,
},
};
Utility functions
prettier.util
provides the following limited set of utility functions for plugins:
typeQuote='"'|"'";
typeSkipOptions={ backwards?:boolean};
functiongetMaxContinuousCount(text:string, searchString:string):number;
functiongetStringWidth(text:string):number;
functiongetAlignmentSize(
text:string,
tabWidth:number,
startIndex?:number,
):number;
functiongetIndentSize(value:string, tabWidth:number):number;
functionskip(
characters:string| RegExp,
):(
text:string,
startIndex:number|false,
options?: SkipOptions,
)=>number|false;
functionskipWhitespace(
text:string,
startIndex:number|false,
options?: SkipOptions,
):number|false;
functionskipSpaces(
text:string,
startIndex:number|false,
options?: SkipOptions,
):number|false;
functionskipToLineEnd(
text:string,
startIndex:number|false,
options?: SkipOptions,
):number|false;
functionskipEverythingButNewLine(
text:string,
startIndex:number|false,
options?: SkipOptions,
):number|false;
functionskipInlineComment(
text:string,
startIndex:number|false,
):number|false;
functionskipTrailingComment(
text:string,
startIndex:number|false,
):number|false;
functionskipNewline(
text:string,
startIndex:number|false,
options?: SkipOptions,
):number|false;
functionhasNewline(
text:string,
startIndex:number,
options?: SkipOptions,
):boolean;
functionhasNewlineInRange(
text:string,
startIndex:number,
startIndex:number,
):boolean;
functionhasSpaces(
text:string,
startIndex:number,
options?: SkipOptions,
):boolean;
functiongetPreferredQuote(
text:string,
preferredQuoteOrPreferSingleQuote: Quote|boolean,
): Quote;
functionmakeString(
rawText:string,
enclosingQuote: Quote,
unescapeUnnecessaryEscapes?:boolean,
):string;
functiongetNextNonSpaceNonCommentCharacter(
text:string,
startIndex:number,
):string;
functiongetNextNonSpaceNonCommentCharacterIndex(
text:string,
startIndex:number,
):number|false;
functionisNextLineEmpty(text:string, startIndex:number):boolean;
functionisPreviousLineEmpty(text:string, startIndex:number):boolean;
Tutorials
- How to write a plugin for Prettier: Teaches you how to write a very basic Prettier plugin for TOML.
Testing Plugins
Since plugins can be resolved using relative paths, when working on one you can do:
import*as prettierfrom"prettier";
const code="(add 1 2)";
await prettier.format(code,{
parser:"lisp",
plugins:["./index.js"],
});
This will resolve a plugin relative to the current working directory.