A modern, production-ready template for creating Counter-Strike 2 addons with TypeScript. This template provides a complete development environment with hot reload support, comprehensive type definitions, and best practices for CS2 scripting.
- ✅ Full TypeScript Support - Type-safe CS2 scripting with comprehensive type definitions
- 🔥 Hot Reload - Update scripts without restarting CS2 (requires
-tools
flag) - 🏗️ Multi-Root Workspace - Organized project structure optimized for development
- 📦 Modern Tooling - ESLint, Prettier, and TypeScript configured out of the box
- 📚 Example Code - Working examples demonstrating key CS2 scripting patterns
- 🤖 AI-Friendly - Includes LLM instructions for AI-assisted development
- 🔄 Watch Mode - Automatic compilation on file save
- Node.js v18 or higher
- Counter-Strike 2 installed via Steam
- CS2 Workshop Tools (free DLC on Steam)
git clone https://github.com/YOUR_USERNAME/cs2-typescript-addon-template.git my-cs2-addon
cd my-cs2-addon
Or click "Use this template" on GitHub to create your own repository.
Download and install from nodejs.org (LTS version recommended)
If you don't have Node.js installed:
# Install nvm (Node Version Manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# Reload shell configuration
source ~/.bashrc
# Install Node.js LTS
nvm install 20
nvm use 20
# Verify installation
node --version
npm --version
cd dev
npm install
# Watch mode - automatically compiles on save
npm run dev
# Or one-time build
npm run build
# Format code with Prettier
npm run format
# Lint code with ESLint
npm run lint
This will compile TypeScript files from dev/src/scripts/
to JavaScript in the scripts/
folder.
From File Explorer:
Double-click ts_addon_template.code-workspace
-
Copy this addon folder to your CS2 content directory:
C:\Program Files (x86)\Steam\steamapps\common\Counter-Strike Global Offensive\content\csgo_addons\
-
Open your map in Hammer Editor
-
Add a
point_script
entity:- Entity Name:
my_script
- Script File:
scripts/example.js
- Entity Name:
-
Launch CS2 with tools mode:
cs2.exe -tools +map your_map_name
ts_addon_template/
├── dev/ # Development source code
│ ├── src/
│ │ ├── scripts/ # TypeScript scripts (EDIT HERE)
│ │ │ └── example.ts # Example script with comments
│ │ └── types/
│ │ └── cs_script.d.ts # Official CS2 API type definitions
│ ├── package.json # Node.js dependencies
│ └── tsconfig.json # TypeScript configuration
├── scripts/ # Compiled JavaScript (auto-generated)
├── maps/ # Your .vmap files
├── ts_addon_template.code-workspace # Multi-root workspace config
├── README.md # This file
├── LICENSE # MIT License
└── .gitignore # Git ignore patterns
- Create or edit TypeScript files in
dev/src/scripts/
- Use imports from
"cs_script/point_script"
for CS2 API - Full IntelliSense and type checking available
Example:
import { Instance, CSPlayerController } from "cs_script/point_script";
Instance.OnPlayerActivate((player: CSPlayerController) => {
const pawn = player.GetPlayerPawn();
if (pawn && pawn.IsValid()) {
pawn.GiveNamedItem("weapon_ak47", true);
Instance.Msg(`${player.GetPlayerName()} spawned with AK-47!`);
}
});
# One-time build
npm run build
# Watch mode (recommended)
npm run dev
# Clean compiled files
npm run clean
When running CS2 with -tools
:
- Edit your TypeScript file
- Save (auto-compiles if watch mode is running)
- CS2 automatically reloads the script
- State is preserved via
OnBeforeReload
/OnReload
callbacks
Example State Persistence:
let playerScores: Record<number, number> = {};
Instance.OnBeforeReload(() => {
return { scores: playerScores }; // Save state
});
Instance.OnReload((memory) => {
if (memory) {
playerScores = memory.scores; // Restore state
}
});
Event | When It Fires | Use Case |
---|---|---|
OnActivate |
Script first loads | Initialize variables, server setup |
OnPlayerConnect |
Player connects | Track connections |
OnPlayerActivate |
Player spawns | Give weapons, set team, teleport |
OnPlayerDisconnect |
Player leaves | Clean up player data |
OnRoundStart |
Round begins | Reset game state |
OnRoundEnd |
Round ends | Calculate scores, display results |
OnPlayerKill |
Player dies | Award points, track stats |
OnPlayerChat |
Player sends chat | Chat commands, filters |
OnGunFire |
Weapon fired | Track shots, ammo management |
OnGrenadeThrow |
Grenade thrown | Custom grenade mechanics |
- Store entity references directly (they become invalid on hot reload)
- Use
OnGameEvent
(removed in Sept 2025 update) - Assume entities stay valid across ticks
- Forget to clear
SetNextThink
inOnBeforeReload
- Reference
.ts
files in Hammer (always use.js
)
- Store entity names as strings, refetch with
FindEntityByName
- Check
entity.IsValid()
before every operation - Use specific event handlers instead of generic events
- Clear think loops:
Instance.SetNextThink(-1)
before reload - Reference compiled
.js
files in Hammer
// ❌ WRONG - can crash if entity removed
const door = Instance.FindEntityByName("door");
door.Kill(); // May crash if door was removed
// ✅ CORRECT
const door = Instance.FindEntityByName("door");
if (door && door.IsValid()) {
door.Kill();
}
let entityNames: string[] = []; // Store names, not entities
Instance.OnActivate(() => {
// Refetch entities by name on activation
entityNames.forEach((name) => {
const entity = Instance.FindEntityByName(name);
if (entity && entity.IsValid()) {
// Use entity
}
});
});
Instance.OnBeforeReload(() => {
Instance.SetNextThink(-1); // Clear think loops
return { names: entityNames }; // Save state
});
Instance.OnReload((memory) => {
if (memory) {
entityNames = memory.names; // Restore state
}
});
- Press
Ctrl+Shift+B
(orCmd+Shift+B
on Mac) - Select "Build TypeScript" or "Watch TypeScript"
The workspace will prompt you to install:
- Prettier - Code formatting
- ESLint - TypeScript linting
- Error Lens - Inline error display
let roundWinner: number | null = null;
Instance.OnRoundEnd((winningTeam: number) => {
roundWinner = winningTeam;
const teamName = winningTeam === 2 ? "Terrorists" : "Counter-Terrorists";
Instance.Msg(`Round won by ${teamName}!`);
});
Instance.OnRoundStart(() => {
if (roundWinner === 2) {
// Give T-side bonus
}
});
Instance.OnPlayerChat(
(speaker: CSPlayerController, team: number, text: string) => {
if (text === "!hp") {
const pawn = speaker.GetPlayerPawn();
if (pawn && pawn.IsValid()) {
Instance.Msg(`${speaker.GetPlayerName()} HP: ${pawn.GetHealth()}`);
}
}
}
);
const spawnPoints = ["spawn_1", "spawn_2", "spawn_3"];
Instance.OnPlayerActivate((player: CSPlayerController) => {
const pawn = player.GetPlayerPawn();
if (pawn && pawn.IsValid()) {
// Random spawn
const spawnName =
spawnPoints[Math.floor(Math.random() * spawnPoints.length)];
const spawn = Instance.FindEntityByName(spawnName);
if (spawn && spawn.IsValid()) {
pawn.Teleport(spawn.GetAbsOrigin(), spawn.GetAbsAngles(), null);
}
}
});
cd dev
npm run build
# Check for errors in terminal
- Ensure you're running CS2 with
-tools
flag - Check that
.js
files exist inscripts/
folder - Verify
point_script
entity has correct path:scripts/your_script.js
- Try
script_reload
console command
- Make sure you opened the
.code-workspace
file, not just the folder - Restart TypeScript server:
Ctrl+Shift+P
→ "TypeScript: Restart TS Server" - Check
tsconfig.json
paths are correct
- Implement
OnBeforeReload
to save state - Implement
OnReload
to restore state - Don't store entity references - store entity names
- Rename folder from
ts_addon_template
toyour_addon_name
- Update
package.json
name field - Update workspace file name and folder paths
- Update README.md
- Create new
.ts
file indev/src/scripts/
- Import CS2 API:
import { Instance } from "cs_script/point_script";
- Compile with
npm run build
- Add
point_script
entity in Hammer with new script path
Contributions welcome! Please:
- Fork this repository
- Create a feature branch
- Make your changes
- Submit a pull request
MIT License - See LICENSE file for details.
Free to use, modify, and distribute for any purpose.
- Type Definitions - Full CS2 API with comments
- Example Script - Comprehensive example
- CS2 Modding Discord - Share your projects (link TBD)
- Steam Workshop - Publish your addons (link TBD)
- ✅ Clone/download this template
- ✅ Run
npm install
indev/
folder - ✅ Start watch mode:
npm run dev
- ✅ Edit
dev/src/scripts/example.ts
- ✅ Open workspace file in VS Code/Cursor
- ✅ Create your map in Hammer
- ✅ Add
point_script
entity - ✅ Launch CS2 with
-tools +map your_map
- ✅ Start coding!
Made with ❤️ for the CS2 modding community