Skip to content

Papyrus Syntax

Papyrus is a statically typed, object-oriented scripting language. Scripts are written in .psc source files and compiled to .pex bytecode for execution by the engine's virtual machine.


Script Structure

Every .psc file defines exactly one script. The first line declares the script name and optionally its parent:

ScriptName MyScript Extends ObjectReference

A script with no Extends clause implicitly extends the built-in ScriptObject base class. Scripts can also be marked as Const, Native, Hidden, or Default:

ScriptName MyUtility Native Hidden
Flag Meaning
Native Script has engine-bound native functions
Hidden Hidden from the Creation Kit UI
Const Script has no mutable state (FO4 only)
Default Used for default scripts on base types

Types

Primitive Types

Type Description Default Value
Bool True or False False
Int 32-bit signed integer 0
Float 32-bit IEEE float 0.0
String Text string ""

Object Types

Any script name can be used as a type. Object variables hold references to game objects:

Actor PlayerRef
ObjectReference DoorRef
MyScript CustomObj

The special value None represents a null reference and is the default for all object types.

Arrays

Arrays are declared by appending [] to any type:

Int[] Numbers
Actor[] NPCList
String[] Names

Arrays are 0-indexed and must be created before use with new:

Int[] Numbers = new Int[10]

Structs (FO4)

Structs are value types defined inside a script:

Struct MyData
    Int Count = 0
    String Label = "default"
    Bool Active = True
EndStruct

Struct members are accessed with dot notation:

MyData Entry = new MyData
Entry.Count = 5
Entry.Label = "test"

Structs are always referenced by their containing script: MyScript:MyData.


Variables

Variables are declared at script scope or function scope:

; Script-scope variable
Int MyCounter = 0

Function Example()
    ; Function-local variable
    String Message = "hello"
EndFunction

Script-scope variables persist across save/load. Local variables exist only for the duration of the function call.

Const Variables (FO4)

Variables can be marked Const to prevent reassignment after initialization:

Int Property MaxItems = 100 Const

Properties

Properties are the primary way to expose script data to the Creation Kit editor. They come in two forms.

Auto Properties

Auto properties generate a backing variable and getter/setter automatically:

Int Property Health Auto
Actor Property TargetNPC Auto
Bool Property IsEnabled = True Auto

The = True sets the default value visible in the Creation Kit.

Full Properties

Full properties define explicit getter and/or setter functions:

Int _health = 100

Int Property Health
    Function Get()
        Return _health
    EndFunction
    Function Set(Int value)
        If value < 0
            _health = 0
        Else
            _health = value
        EndIf
    EndFunction
EndProperty

Auto Read-Only Properties

Read-only properties can only be set in the Creation Kit, not at runtime:

Message Property WelcomeMessage Auto Const

Conditional Properties

Conditional properties are accessible from the game's condition system:

Int Property QuestStage Auto Conditional

Functions

Instance Functions

Function DoSomething(Int count, String label = "default")
    ; function body
EndFunction

Parameters can have default values. Functions with no return type return None implicitly.

Return Values

Int Function CalculateValue(Int base, Float multiplier)
    Return (base * multiplier) as Int
EndFunction

Global (Static) Functions

Global functions belong to the script type, not to an instance:

Int Function GetMax(Int a, Int b) Global
    If a > b
        Return a
    Else
        Return b
    EndIf
EndFunction

Called as MyScript.GetMax(10, 20).

Native Functions

Native functions are implemented in engine code (C++) and declared without a body:

Function AddItem(Form akItemToAdd, Int aiCount = 1, Bool abSilent = False) Native

Calling Functions

; Instance method
MyActor.AddItem(HealthPotion, 3)

; Global/static function
Int Result = Math.Sqrt(16.0) as Int

; Parent class method
Parent.OnInit()

; Chained property access
Game.GetPlayer().AddItem(HealthPotion)

Events

Events are callback functions triggered by the engine. They use the Event keyword:

Event OnInit()
    ; Runs when the script initializes
EndEvent

Event OnActivate(ObjectReference akActionRef)
    ; Runs when the object is activated
EndEvent

Event OnHit(ObjectReference akAggressor, Form akSource, Projectile akProjectile, \
            Bool abPowerAttack, Bool abSneakAttack, Bool abBashAttack, Bool abHitBlocked)
    ; Runs when hit by an attack
EndEvent

Common events include OnInit, OnActivate, OnHit, OnDeath, OnLoad, OnUnload, OnUpdate, OnItemAdded, OnItemRemoved, OnTriggerEnter, and OnTriggerLeave.

Remote Events (FO4)

FO4 added the ability to register for and receive events from other scripts:

Event OnInit()
    RegisterForRemoteEvent(MyQuest, "OnStageSet")
EndEvent

Event Quest.OnStageSet(Quest akSender, Int auiStageID, Int auiItemID)
    ; Handle quest stage change
EndEvent

States

States allow a script to change its behavior at runtime. Each state can override functions and events:

Auto State Waiting
    Event OnActivate(ObjectReference akActionRef)
        Debug.Notification("Starting...")
        GoToState("Running")
    EndEvent
EndState

State Running
    Event OnActivate(ObjectReference akActionRef)
        Debug.Notification("Already running!")
    EndEvent

    Event OnUpdate()
        ; Do work
    EndEvent
EndState

The Auto keyword marks the initial state. GoToState("") returns to the default (empty) state. Functions defined outside any state block belong to the default state and act as fallbacks.

OnBeginState / OnEndState

State Active
    Event OnBeginState()
        RegisterForUpdate(1.0)
    EndEvent

    Event OnEndState()
        UnregisterForUpdate()
    EndEvent
EndState

Control Flow

If / ElseIf / Else

If Health < 25
    Debug.Notification("Low health!")
ElseIf Health < 50
    Debug.Notification("Moderate health")
Else
    Debug.Notification("Healthy")
EndIf

While Loop

Papyrus has only While loops — there is no For loop:

Int I = 0
While I < MyArray.Length
    Debug.Trace(MyArray[I])
    I += 1
EndWhile

Operators

Arithmetic

Operator Description
+ Addition (Int, Float, String concatenation)
- Subtraction
* Multiplication
/ Division (integer division truncates toward zero)
% Modulo (C-style, truncation toward zero)

Comparison

Operator Description
== Equal
!= Not equal
< Less than
<= Less than or equal
> Greater than
>= Greater than or equal

Logical

Operator Description
&& Logical AND
\|\| Logical OR
! Logical NOT

Assignment

Operator Description
= Assign
+= Add and assign
-= Subtract and assign
*= Multiply and assign
/= Divide and assign
%= Modulo and assign

Casting

Use the as keyword to convert between compatible types:

Float F = 3.7
Int I = F as Int          ; Truncates to 3

Int N = 42
Float G = N as Float      ; 42.0

String S = N as String    ; "42"
Int M = "123" as Int      ; 123
Bool B = N as Bool        ; True (non-zero)

Object types can be cast up or down the inheritance chain:

ObjectReference Ref = SomeActor as ObjectReference   ; upcast (always safe)
Actor A = Ref as Actor                               ; downcast (None if wrong type)

The is keyword (FO4) checks type without casting:

If MyRef is Actor
    (MyRef as Actor).Kill()
EndIf

Arrays

Creation

Int[] Numbers = new Int[10]
Actor[] Targets = new Actor[5]

Access

Numbers[0] = 42
Int First = Numbers[0]
Int Len = Numbers.Length
Int Index = Numbers.Find(42)          ; returns -1 if not found
Int RIndex = Numbers.RFind(42)        ; reverse search

Modification (FO4)

Numbers.Add(99)                       ; append
Numbers.Insert(99, 3)                ; insert at index 3
Numbers.Remove(3)                    ; remove at index 3
Numbers.RemoveLast()
Numbers.Clear()

Struct Arrays (FO4)

MyData[] Entries = new MyData[0]

MyData Entry = new MyData
Entry.Count = 5
Entries.Add(Entry)

Int Found = Entries.FindStruct("Count", 5)

Guards (FO4)

Guards provide mutex-like synchronization for scripts that run on multiple threads:

Guard MyGuard

Function SafeIncrement()
    LockGuard MyGuard
        Counter += 1
    EndLockGuard
EndFunction

Guards prevent concurrent execution of the guarded block across all threads referencing the same guard variable. TryLockGuard is a non-blocking variant that skips the block if the guard is already held.


Inheritance

Scripts inherit all properties, functions, events, and states from their parent:

ScriptName ChildScript Extends ParentScript

; Override a parent function
Function DoSomething()
    Parent.DoSomething()   ; call parent implementation
    ; additional behavior
EndFunction

The Parent keyword accesses the parent class implementation of any function.


Comments

; Single-line comment

;/ 
   Multi-line comment block
   (FO4 only)
/;

Script Fragments

The Creation Kit generates fragment scripts for quests, scenes, dialogue, packages, perks, and terminal entries. These are auto-generated .psc files with naming conventions like:

Type Prefix Example
Quest stage QF_ QF_MQ101_00000001.psc
Scene SF_ SF_MQ101Scene01_00000002.psc
Dialogue TIF_ TIF__00000003.psc
Package PF_ PF_SleepPackage_00000004.psc
Perk PRKF_ PRKF_GunNut01_00000005.psc
Terminal TerminalScript TerminalScript.psc

Fragment scripts extend system base classes (Quest, Scene, TopicInfo, Package, Perk, Terminal) and contain auto-generated entry point functions.