A starter template for Skyrim Special Edition / Anniversary Edition SKSE plugins using CommonLibSSE-NG, CMake, and vcpkg.
Supports building on Linux (cross-compilation via clang-cl + xwin) and Windows (MSVC).
- SKSE64
- Address Library for SKSE Plugins for SE and AE, or VR Address Library for SKSEVR for VR
Mod manager (recommended):
- Install the requirements above.
- Install ExampleMod via your mod manager.
- Launch Skyrim via SKSE.
Manual:
- Install the requirements above.
- Copy
ExampleMod.dlltoData\SKSE\Plugins\. - Launch Skyrim via SKSE.
- Compatible with Skyrim SE, AE, and VR.
- No ESP/ESL required.
When loaded by Skyrim the plugin:
- Writes a log to
Data/SKSE/Plugins/ExampleMod.logvia spdlog. - Hooks
kDataLoaded(fires once all game data is loaded). - Prints to the in-game console (
~key):[ExampleMod] Loaded successfully!
- LLVM/Clang (provides
clang-cl,lld-link,llvm-lib,llvm-rc,llvm-mt) - xwin — downloads the real Windows SDK and MSVC CRT headers/libs
- Ninja
# Arch / CachyOS
sudo pacman -S clang lld llvm ninja
# Install xwin (requires Rust/cargo)
cargo install xwin
# Fetch Windows SDK + MSVC CRT headers to ~/.xwin (one-time, ~700 MB)
xwin splat --output ~/.xwinNote: On first configure,
cmake/toolchains/clang-cl-cross.cmakecreates TitleCase symlinks inside your xwin installation, e.g.:~/.xwin/sdk/lib/um/x86_64/Advapi32.lib -> advapi32.liblld-link is case-sensitive but CommonLibSSE-NG references libs with mixed-case names. The originals are untouched.
- Visual Studio 2022 or newer (or the standalone Build Tools) with Desktop development with C++, which provides MSVC, CMake, and Ninja
With the GitHub CLI, create your own repo from the template and clone it with submodules:
# Pick visibility: --public (free GitHub Actions) or --private
gh repo create your-org/your-mod --template codepuncher/CommonLibSSE-NG-template --public
gh repo clone your-org/your-mod -- --recurse-submodules
cd your-modOr click "Use this template" on GitHub, then clone your new repo with submodules:
git clone --recurse-submodules https://github.com/your-org/your-mod.git
cd your-modThe --recurse-submodules flag (or -- --recurse-submodules for gh repo clone) is required: the build needs the vcpkg and CommonLibSSE-NG submodules. If you cloned without it, run git submodule update --init --recursive.
For a repo created from the template, the Template Setup workflow (setup.yml) renames the ExampleMod and Author placeholders, deriving them from the repo name and owner, commits the result, and removes itself. It can run automatically when the repo is created; if it hasn't, trigger it from the Actions tab ("Template Setup" > "Run workflow"). Once it has run, git pull to get the renamed sources.
Prefer to rename locally, or cloned the template directly? Run the init script instead. It replaces the placeholders, initialises the submodules (CommonLibSSE-NG + vcpkg), bootstraps vcpkg, and copies .env.example to .env.
Linux — run interactively or pass arguments directly:
./scripts/init.sh
# or:
./scripts/init.sh "AuthorName" "ModName"Windows (PowerShell):
.\scripts\init.ps1
# or:
.\scripts\init.ps1 "AuthorName" "ModName"Copy .env.example to .env if you don't have one yet (the init script does this for you), then set SKYRIM_MODS_FOLDER to your mod manager's staging folder:
# Vortex (Linux, Steam):
SKYRIM_MODS_FOLDER=$HOME/.local/share/Steam/steamapps/common/Vortex Mods/skyrimse
# MO2 (Linux):
# SKYRIM_MODS_FOLDER=$HOME/MO2/mods./scripts/build.sh
# or directly:
cmake --preset release-linux
cmake --build --preset release-linuxThe DLL lands in build/release-linux/ExampleMod.dll.
./scripts/deploy.sh
# or directly (deploy.sh sources .env for you; cmake does not):
source .env && cmake --workflow --preset deployThis configures, builds, and copies ExampleMod.dll + ExampleMod.pdb directly into:
$SKYRIM_MODS_FOLDER/ExampleMod/SKSE/Plugins/
Vortex will detect the new mod folder automatically. Enable it in Vortex, then launch Skyrim.
On subsequent builds (no config change needed):
./scripts/deploy.shcmake --preset release-windows
cmake --build --preset release-windowsRun these from a Developer Command Prompt for VS (or after sourcing
vcvars64.bat) socl.exeand the Windows SDK are onPATH. The preset uses the Ninja generator, which builds with whatever MSVC toolset the environment provides.
The DLL lands in build/msvc/ExampleMod.dll.
Unit tests build without CommonLibSSE, so they run as a native executable. Two presets are available:
- Linux (
test-linux) uses the system Catch2 3 package. Install it withsudo pacman -S catch2on Arch/CachyOS, or your distribution's equivalent. - Windows (
test-windows) uses Catch2 from vcpkg.
cmake --preset test-linux
cmake --build --preset test-linux
ctest --preset test-linuxTests live in test/ and use Catch2. Only pure-logic code (no RE::/SKSE:: APIs) can be tested this way. See src/Utils.h and test/ExampleTests.cpp for the pattern.
Prerequisites:
go install github.com/evilmartians/lefthook@latestclang-format(part of LLVM — already required for development)clang-tidy(part of LLVM — already required for development)cmake-format(sudo pacman -S cmake-formaton Arch/CachyOS;pip install cmakelangelsewhere)shellcheck(sudo pacman -S shellcheckon Arch/CachyOS)
lefthook installCMake writes compile_commands.json to the build directory automatically.
Copy or symlink it to the project root so clangd picks it up:
# After configuring:
ln -sf build/release-linux/compile_commands.json compile_commands.jsonThe .clangd file already sets --target=x86_64-pc-windows-msvc so clangd
resolves Windows headers correctly on Linux.
Recommended Neovim plugins: nvim-lspconfig
with clangd, and clangd_extensions.nvim.
The script pins the submodule to a release tag (v*) on the ng branch and stages it. Without an argument it picks the latest tag:
./scripts/update.sh # latest release tag
./scripts/update.sh v10.1.0 # a specific tag
git commit -m "chore(deps): update CommonLibSSE-NG to v10.1.0"The <!-- nexus:start/end --> block at the top of README.md is the source of truth for Requirements, Installation, and Compatibility. docs/nexus-page.md holds the rest of the BBCode page (overview, tagline, credits).
To update the Nexus page:
- Edit Requirements/Installation/Compatibility inside the
<!-- nexus:start/end -->block inREADME.md. - Edit overview, tagline, and credits directly in
docs/nexus-page.md.Do not edit the
<!-- generated:start/end -->block indocs/nexus-page.md— it is overwritten every time the script runs. - Generate the combined BBCode output:
python3 scripts/generate-nexus-page.py
# Or copy straight to the clipboard (wl-copy, xclip, xsel or pbcopy):
python3 scripts/generate-nexus-page.py --copy- Paste the output into the Nexus Mods page editor.
| Workflow | Trigger | What it does |
|---|---|---|
setup.yml |
First push in a repo created from this template | Renames placeholders using the repo name, then self-deletes |
ci.yml |
PRs to main touching src/, test/, cmake/, vcpkg.json, CMakeLists.txt, CMakePresets.json |
clang-format (ubuntu) → test-linux and build-linux (ubuntu) and test-windows + build-windows + clang-tidy (windows, parallel) |
release.yml |
Push of a v* tag |
Builds, packages via scripts/package.sh, publishes a GitHub Release with zip + PDB |
nexus-upload.yml |
Auto-triggered via workflow_run once release.yml completes, or manual workflow_dispatch |
Downloads release zip, generates cliff release notes, uploads to Nexus Mods |
lint.yml |
PRs touching scripts/, README.md, or docs/ |
Runs shellcheck on shell scripts, plus dprint formatting and Vale prose checks on Markdown |
pr-title.yml |
PR opened/edited/reopened/synchronize | Checks PR title follows Conventional Commits (feat, fix, chore, refactor) |
nexus-upload.yml runs once release.yml completes, after approval in the nexus environment. It can also be run manually via workflow_dispatch with a version input. The one-time setup (environment, secrets, and variables) is in docs/RELEASING.md.
Copyright (c) 2026 ExampleMod Contributors. Licensed under the GNU GPL v3.0 or later; see LICENSE. CommonLibSSE-NG is also GPL-3.0-or-later, so mods built from this template are distributed under the same terms.