Skip to main content
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

1

Enable filetype detection

-- In init.lua (enabled by default in Neovim)
vim.cmd('filetype on')
2

Enable filetype plugins

vim.cmd('filetype plugin on')
Loads ftplugin files for detected filetypes.
3

Enable filetype indentation

vim.cmd('filetype indent on')
Loads indent rules for detected filetypes.
4

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:
  1. File extension: *.lualua, *.pypython
  2. File name: Makefilemake, Dockerfiledockerfile
  3. File path: /etc/hostsconf
  4. 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

1

View current filetype

:set filetype?
Or in Lua:
:lua print(vim.bo.filetype)
2

Manually set filetype

:set filetype=python
Or in Lua:
vim.bo.filetype = 'python'
3

Detect filetype again

:filetype detect

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

1

Current filetype status

:filetype
Shows detection, plugin, and indent status.
2

List all filetypes

:setfiletype <Tab>
Tab-complete to see available filetypes.
3

View detection logic

:edit $VIMRUNTIME/filetype.lua
4

Check loaded scripts

:scriptnames
Shows all loaded files including ftplugins.

Modeline for Manual Detection

Add modelines to files for explicit filetype:
# vim: set filetype=python:
-- vim: ft=lua
<!-- 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:
  1. Check current detection: :set filetype?
  2. Override with :set filetype=correcttype
  3. Add custom detection with vim.filetype.add()
  4. Use modeline in the file
No Syntax Highlighting
  1. Check filetype is detected: :set filetype?
  2. Ensure syntax is enabled: :syntax on
  3. Check if syntax file exists: :runtime! syntax/<filetype>.vim
  4. Look for errors: :messages

Best Practices

  1. Use vim.filetype.add() for custom detection
  2. Group related autocommands with nvim_create_augroup()
  3. Use opt_local for buffer-specific settings
  4. Leverage ftplugin for complex filetype configs
  5. Test detection with :filetype detect
  6. 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

Build docs developers (and LLMs) love