web-mode.el is an autonomous emacs major-mode for editing web templates.
HTML documents can embed parts (CSS / JavaScript) and blocks (client / server side).

Native Features


html template editor
Screencast on YouTube (prefer HD Quality)


As a result, a block can be located in a part that is embedded in an HTML document.


You can download this mode here.

A GitHub repository is available: http://github.com/fxbois/web-mode/

web-mode.el is also available on melpa and on melpa stable.

web-mode.el is GPL and Free Software.


First drop the file web-mode.el in a directory defined in your load-path.
Then, add in your .emacs (require 'web-mode) (add-to-list 'auto-mode-alist '("\\.phtml\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.tpl\\.php\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.[agj]sp\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.as[cp]x\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.djhtml\\'" . web-mode))

Using web-mode for editing plain HTML files can be done this way (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))

You can also edit plain js, jsx, css, scss, xml files.

Associate an engine

A specific engine can be forced with web-mode-engines-alist.

(setq web-mode-engines-alist '(("php" . "\\.phtml\\'") ("blade" . "\\.blade\\.")) )

Using this association list is required as soon as the file extension is unknown (by web-mode) or is too general (e.g. *.html). In summary, you may have to set both auto-mode-alist and web-mode-engines-alist.

Since the version v10, you can also put a fragment like -*- engine:ENGINE_NAME -*- in a comment at the beginning of your template (web-mode-enable-engine-detection must be set to t in your .emacs)

<?php /* -*- engine:php -*- */ ?> <span><?=$x?></span>

The recognized file extensions are listed in the Engine families paragraph.

Associate a content type

web-mode.el can deal with many content types: html, xml, javascript, jsx, json, css. This was needed to edit *.js.erb files for example: js files that embed ruby blocks.

Sometimes, web-mode.el can not guess the content type with the file extension.
e.g. you want to associate *.api files with web-mode.

The var web-mode-content-types-alist can be used to associate a file path with a content type

(add-to-list 'auto-mode-alist '("\\.api\\'" . web-mode)) (add-to-list 'auto-mode-alist '("/some/react/path/.*\\.js[x]?\\'" . web-mode)) (setq web-mode-content-types-alist '(("json" . "/some/path/.*\\.api\\'") ("xml" . "/other/path/.*\\.api\\'") ("jsx" . "/some/react/path/.*\\.js[x]?\\'")))


The first customisations can be put in a hook like this (defun my-web-mode-hook () "Hooks for Web mode." (setq web-mode-markup-indent-offset 2) ) (add-hook 'web-mode-hook 'my-web-mode-hook)

The customisations below should not be put in the hook. Declare them before loading web-mode.el



  • M-; comment / uncomment line(s)
  • C-c C-f toggle folding on a tag/block
  • C-c C-i indent entire buffer
  • C-c C-m mark and expand
  • C-c C-s insert snippet
  • C-c C-w toggle display of invalid whitespaces


  • C-c C-d a replace apostrophes
  • C-c C-d d show tag mismatch
  • C-c C-d e replace HTML entities
  • C-c C-d n normalize
  • C-c C-d q replace dumb quotes
  • C-c C-d t traverse dom tree
  • C-c C-d x xpath


  • C-c C-b b block beginning
  • C-c C-b c block close
  • C-c C-b e block end
  • C-c C-b k block kill
  • C-c C-b n next block
  • C-c C-b p previous block
  • C-c C-b s block select

HTML element

  • C-c C-e / element close
  • C-c C-e a select element content
  • C-c C-e b element beginning
  • C-c C-e c element clone
  • C-c C-e d child element (down)
  • C-c C-e e element end
  • C-c C-e f toggle folding on children
  • C-c C-e i element insert
  • C-c C-e k element kill
  • C-c C-e m mute blanks between children
  • C-c C-e n next element
  • C-c C-e p previous element
  • C-c C-e r rename element
  • C-c C-e s select element
  • C-c C-e t transpose element
  • C-c C-e u parent element (up)
  • C-c C-e v element vanish
  • C-c C-e w wrap element

HTML tag

  • C-c C-t a sort attributes
  • C-c C-t b tag beginning
  • C-c C-t e tag end
  • C-c C-t m fetch matching tag
  • C-c C-t n next tag
  • C-c C-t p previous tag
  • C-c C-t s select tag

HTML attribute

  • C-c C-a b attribute beginning
  • C-c C-a e attribute end
  • C-c C-a i attribute insert
  • C-c C-a k attribute kill
  • C-c C-a n attribute next
  • C-c C-a p attribute previous
  • C-c C-a s attribute select
  • C-c C-a t attribute transpose

Helper functions

Engine families

Never forget to update the auto-mode-alist.
django twig, jinja, erlydtl, swig, liquid, selmer (Clojure), clabango, swig, nunjucks *.djhtml, *twig*, *.dtl, *.tmpl
php *.php, *.psp, *.ctp php
erberuby, ember, erubis, embedded javascript*.erb, *.rhtml, *.ejs
velocitycheetah, vtl*.vsl, *.vtl, *.vm
gogtl*.gohtml, *.gotmpl
jsp*.jsp, *.gsp
aspx*.aspx, *.ascx
razorplay, play2*.cshtml, *.vbhtml
ctemplatemustache, handlebars, ember, hapax, ngtemplate, meteor, blaze*.hbs, *.mustache
template-toolkit*.tt, *.tt3



To report a bug, submit an issue on GitHub (note: a screenshot, an example on gist and the output of M-x web-mode-debug are always useful). It may also be a good idea to look at the *Messages* buffer.

For general questions (install, config, etc), use emacs.stackexchange.com (do not forget to associate the web-mode tag).


2015-XX : current/devel
2015-07 : v12 aka “Paris” Release
2015-02 : v11 aka “Val d’Isère” Release
2014-10 : v10 aka “Cinque Terre” Release
2014-05: v9 aka “IRCAM” Release
2014-01: v8 aka “Copenhagen” Release
2013-09: v7 aka “Verteuil” Release
2013-06: v6 aka “Roma” Release
2013-03: v5 aka “Tignes” Release
2013-01: v4 aka “Trianon Palace” Release
  • compatibility with new engines: velocity/cheetah, smarty
  • compatibility with the alternative FreeMarker syntax: [# ] [@ ] (thx Daniel Dekany)
  • indentation improvement (thx gnuvince)
  • speed improvement when using web-mode-engines-alist
2012-12: v3.5 aka “St Petersbourg” Release
2012-11: v3 aka “Sandy at the Knickerbocker Club” Release
2012-09: v2 aka “La Baule” Release
2012-05: v1 aka “Valençay” Release
2011-08: v0 aka “La Ferté-Vidame” Release


Ivan Toshkov, Guillaume Pasquet, Andrei Chitu, Jose Peleteiro, Tad Fisher, Lee SanGu, Igor Shymko, Teruki Shigitani, Ed Slocomb, Adam Sokolnicki, Haijun Yang, Mitchel Humpherys, Brent Carmer, Bin Chen, Nate Eagleson, Matthew Carter, Stephen Whipple


François-Xavier Bois - < fxbois AT Google Mail Service >

Co-founder and CTO of Kernix, Digital Agency (Web/Mobile) in Paris (FR).

@fxbois -

web-mode.el has been presented to the 7th European Lisp Symposium with paper Heterogeneous recursive code parsing with Emacs Lisp.

Friendly links: scopalto, magazines gratuits au format pdf, DBSE, ultimate recommendation engine

Kernix, Paris (FR), is hiring good Symfony/NodeJS developers.
Do not hesitate to send me your CV.