Skip to content

Message format ​

The default ​

text
<type>(<scope>)!: <emoji> <title>

<description>

<comment>

BREAKING CHANGE: <breaking>
Closes <issue>
Closes <issue>

Empty parts disappear, together with the blank lines around them. With only a type and a title the message is a single line:

text
fix: 🐛 handle an empty config file
PartQuestionNotes
typetypeChosen from the list; brings its emoji.
(scope)componentWrapped in parentheses only when given.
!breakingAdded when the breaking change answer is not empty.
titletitleFirst letter lower-cased (unless it is an acronym such as API), trailing period removed. Turn off with lowercaseTitle: false.
descriptiondescriptionThe body. | starts a new line (breaklineChar); one space around it is dropped, more is kept as indentation. Wrapped at 100 columns (bodyLineLength).
commentcommentAnother body paragraph. Not asked by default.
BREAKING CHANGE:breakingThe footer semantic-release looks for.
Closes #12issuesOne line per issue, so GitHub closes each of them. 12 13 becomes #12 and #13; keys like JIRA-7 stay; a typed closes or fixes: is dropped. The word comes from issuePrefix.

Why the emoji goes after the colon ​

Tools parse the header with a pattern like type(scope)!: subject. With the emoji after the colon it becomes part of the subject, so:

  • commitlint with @commitlint/config-conventional accepts the message;
  • semantic-release and conventional-changelog read the type, the scope and the breaking change correctly.

An emoji in front of the type (✨ feat: …) breaks both. The test suite lints and parses a generated message for every built-in type and a set of field combinations to keep this true.

commitlint's subject-case rule cannot judge a subject that starts with an emoji. commit-jazzer lower-cases the title itself instead.

Header length ​

commitlint rejects headers over 100 characters by default. While you type the title, the counter shows how much of headerMaxLength is left after the type, scope and emoji. When the breaking change question is still ahead, one character is kept for the !.

The answers have limits of their own: the title 70 characters, the scope 30, the issues 100. Change them with baseQuestionsOptions[].options.validations.length. The scope, the title and the breaking change must fit on one line, and the scope cannot contain parentheses.

Changing the template ​

template is a Handlebars template. Values are inserted as typed: &, < and quotes are not escaped.

VariableValue
type, component, title, description, comment, breaking, issuesThe answers.
emoji, codeThe emoji of the type and its shortcode, e.g. ✨ and :sparkles:.
scope(component), or empty.
bang! for a breaking change, or empty.
issuePrefixThe issuePrefix option.
issueFooterOne <issuePrefix> <issue> line per issue.

Shortcodes instead of unicode emoji: the default template with in place of .

json
{
  "template": "{{type}}{{scope}}{{bang}}: {{code}} {{title}}\n\n{{description}}\n\n{{comment}}\n\n{{#if breaking}}BREAKING CHANGE: {{breaking}}{{/if}}\n{{issueFooter}}"
}

Questions whose answer the template never writes are not asked: with the 1.x format below only the type and the title are. The 1.x format:

json
{
  "template": "{{type}}: {{emoji}} - {{title}}"
}

The only rule for valid Conventional Commits: keep : at the start of the first line.

Body lines are wrapped at bodyLineLength; footer lines (BREAKING CHANGE: …, Closes #12, Co-authored-by: …) are never wrapped, and a wrapped line never starts with #. In hook mode git removes lines that start with # after you close the editor, so don't start a body line with an issue number.

Released under the MIT License.