You can watch the video version of this post on my YouTube channel here

In this post, we will take a large init.lua configuration file for Neovim, and split into easy to manage files for each plugin. We will then use packer to configure each plugin which will give us an easier to maintain setup going forward.

Neovim start screen
Finally got around to adding a custom start screen.


Since moving my Neovim config to Lua about 6 months ago, I’ve got by having all my config in a single init.lua file.

But as my config has stabilized, and I’ve got more and more bespoke config in there, it’s become unwieldy.

In the last few weeks I split out the key mappings and the options into their own files but this still left me a 500+ line init.lua, a 112 line mappings.lua file and a 43 line options.lua file.

Despite those minor concessions to organisation, making any tweaks to the config of a plugin was still pretty burdensome, as I trekked and searched up and down the file for the relevant bit of config.

It was time to roll up my sleeves and sort out this mess.

I’ve been looking at other peoples configs and found one that I thought struck a good balance of simplicity and modularity. The approach of the setup I’m about to share is largely based on this one by Michael Peter. Anything clever here is certainly due to his config. Anything dumb most likely something I did!

Premise and method

The basic idea is that we have the init.lua merely require three other files:


The plugins file is the main file which uses Packer to require the various plugins. Each plugin then has its own setup file which lives inside a sub folder, called setup. So, for example, all the setup for Telescope is inside a file at .config/nvim/lua/setup/telescope.lua, all the config for Lualine is in .config/nvim/lua/setup/lualine.lua and on and on.

The structure of files at the end
This was the structure I ended up with

I had been using Paq as my plugin manager which is super fast and lightweight. Packer, while weightier, adds a few extras and graphical niceties.

The contents of those, per plugin, setup files is the same as it would have been when they were in my single file before. So, while the config for something like Telescope may run to a hundred lines, the setup for something like ‘autopairs’ is as simple as require("nvim-autopairs").setup({}). That’s it – that’s all that’s in the file. But by giving each plugin that needs some (any) setup its own file it keeps it predictable.

Then we use the config option in Packer to associate each plugin with its associated setup file. In the ‘plugins.lua’ file, some plugin setups are more complex than others, for example, here is the section for ‘cmp’:

      requires = {
        { "hrsh7th/cmp-nvim-lsp" },
        { "hrsh7th/cmp-nvim-lua" },
        { "hrsh7th/cmp-buffer" },
        { "hrsh7th/cmp-path" },
        { "hrsh7th/cmp-cmdline" },
        { "hrsh7th/vim-vsnip" },
        { "hrsh7th/cmp-vsnip" },
        { "hrsh7th/vim-vsnip-integ" },
        { "f3fora/cmp-spell", { "hrsh7th/cmp-calc" }, { "hrsh7th/cmp-emoji" } },
      config = get_setup("cmp"),

You can see a few things going on there. Besides the main plugin request, we have a requires map that tells Packer all the other plugins needed to go with that one; and Packer is smart enough to deduce which plugins it already has from another plugin and which it needs to get.

There’s a little convenience function used at the end for grabbing the config for each plugin in our setup folder. That get_setup function looks like this:

function get_setup(name)
  return string.format('require("setup/%s")', name)

It simply creates a string that does a require of the name of the plugin you pass to it as an argument. So, get_setup("cmp") is generating require("setup/cmp") which Packer then uses to fetch the file.


At the end of the Packer configuration are a few ‘nice to haves’. For example, I’ve set it to work in a floating window which I feel is neater.

Even with the start up screen, startup of this config is pretty rapid. But I’ve also enabled ‘profile’ so I can maybe start looking at optimising startup times at some point.

On the subject of start screens, I opted for Alpha, and robbed/amended one of the linked configs.

That gives me not just the nice start screen but also the running count of my plugins. Perhaps that’s a good point to print the startup time in future too?

Never plain sailing

Every plugin manager I’ve ever used promises the ability to ‘bootstrap’ things on a fresh install. The reality is, the first time I tried to get this install working, it all blew up on the first couple or runs. I went back and forth plenty of times, ironing out issues in the way I had written the config before it all actually worked and sprang to life.

I also hit an issue where I thought things were working for the outline plugin I use but it wasn’t picking up my configuration. It wasn’t producing an error but it wasn’t working either. Turned out I needed to swap from using a config for that plugin to a setup so neovim knew what to do with the options at the correct point.

Now it is all working, it’s great. I especially like Packers UI feedback of changes to plugins when you update. And it gives you useful feedback about when you happen to have included a plugin multiple times. Which is something I did about 806 times.

Neovim updating plugins
Packer provides handy additional information on what was changed

Go forth and configure

Hopefully this may prove a useful touch point if you are in the market for organising your own Lua config. If there is some bone-headed mistake somewhere please let me know in the comments. Feedback always gratefully received.

If you are interested in looking at my WIP config, it’s currently in this gist – probably move it into a repo soon!