Automatically reopen the files and windows you had open. It's like you never left!

- πΎ Automatically save and restore sessions, with customizable filters
- π― Session picker, with support for Telescope, Snacks, Fzf-Lua, and
vim.ui.select
- π Track directory changes
- πΏ Separate sessions per git branch
- πͺ Customizable with Hooks
- ποΈ Save custom data along with your session
When you start nvim
, AutoSession will try to restore a session for the current working directory (cwd
) if it exists. If it does, it'll reopen all of your buffers and windows. If not, nothing happens. When you quit nvim
, AutoSession will automatically save a session for cwd
so you can pick up where you left off.
return {
"rmagatti/auto-session",
lazy = false,
---enables autocomplete for opts
---@module "auto-session"
---@type AutoSession.Config
opts = {
suppressed_dirs = { "~/", "~/Projects", "~/Downloads", "/" },
-- log_level = 'debug',
},
}
Note: For other plugin managers, make sure setup is called somewhere, e.g.:
require("auto-session").setup {}
Default settings (you don't have to copy these into your config):
local defaults = {
-- Saving / restoring
enabled = true, -- Enables/disables auto creating, saving and restoring
auto_save = true, -- Enables/disables auto saving session on exit
auto_restore = true, -- Enables/disables auto restoring session on start
auto_create = true, -- Enables/disables auto creating new session files. Can be a function that returns true if a new session file should be allowed
auto_restore_last_session = false, -- On startup, loads the last saved session if session for cwd does not exist
cwd_change_handling = false, -- Automatically save/restore sessions when changing directories
single_session_mode = false, -- Enable single session mode to keep all work in one session regardless of cwd changes. When enabled, prevents creation of separate sessions for different directories and maintains one unified session. Does not work with cwd_change_handling
-- Filtering
suppressed_dirs = nil, -- Suppress session restore/create in certain directories
allowed_dirs = nil, -- Allow session restore/create in certain directories
bypass_save_filetypes = nil, -- List of filetypes to bypass auto save when the only buffer open is one of the file types listed, useful to ignore dashboards
close_filetypes_on_save = { "checkhealth" }, -- Buffers with matching filetypes will be closed before saving
close_unsupported_windows = true, -- Close windows that aren't backed by normal file before autosaving a session
preserve_buffer_on_restore = nil, -- Function that returns true if a buffer should be preserved when restoring a session
-- Git / Session naming
git_use_branch_name = false, -- Include git branch name in session name
git_auto_restore_on_branch_change = false, -- Should we auto-restore the session when the git branch changes. Requires git_use_branch_name
custom_session_tag = nil, -- Function that can return a string to be used as part of the session name
-- Deleting
auto_delete_empty_sessions = true, -- Enables/disables deleting the session if there are only unnamed/empty buffers when auto-saving
purge_after_minutes = nil, -- Sessions older than purge_after_minutes will be deleted asynchronously on startup, e.g. set to 14400 to delete sessions that haven't been accessed for more than 10 days, defaults to off (no purging), requires >= nvim 0.10
-- Saving extra data
save_extra_data = nil, -- Function that returns extra data that should be saved with the session. Will be passed to restore_extra_data on restore
restore_extra_data = nil, -- Function called when there's extra data saved for a session
-- Argument handling
args_allow_single_directory = true, -- Follow normal session save/load logic if launched with a single directory as the only argument
args_allow_files_auto_save = false, -- Allow saving a session even when launched with a file argument (or multiple files/dirs). It does not load any existing session first. Can be true or a function that returns true when saving is allowed. See documentation for more detail
-- Misc
log_level = "error", -- Sets the log level of the plugin (debug, info, warn, error).
root_dir = vim.fn.stdpath("data") .. "/sessions/", -- Root dir where sessions will be stored
show_auto_restore_notif = false, -- Whether to show a notification when auto-restoring
restore_error_handler = nil, -- Function called when there's an error restoring. By default, it ignores fold errors otherwise it displays the error and returns false to disable auto_save
continue_restore_on_error = true, -- Keep loading the session even if there's an error
lsp_stop_on_restore = false, -- Should language servers be stopped when restoring a session. Can also be a function that will be called if set. Not called on autorestore from startup
lazy_support = true, -- Automatically detect if Lazy.nvim is being used and wait until Lazy is done to make sure session is restored correctly. Does nothing if Lazy isn't being used
---@type SessionLens
session_lens = {
picker = nil, -- "telescope"|"snacks"|"fzf"|"select"|nil Pickers are detected automatically but you can also set one manually. Falls back to vim.ui.select
load_on_setup = true, -- Only used for telescope, registers the telescope extension at startup so you can use :Telescope session-lens
picker_opts = nil, -- Table passed to Telescope / Snacks / Fzf-Lua to configure the picker. See below for more information
---@type SessionLensMappings
mappings = {
-- Mode can be a string or a table, e.g. {"i", "n"} for both insert and normal mode
delete_session = { "i", "<C-d>" }, -- mode and key for deleting a session from the picker
alternate_session = { "i", "<C-s>" }, -- mode and key for swapping to alternate session from the picker
copy_session = { "i", "<C-y>" }, -- mode and key for copying a session from the picker
},
---@type SessionControl
session_control = {
control_dir = vim.fn.stdpath("data") .. "/auto_session/", -- Auto session control dir, for control files, like alternating between two sessions with session-lens
control_filename = "session_control.json", -- File name of the session control file
},
},
}
Types
---@class AutoSession.Config
---
---Saving / restoring
---@field enabled? boolean
---@field auto_save? boolean
---@field auto_restore? boolean
---@field auto_create? boolean|fun(): should_create_session:boolean
---@field auto_restore_last_session? boolean
---@field cwd_change_handling? boolean
---@field single_session_mode? boolean
---
---Filtering
---@field suppressed_dirs? table
---@field allowed_dirs? table
---@field bypass_save_filetypes? table
---@field close_filetypes_on_save? table
---@field close_unsupported_windows? boolean
---@field preserve_buffer_on_restore? fun(bufnr:number): preserve_buffer:boolean
---
---Deleting
---@field auto_delete_empty_sessions? boolean
---@field purge_after_minutes? number
---
---Git / Session naming
---@field git_use_branch_name? boolean
---@field git_auto_restore_on_branch_change? boolean
---@field custom_session_tag? fun(session_name:string): tag:string
---
---Saving extra data
---@field save_extra_data? fun(session_name:string): extra_data:string|nil
---@field restore_extra_data? fun(session_name:string, extra_data:string)
---
---Argument handling
---@field args_allow_single_directory? boolean
---@field args_allow_files_auto_save? boolean|fun(): disable_auto_save:boolean
---
---Misc
---@field log_level? string|integer
---@field root_dir? string
---@field show_auto_restore_notif? boolean
---@field restore_error_handler? fun(error_msg:string): disable_auto_save:boolean
---@field continue_restore_on_error? boolean
---@field lsp_stop_on_restore? boolean|fun()
---@field lazy_support? boolean
---
---@field session_lens? SessionLens
---
---Session Lens Config
---@class SessionLens
---@field picker? "telescope"|"snacks"|"fzf"|"select"
---@field load_on_setup? boolean
---@field picker_opts? table
---@field session_control? SessionControl
---@field mappings? SessionLensMappings
---
---@class SessionLensMappings
---@field delete_session? table
---@field alternate_session? table
---@field copy_session? table
---
---@class SessionControl
---@field control_dir? string
---@field control_filename? string
---
---Hooks
---@field pre_save_cmds? table executes before a session is saved
---@field save_extra_cmds? table executes before a session is saved
---@field post_save_cmds? table executes after a session is saved
---@field pre_restore_cmds? table executes before a session is restored
---@field post_restore_cmds? table executes after a session is restored
---@field pre_delete_cmds? table executes before a session is deleted
---@field post_delete_cmds? table executes after a session is deleted
---@field no_restore_cmds? table executes at VimEnter when no session is restored
---@field pre_cwd_changed_cmds? table executes before cwd is changed if cwd_change_handling is true
---@field post_cwd_changed_cmds? table executes after cwd is changed if cwd_change_handling is true
For the best experience, set sessionoptions
to:
Lua
vim.o.sessionoptions = "blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal,localoptions"
VimL
set sessionoptions+=winpos,terminal,folds
:SessionSave " saves a session based on the `cwd` in `root_dir`
:SessionSave my_session " saves a session called `my_session` in `root_dir`
:SessionRestore " restores a session based on the `cwd` from `root_dir`
:SessionRestore my_session " restores `my_session` from `root_dir`
:SessionDelete " deletes a session based on the `cwd` from `root_dir`
:SessionDelete my_session " deletes `my_session` from `root_dir`
:SessionDisableAutoSave " disables autosave
:SessionDisableAutoSave! " enables autosave (still does all checks in the config)
:SessionToggleAutoSave " toggles autosave
:SessionPurgeOrphaned " removes all orphaned sessions with no working directory left.
:SessionSearch " opens a session picker, see Config.session_lens.picker
:Autosession search " opens a session picker, see Config.session_lens.picker
:Autosession delete " opens a vim.ui.select picker to choose a session to delete.
Starting nvim
- When starting
nvim
with no arguments, AutoSession will try to restore the session forcwd
if one exists. - When starting
nvim .
(or another directory), AutoSession will try to restore the session for that directory. See argument handling for more details. - When starting
nvim some_file.txt
(or multiple files), by default, AutoSession won't do anything. See argument handling for more details. - Even after starting
nvim
with a file argument, a session forcwd
can still be manually restored by running:SessionRestore
. - When piping to
nvim
, e.g:cat myfile | nvim
, AutoSession disables itself.
Exiting nvim
- When you exit, AutoSession will try to automatically save a session for
cwd
- If autosaving is enabled (
auto_save = true
) and - If there are non-empty buffers and
- If
cwd
isn't insuppressed_dirs
or, if set, it is inallowed_dirs
and - If the session doesn't exist and
auto_create = true
- Then it will save a session for
cwd
Session naming:
- By default, sessions are named for
cwd
- You can manually name a session with
:SessionSave my_session
. Manually named sessions can't be auto-restored but once restored they will be used for autosaving. - The current git branch can optionally be included in the session name.
- You can also set a custom function that returns a string to include in the session name so you have different sessions for
cwd
per tmux session, window, etc.
You can use Telescope, snacks.nvim, Fzf-Lua to see, load, and delete your sessions. The configuration options are in the session_lens
section:
return {
"rmagatti/auto-session",
lazy = false,
keys = {
-- Will use Telescope if installed or a vim.ui.select picker otherwise
{ "<leader>wr", "<cmd>SessionSearch<CR>", desc = "Session search" },
{ "<leader>ws", "<cmd>SessionSave<CR>", desc = "Save session" },
{ "<leader>wa", "<cmd>SessionToggleAutoSave<CR>", desc = "Toggle autosave" },
},
---enables autocomplete for opts
---@module "auto-session"
---@type AutoSession.Config
opts = {
-- The following are already the default values, no need to provide them if these are already the settings you want.
session_lens = {
picker = nil, -- "telescope"|"snacks"|"fzf"|"select"|nil Pickers are detected automatically but you can also manually choose one. Falls back to vim.ui.select
mappings = {
-- Mode can be a string or a table, e.g. {"i", "n"} for both insert and normal mode
delete_session = { "i", "<C-d>" },
alternate_session = { "i", "<C-s>" },
copy_session = { "i", "<C-y>" },
},
picker_opts = {
-- For Telescope, you can set theme options here, see:
-- https://github.com/nvim-telescope/telescope.nvim/blob/master/doc/telescope.txt#L112
-- https://github.com/nvim-telescope/telescope.nvim/blob/master/lua/telescope/themes.lua
--
-- border = true,
-- layout_config = {
-- width = 0.8, -- Can set width and height as percent of window
-- height = 0.5,
-- },
-- For Snacks, you can set layout options here, see:
-- https://github.com/folke/snacks.nvim/blob/main/docs/picker.md#%EF%B8%8F-layouts
--
-- preset = "dropdown",
-- preview = false,
-- layout = {
-- width = 0.4,
-- height = 0.4,
-- },
-- For Fzf-Lua, picker_opts just turns into winopts, see:
-- https://github.com/ibhagwan/fzf-lua#customization
--
-- height = 0.8,
-- width = 0.50,
},
-- Telescope only: If load_on_setup is false, make sure you use `:SessionSearch` to open the picker as it will initialize everything first
load_on_setup = true,
},
},
}
Use :SessionSearch
to launch the session picker. It will automatically look for Telescope, Snacks, and Fzf-Lua and use the first one it finds. If you have multiple pickers installed, you can set session-lens.picker
to manually pick your picker. If no pickers are installed, it'll fall back to vim.ui.select
If you're using Telescope and want to launch the picker via :Telescope session-lens
, set session-lens.load_on_setup = true
or make sure you've called :SessionSearch
first.
The following default keymaps are available when the session-lens picker is open:
<CR>
loads the currently highlighted session.<C-s>
swaps to the previously opened session. This can give you a nice flow if you're constantly switching between two projects.<C-d>
will delete the currently highlighted session. This makes it easy to keep the session list clean.<C-y>
will let you make a copy of the highlighted session.
When using Telescope, Snacks, or Fzf-Lua, you can customize the picker using picker_opts
. Refer to the links above for the specific picker configuration options.

There are two config options, allowed_dirs
and suppressed_dirs
, that control which directories AutoSession will auto-save a session for. If allowed_dirs
is set, sessions will only be auto-saved in matching directories. If suppressed_dirs
is set, then a session won't be auto-saved for a matching directory. If both are set, a session will only be auto-saved if it matches an allowed dir and does not match a suppressed dir.
Both options are a table of directories, with support for globs:
opts = {
allowed_dirs = { "/some/dir/", "/projects/*" },
suppressed_dirs = { "/projects/secret" },
}
With those options, sessions would only be auto-saved for /some/dir
and any direct child of /projects
(e.g. /projects/myproject
but not /projects/myproject/submodule
) except /projects/secret
If you want even more fine-grained control, you can instead set auto_create
to a function to conditionally create a session.
AutoSession can track cwd
changes!
It's disabled by default, but when enabled it works as follows:
- DirChangedPre (before the cwd actually changes):
- Save the current session
- Clear all buffers
%bw!
. This guarantees buffers don't bleed to the next session. - Clear jumps. Also done so there is no bleeding between sessions.
- Run the
pre_cwd_changed
hook
- DirChanged (after the cwd has changed):
- Restore session using new cwd
- Run the
post_cwd_changed
hook
Now when you changes the cwd with :cd some/new/dir
AutoSession handles it gracefully, saving the current session so there aren't losses and loading the session for the upcoming cwd if it exists.
Hooks are available for custom actions before and after the cwd
is changed. Here's the config for tracking cwd and a hook example:
opts = {
cwd_change_handling = true,
pre_cwd_changed_cmds = {
"tabdo NERDTreeClose", -- Close NERDTree before saving session
},
post_cwd_changed_cmds = {
function()
require("lualine").refresh() -- example refreshing the lualine status line _after_ the cwd changes
end,
},
}
To include the current git branch in the session name, set git_use_branch_name = true,
in your config
AutoSession can also optionally auto-restore sessions when switching branches. Set git_auto_restore_on_branch_change = true,
to enable that. Note, if you have modified files open when the branch is switched, AutoSession will ask if you want to close those files and restore the session or cancel restoring the session. If you cancel restoring the session, auto-saving will be disabled.
opts = {
git_use_branch_name = true,
git_auto_restore_on_branch_change = true,
}
If you use a dashboard, you probably don't want to try and save a session when just the dashboard is open. To avoid that, add your dashboard filetype to the bypass list as follows:
opts = {
bypass_save_filetypes = { "alpha", "dashboard", "snacks_dashboard" }, -- or whatever dashboard you use
}
You can show the current session name in the statusline by using the function current_session_name()
. With no arguments, it will return the full session name. For automatically created sessions that will be the path where the session was saved. If you only want the last directory in the path, you can call current_session_name(true)
.
Here's an example using Lualine:
require("lualine").setup {
options = {
theme = "tokyonight",
},
sections = {
lualine_x = {
function()
return require("auto-session.lib").current_session_name(true)
end,
},
},
}

Command hooks are a list of commands or functions that get executed at different stages of the session management lifecycle.
Command hooks exist in the format: {hook_name}
{pre_save}
: executes before a session is saved{save_extra}
: executes after a session is saved, saves returned string or table to*x.vim
, reference:help mks
{post_save}
: executes after a session is saved{pre_restore}
: executes before a session is restored{post_restore}
: executes after a session is restored{pre_delete}
: executes before a session is deleted{post_delete}
: executes after a session is deleted{no_restore}
: executes atVimEnter
when no session is restored{pre_cwd_changed}
: executes before a directory is changed (ifcwd_change_handling
is enabled){post_cwd_changed}
: executes after a directory is changed (ifcwd_change_handling
is enabled)
Each hook is a table of vim commands or lua functions (or a mix of both):
opts = {
-- {hook_name}_cmds = {"{hook_command1}", "{hook_command2}"}
pre_save_cmds = {
"tabdo NERDTreeClose", -- Close NERDTree before saving session
},
post_restore_cmds = {
"someOtherVimCommand",
function()
-- Restore nvim-tree after a session is restored
local nvim_tree_api = require "nvim-tree.api"
nvim_tree_api.tree.open()
nvim_tree_api.tree.change_root(vim.fn.getcwd())
nvim_tree_api.tree.reload()
end,
},
-- Save quickfix list and open it when restoring the session
save_extra_cmds = {
function()
local qflist = vim.fn.getqflist()
-- return nil to clear any old qflist
if #qflist == 0 then
return nil
end
local qfinfo = vim.fn.getqflist { title = 1 }
for _, entry in ipairs(qflist) do
-- use filename instead of bufnr so it can be reloaded
entry.filename = vim.api.nvim_buf_get_name(entry.bufnr)
entry.bufnr = nil
end
local setqflist = "call setqflist(" .. vim.fn.string(qflist) .. ")"
local setqfinfo = 'call setqflist([], "a", ' .. vim.fn.string(qfinfo) .. ")"
return { setqflist, setqfinfo, "copen" }
end,
},
}
You can use the config functions save_extra_data
and restore_extra_data
to save arbitrary data as part of your session.
As an example, here's how you could save DAP breakpoints with your session:
---@module "auto-session"
---@type AutoSession.Config
local opts = {
save_extra_data = function(_)
local ok, breakpoints = pcall(require, "dap.breakpoints")
if not ok or not breakpoints then
return
end
local bps = {}
local breakpoints_by_buf = breakpoints.get()
for buf, buf_bps in pairs(breakpoints_by_buf) do
bps[vim.api.nvim_buf_get_name(buf)] = buf_bps
end
if vim.tbl_isempty(bps) then
return
end
local extra_data = {
breakpoints = bps,
}
return vim.fn.json_encode(extra_data)
end,
restore_extra_data = function(_, extra_data)
local json = vim.fn.json_decode(extra_data)
if json.breakpoints then
local ok, breakpoints = pcall(require, "dap.breakpoints")
if not ok or not breakpoints then
return
end
vim.notify "restoring breakpoints"
for buf_name, buf_bps in pairs(json.breakpoints) do
for _, bp in pairs(buf_bps) do
local line = bp.line
local opts = {
condition = bp.condition,
log_message = bp.logMessage,
hit_condition = bp.hitCondition,
}
breakpoints.set(opts, vim.fn.bufnr(buf_name), line)
end
end
end
end,
}
See the wiki for more advanced ways to use AutoSession. And feel free to share new and interesting ways you're using AutoSession!
You might run into issues with Firenvim or another plugin and want to disable AutoSession altogether based on some condition. For example, this will disable AutoSession when started under Firenvim:
return {
"rmagatti/auto-session",
lazy = false,
cond = not vim.g.started_by_firenvim and not vim.g.vscode,
}
Or in VimScript:
if exists('g:started_by_firenvim')
let g:auto_session_enabled = v:false
endif
You can also disable the plugin by setting the auto_session_enabled
option to false at startup:
nvim --cmd "let g:auto_session_enabled = v:false"
First run :checkhealth auto-session
to see if it detects any problems.
If that doesn't help, you can:
- refer to the wiki page.
- check the Discussions
- or file an Issue
Neovim >= 0.10
For support < 0.10, use tag pre-nvim-0.10
Tested with:
v0.10.3 - nightly