Skip to main content
Follow these best practices to write efficient, maintainable, and reliable Skript code.

Performance Optimization

Use Local Variables

Local variables (prefixed with _) are faster than global variables and automatically clean up after script execution.
command /test:
	trigger:
		set {_player} to player
		set {_location} to location of {_player}
		teleport {_player} to {_location}
Local variables ({_var}) are automatically deleted when the trigger ends. Use them for temporary data.

Minimize Database Queries

Cache frequently accessed variables instead of repeatedly fetching them:
on damage:
	set {_level} to {level::%uuid of victim%}
	if {_level} is less than 10:
		cancel event
	send "Level: %{_level}%" to victim

Optimize Loops

Limit loop iterations and break early when possible:
loop all players:
	if loop-player has permission "admin":
		send "Alert: Server restarting" to loop-player
		if loop-player is op:
			send "You can delay with /delay" to loop-player
			stop loop  # Found the op, no need to continue
Use stop loop to exit a loop early once you’ve found what you need.

Batch Operations

Group related operations together:
command /setup:
	trigger:
		set {_loc} to player's location
		set block at {_loc} to chest
		set {_inv} to inventory of block at {_loc}
		add diamond, emerald, and gold ingot to {_inv}

Code Organization

Use Descriptive Variable Names

set {homes::%uuid of player%::%arg-1%} to player's location
set {lastLogin::%uuid of player%} to now
set {kills::%uuid of attacker%::total} to {kills::%uuid of attacker%::total} + 1
Use meaningful names that explain the variable’s purpose at a glance.

Use Functions for Reusable Code

From functions.sk:
function say_message(message: text):
	broadcast {_message}

on first join:
	wait 1 second
	say_message("Welcome, %player%!")
Benefits:
  • Reduce code duplication
  • Easier to maintain and update
  • Can return values for complex calculations

Structure Complex Scripts

# ===== Configuration =====
options:
	prefix: <blue>[MyScript]<reset>
	cooldown: 30 seconds

# ===== Aliases =====
aliases:
	rare items = diamond, emerald, netherite ingot

# ===== Functions =====
function sendPrefixedMessage(p: player, msg: text):
	send "{@prefix} %{_msg}%" to {_p}

# ===== Commands =====
command /reward:
	permission: server.reward
	trigger:
		give player a random rare item
		sendPrefixedMessage(player, "Reward given!")

# ===== Events =====
on join:
	sendPrefixedMessage(player, "Welcome!")
Organization sections:
  1. Options/configuration at the top
  2. Aliases and constants
  3. Function definitions
  4. Commands
  5. Event handlers

Comment Your Code

# Home system - allows players to set and teleport to saved locations
# Stores homes in {homes::<uuid>::<name>} format

command /home <text> [<text>]:
	description: Set, delete or travel to your home.
	trigger:
		# Handle "set" subcommand
		if arg-1 is "set":
			if arg-2 is set:
				# Save location with player's UUID for name-change safety
				set {homes::%uuid of player%::%arg-2%} to player's location
				send "Set your home <green>%arg-2%<reset>" to player
Comment:
  • Complex logic
  • Non-obvious behavior
  • System purposes and data structures
  • Important limitations or warnings

Data Management

Always Use Player UUIDs

From config.sk:
use player UUIDs in variable names: true
Store player data by UUID, not username:
set {homes::%uuid of player%::%arg-2%} to player's location
set {balance::%uuid of player%} to 1000
add 1 to {kills::%uuid of attacker%}
Player names can change. UUID-based storage prevents data loss when players change names.

Use Structured Variable Names

Organize related data hierarchically:
# Player stats structure
{stats::%uuid%::kills}
{stats::%uuid%::deaths}
{stats::%uuid%::playtime}

# Home locations structure  
{homes::%uuid%::<home-name>}
{homes::%uuid%::home1}
{homes::%uuid%::home2}

# Guild data structure
{guild::%guild-name%::leader}
{guild::%guild-name%::members::*}
{guild::%guild-name%::balance}
Double colons (::) separate hierarchy levels.

Clean Up Unused Variables

Delete variables that are no longer needed:
on quit:
	# Delete temporary session data
	delete {temp::%uuid of player%::*}
	
command /home remove <text>:
	trigger:
		if {homes::%uuid of player%::%arg-1%} is set:
			delete {homes::%uuid of player%::%arg-1%}
			send "Home deleted" to player
Local variables ({_var}) are automatically cleaned up. Only manually delete global variables.

Error Handling

Validate Input

Check arguments before using them:
command /teleport <player>:
	trigger:
		if arg-1 is online:
			teleport player to arg-1
			send "Teleported to %arg-1%"
		else:
			send "<red>Player %arg-1% is not online"

Provide User Feedback

Always inform users of success or failure:
command /home set <text>:
	trigger:
		if length of arg-1 > 16:
			send "<red>Home name must be 16 characters or less"
			stop
		set {homes::%uuid of player%::%arg-1%} to player's location
		send "<green>Home '%arg-1%' set successfully"

Handle Edge Cases

Consider unusual situations:
command /pay <offline player> <number>:
	trigger:
		# Check: not paying yourself
		if arg-1 is player:
			send "<red>You can't pay yourself!"
			stop
		
		# Check: positive amount
		if arg-2 <= 0:
			send "<red>Amount must be positive"
			stop
		
		# Check: sufficient balance
		if {balance::%uuid of player%} < arg-2:
			send "<red>Insufficient funds"
			stop
		
		# Process payment
		remove arg-2 from {balance::%uuid of player%}
		add arg-2 to {balance::%uuid of arg-1%}
		send "<green>Paid %arg-2% to %arg-1%"

Security

Use Permission Checks

Always require appropriate permissions:
command /item <items>:
	permission: server.item
	permission message: <red>You don't have permission to use this command
	trigger:
		# Additional check for dangerous items
		if player doesn't have permission "server.item.all":
			loop argument:
				if loop-item is tnt or bedrock:
					send "<red>You can't spawn %loop-item%"
				else:
					give loop-item to player
		else:
			give argument to player

Prevent Exploits

command /pay <offline player> <number>:
	permission: server.pay
	trigger:
		# Prevent: paying yourself to duplicate money
		if arg-1 is player:
			send "<red>You can't pay yourself"
			stop
		
		# Prevent: negative payments to steal money
		if arg-2 <= 0:
			send "<red>Amount must be positive"
			stop
		
		# Prevent: paying more than you have
		if {balance::%uuid of player%} < arg-2:
			send "<red>Insufficient funds"
			stop

Sanitize Player Input

Be cautious with user-provided text:
command /sethome <text>:
	trigger:
		# Validate: no special characters that could break variables
		if arg-1 contains "::" or "%%":
			send "<red>Invalid home name"
			stop
		
		# Validate: reasonable length
		if length of arg-1 > 16:
			send "<red>Name too long (max 16 characters)"
			stop
		
		set {homes::%uuid of player%::%arg-1%} to player's location

Configuration Best Practices

Monitor Verbosity During Development

From config.sk:
verbosity: very high
# Shows execution times for triggers - useful for finding slow scripts
Use very high during development to identify performance issues, then set back to normal in production.

Configure Error Handling

From config.sk:
runtime errors:
	frame duration: 1 second
	total errors per frame: 8
	errors from one line per frame: 2
Adjust error thresholds based on your server size. Large servers may need higher limits to avoid hiding important errors.

Use Appropriate Database Settings

From config.sk:
variable changes until save: 1000
Balance between data safety and performance:
  • Lower values (500): Safer, more frequent saves, slightly more lag
  • Higher values (2000): Faster, less frequent saves, more data at risk on crash

Regular Backups

Configure automatic backups:
default:
	type: CSV
	file: ./plugins/Skript/variables.csv
	backup interval: 2 hours
	backups to keep: 10  # Keep last 10 backups (20 hours)
Backups protect against corruption and accidents. Always keep multiple backups.

Script Organization

Split Large Scripts

Divide functionality across multiple files:
plugins/Skript/scripts/
├── commands/
│   ├── admin-commands.sk
│   ├── player-commands.sk
│   └── moderator-commands.sk
├── events/
│   ├── join-quit.sk
│   ├── combat.sk
│   └── protection.sk
├── systems/
│   ├── economy.sk
│   ├── homes.sk
│   └── warps.sk
└── functions/
    ├── utilities.sk
    └── formatting.sk

Use Consistent Naming

Scripts

feature-name.skExamples: home-system.sk, anti-grief.sk

Variables

{category::identifier::property}Examples: {homes::uuid::name}, {stats::uuid::kills}

Functions

camelCase or snake_caseExamples: sendMessage(), calculate_damage()

Permissions

server.feature.actionExamples: server.home.set, server.admin.reload

Testing

Test Edge Cases

Test your scripts with unusual inputs:
  • No arguments
  • Too many arguments
  • Invalid argument types
  • Special characters
  • Extremely long input
  • Cancelled events
  • Events triggered by non-players
  • Multiple simultaneous triggers
  • Events during server shutdown
  • Undefined variables
  • Wrong data types
  • List variables vs. single values
  • Very large datasets

Use Test Servers

Never test untested scripts on production servers. Use a development or staging server.
Test environment checklist:
  • Separate server instance
  • Copy of production data (for realistic testing)
  • Same plugins and versions
  • Lower player count for faster iterations

Monitor Performance

From config.sk:
enable timings: true  # Paper only, pre-1.19.4
Use profiling tools: Identify slow scripts and optimize them.

Common Pitfalls

# Bad - continues after error
if player doesn't have permission "admin":
	send "<red>No permission"
# Code continues executing!

# Good - stops after error
if player doesn't have permission "admin":
	send "<red>No permission"
	stop  # Prevents further execution
# Bad - crashes if variable doesn't exist
add 1 to {score::%uuid of player%}

# Good - initializes if needed
if {score::%uuid of player%} is not set:
	set {score::%uuid of player%} to 0
add 1 to {score::%uuid of player%}

# Better - use default value
add 1 to {score::%uuid of player%}
set {score::%uuid of player%} to {score::%uuid of player%} ? 0
# Bad - loops all players every time
on damage:
	loop all players:
		if loop-player is victim:
			send "You took damage!" to loop-player

# Good - direct reference
on damage:
	if victim is a player:
		send "You took damage!" to victim
# Bad - hardcoded
command /reward:
	trigger:
		give player 5 diamonds
		wait 3600 seconds

# Good - configurable
options:
	reward_amount: 5
	reward_cooldown: 1 hour

command /reward:
	trigger:
		give player {@reward_amount} diamonds
		wait {@reward_cooldown}

Quick Reference

Performance

  • Use local variables
  • Cache database queries
  • Optimize loops
  • Batch operations

Organization

  • Descriptive names
  • Use functions
  • Structure scripts
  • Comment code

Data Management

  • Always use UUIDs
  • Structure variables
  • Clean up unused data
  • Regular backups

Security

  • Check permissions
  • Prevent exploits
  • Sanitize input
  • Validate everything

Configuration

Optimize Skript’s configuration

Commands

Command structure and best practices

Example Scripts

Official example scripts on GitHub

Documentation

Full Skript documentation

Build docs developers (and LLMs) love