Filetype detection automatically identifies the type of file you’re editing and applies appropriate settings, syntax highlighting, and plugins.
Filetype System Overview
Neovim can detect file types and automatically:
- Apply syntax highlighting
- Load filetype-specific plugins
- Set appropriate indentation rules
- Configure buffer-local options
Enabling Filetype Features
Enable filetype detection
-- In init.lua (enabled by default in Neovim)
vim.cmd('filetype on')
Enable filetype plugins
vim.cmd('filetype plugin on')
Loads ftplugin files for detected filetypes.Enable filetype indentation
vim.cmd('filetype indent on')
Loads indent rules for detected filetypes.Enable all at once
vim.cmd('filetype plugin indent on')
Default BehaviorNeovim enables filetype detection by default. You typically don’t need to explicitly enable it unless you’ve disabled it.
How Filetype Detection Works
Neovim detects filetypes using:
- File extension:
*.lua → lua, *.py → python
- File name:
Makefile → make, Dockerfile → dockerfile
- File path:
/etc/hosts → conf
- File contents: Shebang lines, modelines
-- Neovim loads these files for detection:
-- $VIMRUNTIME/filetype.lua (fast, pattern-based)
-- $VIMRUNTIME/filetype.vim (legacy, more patterns)
-- $VIMRUNTIME/scripts.vim (content-based detection)
Adding Custom Filetype Detection
Use vim.filetype.add() to register custom patterns:
By Extension
vim.filetype.add({
extension = {
foo = 'fooscript', -- *.foo → fooscript
bar = 'barscript', -- *.bar → barscript
conf = 'dosini', -- *.conf → dosini
},
})
By Filename
vim.filetype.add({
filename = {
['Makefile'] = 'make',
['README'] = 'markdown',
['.env'] = 'sh',
['.gitignore'] = 'gitignore',
},
})
By Pattern
vim.filetype.add({
pattern = {
['.*/%.config/foo/.*'] = 'fooscript', -- Path patterns
['.*%.template%.html'] = 'html', -- Multiple extensions
['.*%.component%.ts'] = 'typescript',
},
})
By Content Detection
vim.filetype.add({
extension = {
pl = function(path, bufnr)
-- Detect if it's Perl or Prolog by content
local first_line = vim.api.nvim_buf_get_lines(bufnr, 0, 1, false)[1]
if first_line and first_line:match('^#!/.*perl') then
return 'perl'
end
return 'prolog'
end,
},
})
Complete Detection Example
vim.filetype.add({
-- By extension
extension = {
mdx = 'markdown',
conf = 'dosini',
env = 'sh',
},
-- By filename
filename = {
['Brewfile'] = 'ruby',
['.eslintrc'] = 'json',
['docker-compose.yml'] = 'yaml.docker-compose',
},
-- By pattern (Lua patterns)
pattern = {
['.*%.blade%.php'] = 'blade',
['.*/%.config/nvim/.*%.lua'] = 'lua',
['.*%.component%.ts'] = 'typescript',
['.*/%.github/workflows/.*%.yml'] = 'yaml.github-actions',
},
})
Filetype-Specific Configuration
Using Autocommands
The most flexible approach:
-- Python settings
vim.api.nvim_create_autocmd('FileType', {
pattern = 'python',
callback = function()
vim.opt_local.expandtab = true
vim.opt_local.shiftwidth = 4
vim.opt_local.tabstop = 4
vim.opt_local.colorcolumn = '88'
-- Python-specific keymaps
vim.keymap.set('n', '<leader>r', ':!python3 %<CR>', { buffer = true })
end,
})
-- JavaScript/TypeScript settings
vim.api.nvim_create_autocmd('FileType', {
pattern = { 'javascript', 'typescript', 'javascriptreact', 'typescriptreact' },
callback = function()
vim.opt_local.expandtab = true
vim.opt_local.shiftwidth = 2
vim.opt_local.tabstop = 2
end,
})
-- Markdown settings
vim.api.nvim_create_autocmd('FileType', {
pattern = 'markdown',
callback = function()
vim.opt_local.wrap = true
vim.opt_local.spell = true
vim.opt_local.linebreak = true
vim.opt_local.conceallevel = 2
end,
})
-- Git commit messages
vim.api.nvim_create_autocmd('FileType', {
pattern = 'gitcommit',
callback = function()
vim.opt_local.spell = true
vim.opt_local.textwidth = 72
vim.opt_local.colorcolumn = '73'
end,
})
Using ftplugin Files
Create dedicated files for each filetype:
~/.config/nvim/
├── init.lua
└── ftplugin/
├── python.lua
├── javascript.lua
├── markdown.lua
└── lua.lua
-- ~/.config/nvim/ftplugin/python.lua
vim.opt_local.expandtab = true
vim.opt_local.shiftwidth = 4
vim.opt_local.tabstop = 4
vim.opt_local.colorcolumn = '88'
-- This automatically runs when opening Python files
-- ~/.config/nvim/ftplugin/markdown.lua
vim.opt_local.wrap = true
vim.opt_local.spell = true
vim.opt_local.conceallevel = 2
-- Markdown-specific keymaps
vim.keymap.set('n', '<leader>p', ':MarkdownPreview<CR>', { buffer = true })
ftplugin Benefits
- Automatic loading when filetype is detected
- Clean separation of filetype-specific config
- Can be overridden with
after/ftplugin/
- Follows Neovim conventions
Filetype Plugins Location
Neovim searches for filetype plugins in:
$VIMRUNTIME/ftplugin/{filetype}.lua
~/.config/nvim/ftplugin/{filetype}.lua
~/.config/nvim/after/ftplugin/{filetype}.lua
The after/ directory loads last, allowing you to override defaults.
Checking Current Filetype
View current filetype
Or in Lua::lua print(vim.bo.filetype)
Manually set filetype
Or in Lua:vim.bo.filetype = 'python'
Filetype Options
Common Buffer-Local Options
vim.api.nvim_create_autocmd('FileType', {
pattern = 'filetype_name',
callback = function()
-- Indentation
vim.opt_local.expandtab = true -- Use spaces
vim.opt_local.shiftwidth = 4 -- Indent size
vim.opt_local.tabstop = 4 -- Tab display width
vim.opt_local.softtabstop = 4 -- Tab key behavior
-- Text formatting
vim.opt_local.textwidth = 80 -- Max line length
vim.opt_local.wrap = false -- Line wrapping
vim.opt_local.linebreak = true -- Wrap at word boundaries
-- Display
vim.opt_local.conceallevel = 2 -- Conceal level
vim.opt_local.colorcolumn = '80' -- Visual guide
-- Other
vim.opt_local.spell = true -- Spell checking
vim.opt_local.commentstring = '# %s' -- Comment format
end,
})
Compound Filetypes
Use compound filetypes for specialized syntax:
vim.filetype.add({
filename = {
['docker-compose.yml'] = 'yaml.docker-compose',
},
pattern = {
['.*/%.github/workflows/.*%.ya?ml'] = 'yaml.github-actions',
},
})
-- Now you can target these specifically:
vim.api.nvim_create_autocmd('FileType', {
pattern = 'yaml.docker-compose',
callback = function()
-- Docker compose specific settings
end,
})
Disabling Filetype Plugins
Prevent specific filetype plugins from loading:
-- Disable all plugins for a filetype
vim.g.did_load_filetypes = 1 -- Disable built-in detection entirely
-- Disable specific plugin
vim.g.no_plugin_maps = 1 -- Disable all filetype plugin mappings
vim.g.no_mail_maps = 1 -- Disable mail filetype mappings
-- Use ftplugin with early return
-- ~/.config/nvim/ftplugin/python.lua
if vim.g.disable_python_ftplugin then
return
end
-- rest of config...
Override Global Variables
Some filetypes check global variables for behavior:
-- Shell syntax (sh vs bash)
vim.g.bash_is_sh = 1 -- Treat .sh files as bash
-- Python
vim.g.python_highlight_all = 1
vim.g.python_highlight_space_errors = 0
-- Markdown
vim.g.markdown_fenced_languages = {
'python',
'javascript',
'typescript',
'lua',
'bash=sh',
}
-- LaTeX
vim.g.tex_flavor = 'latex' -- Default .tex filetype
Practical Examples
Web Development
local web_group = vim.api.nvim_create_augroup('WebDev', { clear = true })
vim.api.nvim_create_autocmd('FileType', {
group = web_group,
pattern = { 'html', 'css', 'javascript', 'typescript', 'json' },
callback = function()
vim.opt_local.expandtab = true
vim.opt_local.shiftwidth = 2
vim.opt_local.tabstop = 2
end,
})
vim.api.nvim_create_autocmd('FileType', {
group = web_group,
pattern = 'json',
callback = function()
vim.keymap.set('n', '<leader>f', ':%!jq .<CR>', { buffer = true })
end,
})
Configuration Files
vim.filetype.add({
filename = {
['.env'] = 'sh',
['.env.local'] = 'sh',
['.env.development'] = 'sh',
},
pattern = {
['.*%.env%..*'] = 'sh',
},
})
vim.api.nvim_create_autocmd('FileType', {
pattern = { 'yaml', 'json', 'toml' },
callback = function()
vim.opt_local.expandtab = true
vim.opt_local.shiftwidth = 2
end,
})
Documentation
vim.api.nvim_create_autocmd('FileType', {
pattern = { 'markdown', 'text', 'rst' },
callback = function()
vim.opt_local.wrap = true
vim.opt_local.linebreak = true
vim.opt_local.spell = true
vim.opt_local.spelllang = 'en_us'
end,
})
Viewing Filetype Info
Current filetype status
Shows detection, plugin, and indent status. List all filetypes
Tab-complete to see available filetypes. View detection logic
:edit $VIMRUNTIME/filetype.lua
Check loaded scripts
Shows all loaded files including ftplugins.
Modeline for Manual Detection
Add modelines to files for explicit filetype:
# vim: set filetype=python:
<!-- vim: set ft=html: -->
Place at the start or end of file within first/last 5 lines.
Troubleshooting
Wrong Filetype DetectedIf Neovim detects the wrong filetype:
- Check current detection:
:set filetype?
- Override with
:set filetype=correcttype
- Add custom detection with
vim.filetype.add()
- Use modeline in the file
No Syntax Highlighting
- Check filetype is detected:
:set filetype?
- Ensure syntax is enabled:
:syntax on
- Check if syntax file exists:
:runtime! syntax/<filetype>.vim
- Look for errors:
:messages
Best Practices
- Use
vim.filetype.add() for custom detection
- Group related autocommands with
nvim_create_augroup()
- Use
opt_local for buffer-specific settings
- Leverage ftplugin for complex filetype configs
- Test detection with
:filetype detect
- Document custom patterns with comments
See Also
- Autocommands - Triggering actions on FileType
- Options - Buffer-local option configuration
:help filetype - Comprehensive filetype documentation
:help vim.filetype.add() - Custom filetype detection API
:help ftplugin - Filetype plugin documentation
:help new-filetype - Adding new filetype detection