A TypeScript/JavaScript wrapper for the Steamworks SDK using Koffi FFI, designed for Node.js and Electron applications with Steamworks SDK v1.63 integration.
โ No C++ Compilation Required: Uses Koffi FFI for seamless installation without Visual Studio Build Tools!
โก Steamworks SDK v1.63: Latest SDK with Linux ARM64, Android ARM64, and Lenovo Legion Go controller support!
๐ฆ 16 Complete API Managers: 100% coverage of achievements, stats, leaderboards, friends, workshop, networking, and more - see full documentation!
๐งช EXPERIMENTAL: Native Overlay for Electron - Metal (macOS), OpenGL (Windows), OpenGL 3.3 (Linux/SteamOS). See Documentation
SteamLogger utility for consistent logging across all modulesinit() and you're ready!# Install the package
npm install steamworks-ffi-node
Download Steamworks SDK (required separately due to licensing):
Visit Steamworks Partner site
redistributable_bin folder to your projectsteamworks_sdk/ in project rootCan now use custom locations! See setSdkPath() method below
Set your Steam App ID - No file creation needed!
The library automatically sets the SteamAppId environment variable when you call init().
For Development (Default SDK Location):
```typescript
const steam = SteamworksSDK.getInstance();
// SDK at: your-project/steamworks_sdk/
if (steam.restartAppIfNecessary(480)) {
process.exit(0);
}
steam.init({ appId: 480 });
```
For Custom SDK Location:
```typescript
const steam = SteamworksSDK.getInstance();
// IMPORTANT: Set SDK path BEFORE restartAppIfNecessary() or init()
steam.setSdkPath('vendor/steamworks_sdk'); // SDK in vendor folder
if (steam.restartAppIfNecessary(480)) {
process.exit(0);
}
steam.init({ appId: 480 });
```
Custom SDK Path Examples:
```typescript
// SDK in vendor folder: your-project/vendor/steamworks_sdk/
steam.setSdkPath('vendor/steamworks_sdk');
// SDK in nested structure: your-project/source/main/sdk/steamworks/
steam.setSdkPath('source/main/sdk/steamworks');
// SDK in monorepo: monorepo/packages/game/steamworks_sdk/
steam.setSdkPath('packages/game/steamworks_sdk');
```
Debug Mode (Optional):
```typescript
const steam = SteamworksSDK.getInstance();
// Enable debug logs to see initialization details
steam.setDebug(true);
// Set custom SDK path if needed (debug logs will show path resolution)
steam.setSdkPath('vendor/steamworks_sdk');
// Check restart requirement (debug logs will show library loading)
if (steam.restartAppIfNecessary(480)) {
process.exit(0);
}
// Initialize (debug logs will show initialization steps)
steam.init({ appId: 480 });
// Disable debug logs after initialization
steam.setDebug(false);
```
Note: Debug mode is useful for troubleshooting SDK path issues and initialization problems.
Errors and warnings always display regardless of debug mode setting.
Optional: Create steam_appid.txt manually (if needed for other tools):
bash
echo "480" > steam_appid.txt # Use 480 for testing, or your Steam App ID
For Production:
```typescript
// Check if launched through Steam, restart if necessary
if (steam.restartAppIfNecessary(480)) {
process.exit(0); // Steam will relaunch your app
}
steam.init({ appId: 480 });
```
import SteamworksSDK, {
LeaderboardSortMethod,
LeaderboardDisplayType,
LeaderboardUploadScoreMethod,
LeaderboardDataRequest,
EFriendFlags,
EUGCQuery,
EUGCMatchingUGCType,
EItemState,
} from "steamworks-ffi-node";
// Helper to auto-start callback polling
function startCallbackPolling(steam: SteamworksSDK, interval: number = 1000) {
return setInterval(() => {
steam.runCallbacks();
}, interval);
}
// Initialize Steam connection
const steam = SteamworksSDK.getInstance();
const initialized = steam.init({ appId: 480 }); // Your Steam App ID
if (initialized) {
// Start callback polling automatically (required for async operations)
const callbackInterval = startCallbackPolling(steam, 1000);
// Get current Steam language for localization
const language = steam.getCurrentGameLanguage();
console.log("Steam language:", language); // e.g., 'english', 'french', 'german'
// Get achievements from Steam servers
const achievements = await steam.achievements.getAllAchievements();
console.log("Steam achievements:", achievements);
// Unlock achievement (permanent in Steam!)
await steam.achievements.unlockAchievement("ACH_WIN_ONE_GAME");
// Check unlock status from Steam
const isUnlocked = await steam.achievements.isAchievementUnlocked(
"ACH_WIN_ONE_GAME"
);
console.log("Achievement unlocked:", isUnlocked);
// Track user statistics
const kills = (await steam.stats.getStatInt("total_kills")) || 0;
await steam.stats.setStatInt("total_kills", kills + 1);
// Get global statistics
await steam.stats.requestGlobalStats(7);
await new Promise((resolve) => setTimeout(resolve, 2000));
steam.runCallbacks();
const globalKills = await steam.stats.getGlobalStatInt("global.total_kills");
console.log("Total kills worldwide:", globalKills);
// Work with leaderboards
const leaderboard = await steam.leaderboards.findOrCreateLeaderboard(
"HighScores",
LeaderboardSortMethod.Descending,
LeaderboardDisplayType.Numeric
);
if (leaderboard) {
// Upload score
await steam.leaderboards.uploadLeaderboardScore(
leaderboard.handle,
1000,
LeaderboardUploadScoreMethod.KeepBest
);
// Download top 10 scores
const topScores = await steam.leaderboards.downloadLeaderboardEntries(
leaderboard.handle,
LeaderboardDataRequest.Global,
1,
10
);
console.log("Top 10 scores:", topScores);
}
// Access friends and social features
const personaName = steam.friends.getPersonaName();
const friendCount = steam.friends.getFriendCount(EFriendFlags.All);
console.log(`${personaName} has ${friendCount} friends`);
// Get all friends with details
const allFriends = steam.friends.getAllFriends(EFriendFlags.All);
allFriends.slice(0, 5).forEach((friend) => {
const name = steam.friends.getFriendPersonaName(friend.steamId);
const state = steam.friends.getFriendPersonaState(friend.steamId);
const level = steam.friends.getFriendSteamLevel(friend.steamId);
console.log(`${name}: Level ${level}, Status: ${state}`);
// Get avatar handles
const smallAvatar = steam.friends.getSmallFriendAvatar(friend.steamId);
const mediumAvatar = steam.friends.getMediumFriendAvatar(friend.steamId);
if (smallAvatar > 0) {
console.log(
` Avatar handles: small=${smallAvatar}, medium=${mediumAvatar}`
);
}
// Check if playing a game
const gameInfo = steam.friends.getFriendGamePlayed(friend.steamId);
if (gameInfo) {
console.log(` Playing: App ${gameInfo.gameId}`);
}
});
// Check friend groups (tags)
const groupCount = steam.friends.getFriendsGroupCount();
if (groupCount > 0) {
const groupId = steam.friends.getFriendsGroupIDByIndex(0);
const groupName = steam.friends.getFriendsGroupName(groupId);
const members = steam.friends.getFriendsGroupMembersList(groupId);
console.log(`Group "${groupName}" has ${members.length} members`);
}
// Check recently played with
const coplayCount = steam.friends.getCoplayFriendCount();
if (coplayCount > 0) {
const recentPlayer = steam.friends.getCoplayFriend(0);
const playerName = steam.friends.getFriendPersonaName(recentPlayer);
const coplayTime = steam.friends.getFriendCoplayTime(recentPlayer);
console.log(`Recently played with ${playerName}`);
}
// Set rich presence for custom status
steam.richPresence.setRichPresence("status", "In Main Menu");
steam.richPresence.setRichPresence("connect", "+connect server:27015");
// Open Steam overlay
steam.overlay.activateGameOverlay("Friends"); // Open friends list
steam.overlay.activateGameOverlayToWebPage("https://example.com/wiki"); // Open wiki
// Steam Cloud storage operations
const saveData = { level: 5, score: 1000, inventory: ["sword", "shield"] };
const buffer = Buffer.from(JSON.stringify(saveData));
// Write save file to Steam Cloud
const written = steam.cloud.fileWrite("savegame.json", buffer);
if (written) {
console.log("โ
Save uploaded to Steam Cloud");
}
// Check cloud quota
const quota = steam.cloud.getQuota();
console.log(
`Cloud storage: ${quota.usedBytes}/${
quota.totalBytes
} bytes (${quota.percentUsed.toFixed(2)}%)`
);
// Read save file from Steam Cloud
if (steam.cloud.fileExists("savegame.json")) {
const result = steam.cloud.fileRead("savegame.json");
if (result.success && result.data) {
const loadedSave = JSON.parse(result.data.toString());
console.log(
`Loaded save: Level ${loadedSave.level}, Score ${loadedSave.score}`
);
}
}
// List all cloud files
const cloudFiles = steam.cloud.getAllFiles();
console.log(`Steam Cloud contains ${cloudFiles.length} files:`);
cloudFiles.forEach((file) => {
const kb = (file.size / 1024).toFixed(2);
const status = file.persisted ? "โ๏ธ" : "โณ";
console.log(`${status} ${file.name} - ${kb} KB`);
});
// Steam Workshop operations
// Subscribe to a Workshop item
const subscribeResult = await steam.workshop.subscribeItem(123456789n);
if (subscribeResult.success) {
console.log("โ
Subscribed to Workshop item");
}
// Get all subscribed items
const subscribedItems = steam.workshop.getSubscribedItems();
console.log(`Subscribed to ${subscribedItems.length} Workshop items`);
// Query Workshop items with text search
const query = steam.workshop.createQueryAllUGCRequest(
EUGCQuery.RankedByTextSearch,
EUGCMatchingUGCType.Items,
480, // Creator App ID
480, // Consumer App ID
1 // Page 1
);
if (query) {
// Set search text to filter results
steam.workshop.setSearchText(query, "map");
const queryResult = await steam.workshop.sendQueryUGCRequest(query);
if (queryResult) {
console.log(
`Found ${queryResult.numResults} Workshop items matching "map"`
);
// Get details for each item
for (let i = 0; i < queryResult.numResults; i++) {
const details = steam.workshop.getQueryUGCResult(query, i);
if (details) {
console.log(`๐ฆ ${details.title} by ${details.steamIDOwner}`);
console.log(
` Score: ${details.score}, Downloads: ${details.numUniqueSubscriptions}`
);
}
}
}
steam.workshop.releaseQueryUGCRequest(query);
}
// Check download progress for subscribed items
subscribedItems.forEach((itemId) => {
const state = steam.workshop.getItemState(itemId);
const stateFlags = [];
if (state & EItemState.Subscribed) stateFlags.push("Subscribed");
if (state & EItemState.NeedsUpdate) stateFlags.push("Needs Update");
if (state & EItemState.Installed) stateFlags.push("Installed");
if (state & EItemState.Downloading) stateFlags.push("Downloading");
console.log(`Item ${itemId}: ${stateFlags.join(", ")}`);
if (state & EItemState.Downloading) {
// If downloading
const progress = steam.workshop.getItemDownloadInfo(itemId);
if (progress) {
const percent = ((progress.downloaded / progress.total) * 100).toFixed(
1
);
console.log(
` Download: ${percent}% (${progress.downloaded}/${progress.total} bytes)`
);
}
}
if (state & EItemState.Installed) {
// If installed
const info = steam.workshop.getItemInstallInfo(itemId);
if (info.success) {
console.log(` Installed at: ${info.folder}`);
}
}
});
}
// Cleanup
clearInterval(callbackInterval);
steam.shutdown();
// Option 1: ESM Named import
import { SteamworksSDK } from "steamworks-ffi-node";
// Option 2: CommonJs named import (recommended - no .default needed)
const { SteamworksSDK } = require("steamworks-ffi-node");
// Option 3: CommonJs default named import (also works)
const SteamworksSDK = require("steamworks-ffi-node").default;
// Helper to auto-start callback polling
function startCallbackPolling(steam, interval = 1000) {
return setInterval(() => {
steam.runCallbacks();
}, interval);
}
async function example() {
const steam = SteamworksSDK.getInstance();
if (steam.init({ appId: 480 })) {
// Start callback polling automatically
const callbackInterval = startCallbackPolling(steam, 1000);
const achievements = await steam.achievements.getAllAchievements();
console.log(`Found ${achievements.length} achievements`);
// Unlock first locked achievement
const locked = achievements.find((a) => !a.unlocked);
if (locked) {
await steam.achievements.unlockAchievement(locked.apiName);
}
// Cleanup
clearInterval(callbackInterval);
}
steam.shutdown();
}
example();
For immediate testing, use Spacewar (App ID 480):
steam://install/480 or search "Spacewar" in SteamComplete documentation for all APIs is available in the docs folder:
โก๏ธ View Complete Documentation
This library connects directly to the Steam client and Steamworks SDK:
// Steamworks API
const steam = SteamworksSDK.getInstance();
steam.init({ appId: 480 }); // Connects to actual Steam
// Live achievements from Steam servers
const achievements = await steam.achievements.getAllAchievements();
console.log(achievements); // Achievement data from your Steam app
// Permanent achievement unlock in Steam
await steam.achievements.unlockAchievement("YOUR_ACHIEVEMENT");
// ^ This shows up in Steam overlay and is saved permanently
What happens when you unlock an achievement:
For Electron applications, use it in your main process:
// main.ts
import { app } from "electron";
import SteamworksSDK from "steamworks-ffi-node";
app.whenReady().then(() => {
const steam = SteamworksSDK.getInstance();
if (steam.init({ appId: YOUR_STEAM_APP_ID })) {
console.log("Steam initialized in Electron!");
// Handle achievement unlocks from renderer process
// via IPC if needed
}
});
app.on("before-quit", () => {
const steam = SteamworksSDK.getInstance();
steam.shutdown();
});
For Electron applications, the library will automatically detect the Steamworks SDK files in your project directory. No special packaging configuration is needed - just ensure your steamworks_sdk/redistributable_bin folder is present in your project.
The library searches for the SDK in standard locations within your Electron app bundle.
steam.init({ appId: YOUR_APP_ID })steam_appid.txt if needed for other Steam toolsSteamworks SDK Version: v1.63 (Latest)
Note: You must download and install the SDK redistributables separately as described in the Setup section above.
steam.init({ appId: 480 }) (library sets environment variable automatically)npm install steamworks-ffi-nodesteam.init({ appId }) firstshutdown() in before-quit eventHere are some projects and applications built with steamworks-ffi-node:
Snake-meets-bullet-hell roguelike where your tail is your ammo. Eat bullets to grow, spend length to shoot, and survive escalating waves, enemies, and bosses.
DekaDuck is a 2D action platformer, featuring fully hand-drawn art and animation. Fight enemies using their own abilities and quirks to solve puzzles and defeat bosses, in a sci-fi world full of conspiracies!
Falling Face Fragments is a falling block puzzle game where you must assemble faces correctly and clear them on a button press. The more faces you have on screen when you choose to clear them, the higher you will score! Three fun modes of play; arcade mode, mission mode, and local 2-player!
Slay endless monsters for legendary loot. Master deep crafting and prestige systems to grow infinitely. Collect an army of companions and conquer the unknown. Do you have what it takes to become a hero?
Crush endless waves of creatures and become absurdly overpowered! Grab loot, level up, unlock characters, and upgrade weapons to craft insane builds that obliterate everything on screen.
Using steamworks-ffi-node in your project? We'd love to showcase it here!
To add your project:
Your project helps demonstrate the library's capabilities and inspires other developers!
You can also support by wishlisting, subscribing, and purchasing the app AFK Companion on Steam:
AFK Companion is built using this library! Your support helps fund further development and improvements.
MIT License - see LICENSE file for details.
Note: This package requires the Steamworks SDK redistributables to be installed separately by users. Users are responsible for complying with Valve's Steamworks SDK Access Agreement when downloading and using the SDK.