Merged reference for this repo's VirtualDJ notes, examples, and preferred implementation patterns.
Last reviewed against live VirtualDJ documentation and forum sources on 2026-04-22. Local repo links and example inventory audited on 2026-05-09. Remote skin local deployment notes updated on 2026-05-24. Bundled Remote skin patterns updated on 2026-05-27. Stem FX slot forum sources updated on 2026-05-24. Variable-scope and device-definition forum sources updated on 2026-05-24. Pitch beats-parameter forum source updated on 2026-05-24. Pad-page generic/sampler-scope notes updated on 2026-05-30. FX slot restart-persistence note added on 2026-06-01. FX state-lifetime model added on 2026-06-01. Conditional skin breaklines added on 2026-06-06. Skin condition and dynamic-color rules updated on 2026-06-08.
For verb-by-verb API details, use VDJScript Verbs.
This document is the source-backed overview and policy layer above the older split reference pages, which are still being folded into a more reliable local reference set. It focuses on:
- Which methods to prefer
- Why those methods are preferable
- Where the working examples live in this repo
- Which notes come from official docs, official staff posts, or repo inference
Source labels used below:
Official: current VirtualDJ manual or VDJPediaOfficial forum: post by VirtualDJ staff, Development Manager, CTO, or Support staff. Treat Adion/CTO posts as high-authority implementation notes for scripting, audio-engine, and feature-behavior questions; VirtualDJ forum badges identify Adion as CTO, and Atomix's press archive confirms Atomix Productions acquired AdionSoft in 2011.Community: forum moderators, non-staff forum users, Reddit posts, or other community examplesPublished skin: command or pattern observed in a working public skinBuilt-in skin: command or pattern observed in skin XML shipped inside the VirtualDJ app bundlePublished pad page: command or pattern observed in a working public pad pageBuilt-in pad page: command or pattern observed in pad-page XML shipped inside the VirtualDJ app bundleLocal test: behavior reproduced in VirtualDJ locallyInference: conclusion drawn from official docs plus this repo's build setup
-
Main deck filter or ColorFX: Prefer
filter_selectcolorfxto choose the ColorFX andfilterto drive it. Why: the current verbs list describesfilteras the control that applies the selected ColorFX, with nothing applied at50%. Source:Official -
Extra ColorFX-style controls on custom knobs: Prefer
effect_colorfx <1-4>witheffect_colorsliderorcolorfx_slider. Why: the current verbs list exposes four custom ColorFX slots, and CTO guidance explains they exist for extra dedicated controls rather than replacing the main filter knob. Source:Official,Official forum -
FX state lifetime: Treat FX1-FX6 as persistent deck-rack state, and treat Pad FX plus named stem FX slots as volatile performance state. Pads that should respect the performer's saved rack should trigger/control existing FX1-FX6 assignments with
effect_active <slot>,effect_slider <slot> ..., andeffect_button <slot> ...rather than silently selecting a different effect into the slot. Pads that need to assign their own effect state should usually usepadfxor named stem FX slots, or explicitly document that the pad page owns/reprograms the deck rack. Why: official docs and UI expose FX1-FX6 as the deck rack, and local observation found FX1-FX6 persist loaded effects across restarts while FX7+ and named stem slots reset after restart. Source:Official,Local test,Inference -
Rack-owning deck FX presets: When a pad page intentionally owns a deck FX slot, prefer
effect_select <slot>,effect_active <slot>, andeffect_slider <slot> .... Why: slot-based mappings avoid ambiguity that comes from global effect-name toggles and make the overwrite explicit. Name-based forms such aseffect_active 'Echo'are valid VDJScript, but they are best treated as convenience shortcuts unless the mapping deliberately wants "whatever Echo instance exists." Source:Official,Inference -
Numbered deck FX slots: Treat slots 1-6 as the supported numbered range. Why: the current manual exposes an
FX x6view, hardware manuals describe six FX slots, andeffect_bank_save/effect_bank_loadexplicitly cover deck FX slots 1 to 6. User-provided local observation also found that FX1-FX6 keep their loaded effect across a VirtualDJ restart, while FX7 and higher keep their loaded effect only during the current session/across track loads and reset after restart. Source:Official,Local test -
Generic FX panels: Prefer
effect_has_slider,get_effect_slider_label,get_effect_slider_text,get_effect_slider_default,effect_has_button, andget_effect_button_shortnameso the UI follows the selected plugin. Why: built-in skins use these helpers to disable missing controls and show plugin-provided labels/defaults for deck FX, video FX, and transitions. Source:Official,Built-in skin,Inference -
Stem-specific FX instances: Use named stem FX slots such as
vocals,bass,instru,rhythm,melody,hihat, orkickwith the normaleffect_*verbs when an effect instance should belong to a stem-specific slot. Usepadfx ... 'stemfx:<stem>'for quick pad effects, and useeffect_stems <stem>when the normal FX rack should follow a shared stem-routing mode. Why: CTO forum guidance says stem targets can be treated as separate slots foreffect_*actions, and the officialpadfxdocs exposestemfx:stemnamefor pad effects. Source:Official,Official forum,Community,Local test -
Pad FX cleanup and ownership: Use
padfxfor quick temporary effect triggers, not for private effect-chain ownership. Keepeffect_disable_all 'padfx'as a separate cleanup/reset action rather than chaining it immediately before newpadfxcalls in the same pad. Why: user-provided local testing showed that inlineeffect_disable_all 'padfx'can prevent the followingpadfxchain from activating, and that another pad using the same effect/stem target can alter that shared pad-FX instance's parameters. Source:Official,Local test,Inference -
Vocal/instrumental stem isolation: Prefer
stem_pad 'acapella' onandstem_pad 'instrumental' onfor button-style isolate pads. Why: official docs describestem_padas the stem-pad helper and local testing confirms theonargument forces these aggregate stem-pad states on in isolation.only_stem <stem> on/offalso works, but it follows state-dependent button semantics and can require a second press from some starting states. Source:Official,Local test -
Generic pad-page buttons: Treat pad pages as generic VDJScript surfaces. In skins and controller displays, use
pad <n>to trigger the current page's pad,padshift <n>for the shifted action,pad_has_action <n>when the skin needs to know whether the current page defines a push action, andpad_color/pad_button_colorfor current-page color feedback. Why: the official Pads Editor says each pad is page-specific and can be mapped to any VDJScript action; bundled desktop and Remote skins dispatch pads generically withpad <n>and do not assume the page is a sampler. Source:Official,Built-in skin -
Sampler page-aware pads: Prefer
sampler_pad,sampler_color, andsampler_pad_page. Why: this applies to sampler pages only.sampler_colorexplicitly follows the visible sampler page, and the pads manual documents sample-page cycling when a bank has more than eight samples. Source:Official -
Sampler drag-and-drop assignment inside pads: Prefer pad-page
drop="sampler_assign <absolute-slot>"when you want a dragged file to populate a sampler slot from the Pads area. Why:sampler_assignis the official assignment verb, and the sampler manual documents dragging files onto unlocked sampler pads. The current skin button docs only document click-oriented handlers, while skin drag targets are handled with<dropzone>rather than a generic button drag state. Source:Official,Inference -
Absolute sampler slots: Prefer
sampler_play,sampler_stop,get_sample_name, andget_sample_colorwhen you do not want the action to follow the currently visible sampler page. Why: these verbs target fixed slots rather than the visible pad page. Source:Official -
Dynamic text color in skins: Prefer one
<text>element whosecolorattribute is a backticked VDJScript expression over state-specific text color attributes when the color itself is dynamic. Why: the color docs distinguish between action-returning color values and literal colors, and Development Manager guidance recommends a single dynamic text color action. Source:Official,Official forum -
Deck-scoped dynamic text color in reusable skin classes: When a dynamic color depends on deck state inside a reusable class, explicitly scope the predicate with the class's deck placeholder, for example
color="`deck [DECK] masterdeck ? color 'orange' : color 'white'`". Why: the dynamic-color pattern is officially supported, but deck-sensitive predicates can otherwise follow the ambient/current deck context. The explicitdeck [DECK] masterdeckform was confirmed working in GraveRaver'sSYNC_INFO_EXTENDEDclass. Source:Official,Official forum,Local test,Inference -
Defined skin colors inside dynamic color scripts: Use
<define color="name" value="..."/>names directly in XML color fields such ascolor="name", but do not reference those names inside backticked VDJScript color expressions. Why: official color docs define custom colors for skin color fields, and staff guidance says defined colors are not available inside scripts such ascolor="`play ? color 'green' : color 'mydefine'`". Source:Official,Official forum -
Dynamic button borders: Do not rely on dynamic
border=colors. Why: CTO guidance says dynamic colors are not supported for button borders. Source:Official forum -
Time mode toggles: Prefer
display_time 'remain,elapsed'withget_timeinstead of custom variables. Why: there is a dedicated verb for the job, and current forum guidance explicitly recommends using it instead of toggling your own skin vars for remain versus elapsed displays. Source:Official,Official forum -
Remote browser/settings views: For simple full-screen Remote layouts, follow the bundled pattern:
toggle '$rmbrowser'drives deck/browser panels withvisibility="var '$rmbrowser' 0/1", andtoggle '$rmsettings'drives a settings overlay initialized withset '$rmsettings' 0. In wide phone layouts where browser is one tab among deck/mixer panels, useskin_panel 'rmbrowser' onand hide deck-only groups with askin_panel 'rmbrowser'visibility query. Usebrowser_window 'folders'/'songs'for touch buttons that focus specific browser panes. Source:Built-in skin -
Scripted tempo relationships: Use
pitch <beats>or compute a BPM-like value,param_cast 'beats', then pass it to the target deck'spitch. Why: Adion notes thatpitchaccepts beats parameters to set pitch so it matches a given BPM, and forum examples showparam_multiply 1.333333 get_bpm & param_cast 'beats' & deck 2 pitch. Source:Official forum,Community -
Mapper vs device definition logic: Put VDJScript in mapper
action=""/query=""or<map action="...">entries, not in device definition XML. Why: staff guidance says device definitions are only the hardware I/O declaration layer; the mapper defines what VirtualDJ says to the device. Source:Official forum -
Pitch reset pad status: For pad-page XML that colors a reset-pitch pad by distance from original BPM, use
get_pitch_value & param_bigger 125/param_smaller 75with bare numeric thresholds, not125%/75%. Put the color thresholds incolor=""and the blink/on/off state inquery="". Why: local testing showed percent literals made the red branch match incorrectly, while bare values match theget_pitch_valuescale where original pitch is100. Source:Official,Local test -
Panel visibility and persistent panel switching: Prefer
<panel visibility="...">for pure query-driven UI andname="",group="",visible="", plusskin_panelgroupwhen you want manual switching that persists across sessions. Why: this is exactly how the panel SDK page distinguishes the two patterns. Source:Official -
Multi-layout skin breaklines: Use top-level conditional
<breaklines>children when different structural layouts in one desktop skin need different vertical stretch regions. Why: root<skin breakline="" breakline2="">is static for the whole document, while conditional<breaklines breakline1="" breakline2="" condition="">entries were confirmed working in GraveRaver and have VirtualDJ community/forum evidence. Pair layout-variable changes withload_skin. Source:Official forum,Community,Local test -
Modular skins: Prefer build-time includes that flatten to one installed
skin.xml. Why: the official SDK still describes skins as a flat package, while extra XML includes remain a forum wish rather than an official runtime feature. This repo already usesxmllint --xincludeto flatten modules before install. Source:Official,Inference -
Remote skin development: Install custom Remote skins under
~/Library/Application Support/VirtualDJ/RemoteSkins, and select them from Settings -> Interface -> Phone/tablet remote. Why: local testing confirmed the Interface tab is the local Remote skin selector; Settings -> Extensions is the online add-on catalog and can make a correctly deployed local skin look "missing." Source:Official forum,Local test,Inference -
Remote skin view tabs: Prefer grouped named panels switched with
skin_panel '<panel-name>' on; drive selected button state withquery="skin_panel '<panel-name>' on". Why: bundled Remote skins use directskin_panelfor touch tabs inside panel groups, and local Grave Raver Remote testing foundskin_panelgrouptop-nav buttons did not react reliably on the Remote surface. Source:Built-in skin,Local test,Inference -
Published skin findings: Preserve unfamiliar commands from working public skins in Published Skin Findings, then reconcile them against live official docs, forum context, and local tests. Why: local docs can lag behind VirtualDJ's live manual, and users need searchable explanations for aliases and skin idioms they encounter in real skins. Source:
Published skin,Official,Community,Inference -
Built-in skins: Use examples/Skins/Built-In as semi-official executable examples of skin XML shipped with VirtualDJ. Why: app-bundle skins are stronger evidence than community skins for how Atomix exercises the skin engine, but they still need curation before becoming preferred local patterns. Source:
Built-in skin,Inference -
Built-in plugin UI XML: Treat examples/Skins/Built-In/Plugin-UI/AFX_beatgrid.xml as evidence for compact effect/plugin GUI surfaces, not as a full desktop or Remote skin template. Why: it is a tiny
341x139skin with no explicitname,image,preview,nbdecks,<deck>,<panel>, browser, or initialization scaffolding. Its controls run in the current plugin context with hardcoded BeatGrid commands such aseffect_command 'set 00',effect_command 'get 00', andeffect_command 'cur 0'. Source:Built-in skin(examples/Skins/Built-In/Plugin-UI/AFX_beatgrid.xml),Inference -
Built-in pad pages: Use examples/Pads/Built-In as semi-official executable examples of pad-page XML shipped with VirtualDJ. Why: app-bundle pad pages are stronger evidence than community examples for how Atomix exercises pad helpers, but they still need curation before becoming preferred local patterns. Source:
Built-in pad page,Inference
For the current pad-page inventory, status labels, built-in page copies, and maintenance checklist, see examples/Pads/README.md. For reproducible documentation fixtures, see tests/README.md.
Recommended runnable pad-page examples:
-
Reference - Slot FX.xml Canonical slot-based audio FX pads.
-
Reference - ColorFX.xml Canonical filter and ColorFX selection patterns using the current verbs.
-
Reference - Page Aware Sampler.xml Legacy page-aware sampler labels, colors, and actions retained for comparison with newer sampler findings.
-
SAMPLER READ ONLY.xml Confirmed read-only multi-page sampler with absolute empty-slot guards.
-
Reference - Sparse Helper Tests.xml Manual-test harness for sparse official helpers such as
connect,system,open_stem_creator,karaoke_venue_name, anddualdeckmode_decks.
Built-in pad-page examples:
- Built-In/README.md
Copied app-bundle
pads_*.xmlpages from VirtualDJ8.5.9307/18.0.9336; use asBuilt-in pad pageevidence rather than curated recommendations.
Skin examples:
-
Built-In/README.md Copied app-bundle desktop, Lite, Remote, and plugin UI skins from VirtualDJ
8.5.9307/18.0.9336; use asBuilt-in skinevidence rather than curated recommendations. -
ModularSkeleton README Build-time XInclude workflow,
<define class>system, and named color patterns. -
ModularSkeleton built skin Flat installed output showing real
<define>,<panel>,<deck>, and<visual>usage. -
GraveRaver Build Demo Minimal XInclude source tree that exists only to demonstrate the build system. Do not treat it as a polished skin reference.
The official SDK still describes a skin package as a .zip containing:
image_name.pngskincode_name.xmlpreview_image.pngoptionally- optional window image files
That makes two things worth keeping straight:
- VirtualDJ expects a flat installed skin package.
- Modularity is a build concern, not a runtime skin feature.
Minimal root pattern:
<skin
name="My Skin"
version="8"
width="1600"
height="900"
image="skin.png"
preview="preview.png"
author="Your Name"
breakline="900"
breakline2="900">
...
</skin>Conditional breaklines for multi-layout skins:
<skin ...>
<breaklines breakline1="675" breakline2="1000" condition="var_equal '@$skin_mode' 0"/>
<breaklines breakline1="980" breakline2="1070" condition="var_equal '@$skin_mode' 1"/>
...
</skin>Use static root breakline="" / breakline2="" when the whole skin has one
stretch region. Use top-level conditional <breaklines> children when one
runtime skin contains mutually exclusive layouts with different stretch regions.
If the controlling variable changes from a button or menu, pair it with
load_skin so the breakline selection is rebuilt.
Source: Official, Official forum, Community, Local test, Inference
Conditional deck-count pattern:
<nbdecks value="2" condition="var_equal '@$4decks' 0"/>
<nbdecks value="4" condition="var_not_equal '@$4decks' 0"/>This is useful when a skin has a user-facing two-deck/four-deck mode. If the controlling variable changes from a button or menu, pair that state change with load_skin so VirtualDJ reparses the structural XML.
Source: Official, Local test, Inference
The core containers worth using first are:
<deck>for deck-scoped UI<panel>for grouped visibility and panel persistence<group>for shared positioning and organization<stack>when only the last visible items should occupy shared slots<define>for reusable classes and placeholders
Preferred pattern:
- Nest elements inside containers instead of repeating
deck=""andpanel=""everywhere. - Use
<define>early for repeated button, text, and frame shapes.
Why:
- The SDK explicitly supports nested containers.
- The common element properties page says nesting is preferred over repeating
panel="".
Source: Official, Local test, Inference
Reusable class defines can use named placeholders:
<define class="LABELED_BUTTON" placeholders="*label,width=160,color=textoff">
<size width="[WIDTH]" height="24"/>
<text text="[LABEL]" color="[COLOR]"/>
</define>
<button class="labeled_button" label="SYNC" width="220" action="sync"/>Practical conventions:
- use uppercase class names in
<define>and lower-case class calls for readability - keep placeholder tokens uppercase inside brackets
- use
*namewhen a placeholder participates in math or expression-like contexts; Atomix describes this as needed for simple math, but the exact boundary of "math" is unclear and may include boolean/condition expressions - unstarred placeholders are common in official skins for straightforward pass-through values such as
action="[ACTION]",text="[TEXT]", andsource="[SOURCECOLOR]"; local canaries showed some unstarred string/condition patterns can remain literal, so document build-specific tests before generalizing - do not declare pass-through attributes such as
actionorqueryas placeholders in a visual button class unless the define body forwards them onto an inner button; otherwise VirtualDJ can consume the attributes and leave a button that shows down-state visuals but fires no command - use
name=valueto provide a default value - use conditional defines when one class needs different implementations per skin mode or color scheme
Example conditional define:
<define class="PADBUTTON" placeholders="*source" condition="var_equal '@$color_scheme' 4">
...
</define>Source: Local test, Inference
There are two useful panel modes:
- Query-driven:
<panel visibility="loop">
...
</panel>- Persistent manual switching:
<panel group="rack" name="fx" visible="yes">
...
</panel>Driven with:
skin_panelgroup 'rack' 'fx'
skin_panelgroup 'rack' +1
skin_panel 'my_panel' on
Use query-driven panels when state should follow live deck conditions. Use named groups when the user is choosing a mode and you want it remembered.
Source: Official
Remote skins in examples/Skins/Built-In/Remote/ show two practical view-switching patterns:
<button action="toggle '$rmbrowser'"/>
<panel name="rmdecksview" visibility="var '$rmbrowser' 0"/>
<panel name="rmbrowserview" visibility="var '$rmbrowser' 1" breakline1="90" breakline2="1476-20"/><button action="skin_panel 'rmbrowser' on"/>
<group name="topwave" visibility="skin_panel 'rmbrowser' ? no : yes"/>
<panel name="rmbrowser" group="rmporpanels" visible="no"/>Use the first for a full-screen browser mode controlled by a global variable. Use the second when browser is one remembered panel inside the same manual panel group as deck/mixer views. Settings overlays are separate in the bundled files:
<oninit action="set '$rmsettings' 0"/>
<button action="toggle '$rmsettings'"/>
<panel name="rmsettingsview" visibility="var '$rmsettings' 1"/>Source: Built-in skin
Use visibility="" for live display state and condition="" for structural selection. Both take VDJScript-style boolean queries, but they are evaluated at different times.
<panel class="main_decks" visibility="not browser_zoom"/>
<panel class="browser_zoom_decks" visibility="browser_zoom"/>
<panel class="pro_2decks" condition="var_equal '@$layout_4deck' 0"/>
<panel class="pro_4decks" condition="var_equal '@$layout_4deck' 1"/>Useful rule of thumb:
visibility=""can follow frequently changing state without a skin reload:masterdeck,loaded,play,browser_zoom,skin_panel, loop state, current deck role, and similar performance-time state.condition=""is better for load-time or reload-time choices: mutually exclusive layout branches, OS-specific controls, texture/color variants, conditional<nbdecks>, conditional<breaklines>, and conditionalcustomicons.condition=""is allowed on skin elements and many nested children, but it is still structural. A false branch is ignored/not loaded rather than merely hidden. When user actions change the controlling state, pair the change withload_skin.- For layout mode buttons,
set '@$skin_mode' 1 & load_skinis reasonable. For a masterdeck-colored label, prefervisibility=""wrappers or a dynamic color expression. - For panels, official docs allow
visible=""/visibility=""; use query-driven visibility for live panels, andname=""+group=""+skin_panel/skin_panelgroupwhen the user should manually switch and persist a panel choice. visibility=""can also be a numeric opacity. If a ternary must return opacity values, wrap the numbers withconstant, for examplevisibility="loaded ? constant 0.5 : constant 0.0".
Source: Official, Official forum, Community, Local test, Inference
The current button SDK page is explicit: query="" enables the <on> graphics when true.
That means:
- Use
query=""to drive the button's on-state. - Do not assume
query=""selects<selected>graphics. - If the element is informational only,
action="nothing"is a valid way to make it non-destructive.
Example:
<button action="nothing" query="play">
<off color="#1C1F24" border="white" border_size="1" radius="10"/>
<on color="#1C1F24" border="orange" border_size="1" radius="10"/>
<text text="PLAYING" color="`masterdeck ? color 'orange' : color 'white'`"/>
</button>Source: Official, Official forum
The safest dynamic-color rules are:
source=on<visual type="color">expects an action that returns a color. Do not wrap it in backticks.color=expects a color value, so a script action must be wrapped in backticks.- Inside a dynamic color expression, return a color with
color 'orange',color '#FF7F00',get_key_color,cue_color 1, etc. - Skin-defined color names work in XML color fields, but not inside backticked VDJScript color expressions.
Examples:
<visual type="color" source="pad_color 1">
<pos x="0" y="0"/>
<size width="24" height="2"/>
</visual><text color="`get_key_color`" action="get_key"/>Deck-scoped reusable-class example:
<define class="SYNC_INFO_EXTENDED" placeholders="*deck">
<text color="`deck [DECK] masterdeck ? color 'orange' : color 'white'`"
action="get_bpm"/>
</define>Preferred methods:
- Use
<visual type="color">for colored underlines, fills, and status bars. - Use a single
<text>with a dynamiccolorexpression when only the text color changes. - Use separate
visibility=""wrappers when you need to preserve skin-defined color names, separate click handlers, or substantially different child structure.
Avoid:
- Dynamic
border=colors on button vector states. CTO guidance says this is not supported. color="`play ? color 'green' : color 'my_defined_skin_color'`". Use the literal color value inside the script, or keep separate XML branches that can use the defined color directly.
Source: Official, Official forum, Local test, Inference
The type="" attribute on <visual> determines what the element renders and how its source="" is interpreted.
| Type | What it renders | source= expects |
Notes | Source |
|---|---|---|---|---|
linear |
Progressively reveals <on> over <off> graphic |
Numeric action (volume, position, get_level, etc.) |
Use orientation="vertical" / "horizontal" and direction="up" / "down" / "left" / "right". Add granularity="N" for a segmented VU meter. |
Official |
onoff |
Shows <on> or <off> graphic based on a threshold |
Numeric action; ≥ 2048 triggers <on> |
Use for beat flash, status indicators. | Official |
color |
Solid filled rectangle in the color returned by source | Action returning a color value (cue_color 1, pad_color 1, get_key_color, etc.) |
No <on> / <off> children needed. Width/height set the fill area. |
Official |
waveform |
Deck waveform display | Implicit (always the current deck's waveform) | Must be inside a <deck> container. source="" is not used. |
Official |
spectrum |
Frequency spectrum analyzer | Implicit (current deck's audio) | Displays frequency bands as a bar graph. | Official |
cover |
Album art for the loaded track | Implicit (loaded track cover art) | Scales to <size>. Use <off> to show a placeholder when no art is available. |
Official |
custom |
Raw graphic tile from skin image | Numeric action | Low-level; use when none of the above types fit. | Official |
<visual source="get_level" type="linear" orientation="vertical" direction="up">
<pos x="680" y="220"/>
<size width="16" height="560"/>
<off color="#23303C"/>
<on color="accent_1"/>
</visual>When using named colors (<define color="...">) instead of a skin image, omit x="" / y="" from <off> and <on> and use the color="" attribute instead.
<visual type="color" source="cue_color 1">
<pos x="0" y="0"/>
<size width="4" height="48"/>
</visual><deck deck="1">
<visual type="waveform">
<pos x="0" y="0"/>
<size width="800" height="80"/>
</visual>
</deck><visual type="cover">
<pos x="24" y="24"/>
<size width="128" height="128"/>
<off color="#1A2129"/>
</visual>Source: Official
The positioning SDK page allows both nested <pos> and inline x="" y="" width="" height="" forms.
For larger skins, use <pos> and <size> consistently because:
- it is easier to scan
- it matches this repo's style
- it makes class placeholders easier to reason about
Source: Official
Use <browser> when the default browser arrangement is enough.
Use the smaller browser elements when the skin needs custom placement:
<folderlist>plus<browsertoolbartree>for the folder tree and its vertical toolbar<fileview>for the full songs area<browsertoolbar>,<coverflow>, and<filelist>when the songs area needs custom composition<sideview>orfilelist source="sideview"for sideview layouts<filelist source="automix">,source="karaoke",source="sidelist", orsource="sampler"for pinned sideview lists<browserinfo>for selected-track info and prelisten<pluginzone>for docked effect GUIs<sampler>for sampler trigger-pad view
When composing these pieces inside <split> panels, use attachX, attachY, resizeX, and resizeY for anchoring/resizing. Use grid="yes" only when a list should always stay in grid view instead of following the user's current Grid/List selection.
If the skin docks effect GUIs, put <pluginzone> in a split named effects so VirtualDJ can resize that area automatically when a plugin GUI appears.
Browser zoom / "mini" layouts are usually not separate VirtualDJ layout types. They are skin branches driven by the browser_zoom state, sometimes combined with browser_isactive for automatic browser-focused behavior:
<panel class="main_decks" visibility="not browser_zoom"/>
<panel class="browser_zoom_decks" visibility="browser_zoom ? true : browser_isactive ? true : false"/>The <browser showzoom="yes"> attribute shows VirtualDJ's built-in zoom control in the browser toolbar; custom buttons can also use action="browser_zoom" and query="browser_zoom".
Bundled Remote skins also use a dedicated Remote browser mode rather than browser_zoom. The browser panel carries panel-local breakline1 / breakline2 values so the resize/stretch region follows the active browser view:
<panel name="rmbrowserview" visibility="var '$rmbrowser' 1" breakline1="432+2+2" breakline2="432+2+975-4-2">
<browser>
<pos x="25" y="344"/>
<size width="2409" height="1061"/>
</browser>
</panel>For wide Remote phone layouts, the bundled browser side buttons use browser_window to focus panes:
<button action="browser_window 'folders'"/>
<button action="browser_window 'songs'"/>Source: Official, Built-in skin, Local test, Inference
-
action1 & action2Sequential actions -
condition ? when_true : when_falseBranching -
query1 && query2Query-only conjunction. Official VDJScript docs describe this as the way to make a chained query return true only when both commands are true. -
Backticks around action-returning values Use when a value consumer needs the result of another action. Acceptance is per-verb:
setand query comparisons take computed values, but transport-style verbs such asloop,beatjump, andphrase_syncignore them (see Tested Grammar Rules below) -
param_*Use for live parameter comparisons and transforms -
var_*Use when you truly need stored state
Examples:
param_equal `get_browsed_song 'type'` 'audio' ? load : nothing
down ? filter 75% : filter 50%
repeat_start 'fxpulse' 1bt & effect_active 1
Source: Official
The full language reference now lives in VDJScript Grammar, which folds these rules together with the later HTTP-channel findings (quoting,
&&short-circuiting, empty branches, backtick surface-dependence) and lists what is still unknown. Read that first; the rules below are kept as the original pad-run record.
Established by a controlled run of the grammar battery pad page on VirtualDJ v2026-m b9482 (see VDJScript Syntax Evidence and the 2026-07-14 entry in VDJScript Local Test Tracker):
-
A trailing
&chain after a ternary's:belongs to the false branch, not the whole statement. Incond ? a : b & c,cruns only whencondis false. Do not place always-run cleanup after a ternary.var_equal '$x' 1 ? action_a : action_b & action_cruns
action_conly alongsideaction_b. -
A leading
&chain before a ternary stays a separate statement. Put unconditional actions first:set '$mode' 1 & play ? action_a : action_bruns the
setalways, then branches. -
Nested ternaries associate the standard way,
a ? (b ? c : d) : e, so clamped-value selection likevar_equal '$phrase_len' 16 ? phrase_sync 16 : phrase_sync 32composes safely. -
Backtick-computed arguments are accepted per-verb.
set '$dst' ``get_var '$src'`` works, butloop,beatjump, andphrase_sync` silently ignore backtick-computed arguments even when the identical literal works. For those verbs, select literal values with a conditional, or use implicit param chaining, which does work:get_var '$src' & param_multiply 2 & set '$dst' -
beatjumpneeds a signed argument on the tested build:beatjump +4jumps,beatjump 4does nothing.
Source: Local test
If VirtualDJ already has a dedicated action for a behavior, prefer that over inventing a variable.
Good examples:
display_time 'remain,elapsed'instead of a custom elapsed/remain toggle varskin_panelgroupinstead of a custom var that emulates grouped panelssetting 'optionName' valuewhen you are intentionally changing a setting
Reason:
- less state drift
- fewer hidden dependencies
- behavior lines up better with controllers and the default UI
Source: Official, Official forum
Variable prefixes decide scope. Plain variables are deck-local; $ variables are global; adding @ makes the variable persistent across VirtualDJ restarts.
deck 1 set 'mode' 1
deck 2 var 'mode' ? action1 : action2
toggle 'MyVar'
toggle '$MyVar'
var_equal '$MyVar' 1 ? action1 : action2
set '$controller_shift' 1 while_pressed
set '@$layout_4deck' 1 & load_skin
Quick guide:
name/#name: local to the current deck%name: local to a logical deck reference such asdeck left$name: global during the current VirtualDJ session@name,@%name,@$name: persistent variants saved across sessions
For skin-wide or controller-wide state, use $... or @$.... A plain set 'mode' 1 can read differently when the script later runs in another deck context. The prefix is part of the variable identity: MyVar and $MyVar are two different variables, so a global variable must be set, toggled, and queried with the $ prefix every time.
For init/setup actions that need local deck state, prefer explicit numbered decks:
deck 1 set 'var1' 0.5
Source: Official, Official forum, Community
Prefer built-in state when VirtualDJ already has it. When the skin truly needs its own layout state, use persistent global variables such as @$layout_4deck, @$skin_mode, or @$show_zoom_racks and keep their purpose narrow. Plain variables are deck-local and can produce different state depending on the current deck context.
set '@$layout_4deck' 1 & load_skin
var_equal '@$layout_4deck' 1 ? action1 : action2
Use load_skin when the variable controls structural XML, such as conditional <nbdecks>, conditional <breaklines>, conditional defines, or mutually exclusive layout branches. Avoid reloading for simple live visibility toggles unless the skin actually needs to rebuild.
Source: Local test, Inference
Prefer:
effect_active 1 ? blink 500ms : off
Over:
effect_active 1 ? blink 500ms
Why:
- explicit
offavoids empty or ambiguous UI states - it is easier to debug later
Source: Inference
pitch can accept a beats-typed value such as 128bt to set the deck pitch so it matches that BPM-like target.
pitch 128bt
For calculated relationships, compute the target BPM, cast it to beats, then feed it to the destination deck's pitch:
param_multiply 1.333333 get_bpm & param_cast 'beats' & deck 2 pitch
This is the useful pattern for half/double tempo helpers, 3:4 transitions, and other scripted tempo-ratio moves. Without param_cast 'beats', a computed plain number can be interpreted as a pitch-slider value instead of a target BPM.
Source: Official forum, Community
Use this pad-page XML pattern when a reset-pitch pad should be green near original tempo, yellow when moderately shifted, and blinking red when far from original tempo:
<pad13 name="RESET PITCH `get_text '%Ppitch%'`" autodim="false" color="loaded ? get_pitch_value & param_bigger 125 ? color 'red' : get_pitch_value & param_smaller 75 ? color 'red' : get_pitch_value & param_bigger 105 ? color 'yellow' : get_pitch_value & param_smaller 95 ? color 'yellow' : color 'green' : color 'black'" query="loaded ? get_pitch_value & param_bigger 125 ? blink 500ms : get_pitch_value & param_smaller 75 ? blink 500ms : on : off">pitch_reset 4bt</pad13>Rules from the working version:
get_pitch_valueis centered on100for the original track BPM.- Use bare thresholds:
95,105,75,125. - In XML, write chained VDJScript
&as&. - Put color selection in the pad
color=""attribute. - Put blinking only in
query="":... ? blink 500ms : on : off.
Source: Official, Local test
The official verbs and the current deck FX UI are slot-based.
Use numbered deck FX slots 1-6 in reference examples and deterministic pad pages. The current manual exposes an FX x6 view, hardware manuals describe six VirtualDJ FX slots, and the official effect_bank_save / effect_bank_load summaries save/load deck FX slots 1 to 6. User-provided local observation on 2026-06-01 found that FX1-FX6 keep their loaded effect across a VirtualDJ quit/reopen, while FX7 and higher keep their loaded effect during the current session/across track loads but reset after restart. Treat slots above 6 as unsupported unless a specific target build is locally tested.
Conceptual model: FX1-FX6 are the persistent deck rack. Use them like saved rack assignments that skins/controllers can display, trigger, and tweak. A pad can still call effect_select to write a new effect into FX1-FX6, but that is a rack-owning preset action because it changes the saved slot assignment. If the pad should behave like a temporary performance effect, prefer padfx or a named stem FX slot instead.
There is no known single-command persistent-slot equivalent to padfx 'cut' 90% 0.5bt 50% 'stemfx:vocal'. That compact padfx form is specific to volatile Pad FX: it names the effect, passes effect parameters directly, can include switch/stem modifiers, and VirtualDJ returns parameters when the pad effect stops. Persistent slot FX use separate verbs: select the effect, set each slider/button, then activate the slot.
Preferred slot workflow:
- Select the effect into a slot
- Activate the slot
- Move the slot's sliders or buttons
Example:
effect_select 1 'Echo' &
effect_slider 1 1 75% &
effect_slider 1 2 1bt &
effect_active 1 on
For a slot-FX preset equivalent to a padfx parameter call, write the steps explicitly:
effect_select 1 'Cut' &
effect_slider 1 1 90% &
effect_slider 1 2 0.5bt &
effect_slider 1 3 50% &
effect_active 1 on
In XML attributes, write those chain separators as &.
Why this is the safest rack-owning reference pattern:
- it mirrors the actual deck FX rack model
- it behaves predictably across skins and controllers
- it avoids name-based ambiguity when several effects are loaded
- it makes clear that the pad owns both the effect choice and the parameter preset
Use Reference - Slot FX.xml for a working repo example.
Source: Official, Local test
effect_active 'Echo' is legal and useful for quick personal mappings. It asks VirtualDJ to toggle an effect by name, wherever that effect is currently represented.
For pad pages that intentionally own/reprogram deck FX slots, prefer one of these slot-based designs:
-
Dedicated slot pads: A pad owns a specific slot, for example Echo on slot 1 and Reverb on slot 2. This works well when the page should allow several effects to remain active at once.
-
Shared slot preset pads: Many pads program the same slot, usually slot 1. Pressing Echo Out replaces whatever is in that slot with Echo Out, sets known parameters, and activates it. This works well for performance pages where the pad row is an effect picker rather than a multi-effect rack.
-
Multi-effect slot pads: Several pads target the same slot, but use
effect_select_multiso VirtualDJ keeps earlier effect instances in that slot instead of replacing them. Query and activate with both the slot and effect name, for exampleeffect_active 1 'echo out'oreffect_active 'vocals' 'reverb', so separate pads can light independently while sharing one slot/channel.
If a pad page is meant to respect the user's persistent FX1-FX6 rack, do not use these preset patterns as the normal pad action. Trigger or control the existing slot instead:
effect_active 1
effect_slider 1 1 75%
effect_button 1 1
Avoid mixing these designs without documenting it. A pad labeled ECHO with only query="effect_active 1" can blink when slot 1 is active with a different effect. If the pad state is meant to mean "slot 1 contains Echo and is active," query get_effect_name <slot> first, then nest the slot active check:
get_effect_name 1 & param_lowercase & param_equal 'echo' ?
effect_active 1 ? blink 500ms : off :
off
To turn the effect off, use the slot activation verb: effect_active 1 off. Reference pages can expose that as a dedicated S1 OFF/KILL pad, or make the preset pad itself toggle only when the same effect is already loaded and active:
get_effect_name 1 & param_lowercase & param_equal 'echo' ?
effect_active 1 ?
effect_active 1 off :
effect_slider 1 1 75% & effect_slider 1 2 50% & effect_active 1 on :
effect_select 1 'Echo' & effect_slider 1 1 75% & effect_slider 1 2 50% & effect_active 1 on
Do not use bare effect_select 1 as that state check. In pad actions, it can open the selector popup. Use effect_select 1 'Echo' only when you are deliberately loading a named effect into the slot.
Official VDJScript documents && for query chains, for example "true only when both commands are true." That is different from using && inside a complex pad action body that also branches and performs load/set/on actions. Use nested conditionals for same-pad toggle actions, and reserve && for simple query expressions you have verified in the target surface.
For intentional multi-effect use in one numeric slot:
effect_select_multi 1 'Echo Out' & effect_active 1 'Echo Out'
effect_select_multi 1 'Reverb' & effect_active 1 'Reverb'
For a named stem FX slot, use the same pattern with the stem slot name. This local pad-page fragment keeps Echo Out and Reverb as separately queryable/activatable effects on the vocal stem FX slot:
<pad1 name="FX-VOCALS\nECHO OUT"
color="stem_color 'vocal'"
query="effect_active 'vocals' 'echo out'">
effect_select_multi 'vocals' 'echo out' & effect_active 'vocals' 'echo out'
</pad1>
<pad2 name="FX-VOCALS\nREVERB"
color="stem_color 'vocal'"
query="effect_active 'vocals' 'reverb'">
effect_select_multi 'vocals' 'reverb' & effect_active 'vocals' 'reverb'
</pad2>Omitting on makes the named effect instance toggle. Add on when the pad should only turn the effect on:
effect_select_multi 'vocals' 'echo out' & effect_active 'vocals' 'echo out' on
Source: Official, Official forum, Local test, Inference
For a reusable FX panel, ask VirtualDJ what the selected effect exposes instead of hardcoding slider/button labels:
<slider action="effect_slider 1 1"
dblclick="effect_slider_reset 1 1"
disabled="not effect_has_slider 1 1"
frommiddle="get_effect_slider_default 1 1 0.5"/>
<text action="get_effect_slider_label 1 1"/>
<button action="effect_button 1"
visibility="effect_has_button 1"
textaction="get_effect_button_shortname 1"/>Use this for skin panels and generic controller displays. For pad pages that intentionally load one known effect preset, hardcoded values such as effect_slider 1 1 75% are still clearer.
Video FX and transitions use the same pattern with special targets:
effect_has_slider 'video' 1
get_video_fx_slider_label 1
effect_has_slider 'transition' 1
get_effect_slider_label 'transition' 1
Source: Official, Built-in skin, Inference
Current official behavior:
filterapplies the selected ColorFX to the sound- nothing is applied at
50% - more effect is applied the farther the control moves from center
filter_selectcolorfxselects which ColorFX the filter knob controlsfilter_labelreturns the label under the filter knobfilter_resonancechanges filter resonance
Preferred method for the main deck filter:
filter_selectcolorfx 'Echo' &
filter 75%
Preferred method for a dedicated select-only button:
filter_selectcolorfx 'Flanger'
Preferred method for a ColorFX selected-state query:
param_equal `filter_label 'name'` 'Flanger' ? (effect_active 'colorfx' ? on : off) : off
Preferred method for an extra custom ColorFX control:
effect_colorfx 1 'Echo'
effect_colorslider 1
Notes:
effect_colorslideris the center-off ColorFX-style slider action.effect_colorfxexposes up to four extra custom ColorFX slots.- In pad XML
query=""attributes, usefilter_label 'name'for selected-state checks instead of running selector actions such asfilter_selectcolorfx 'Name'. - CTO guidance says that the dedicated
colorfxslot only exposes approved ColorFX-compatible effects, while extra slots are more flexible.
Use Reference - ColorFX.xml for a working repo example.
Source: Official, Official forum
-
If you are emulating the standard deck filter knob: use
filter_selectcolorfx+filter -
If you are building extra ColorFX-like controls that should not steal the deck's main filter: use
effect_colorfx <1-4>+effect_colorslider -
If you are building a deterministic pad page for normal audio effects: use regular slot FX instead of ColorFX
Source: Official, Official forum
The official verbs list includes effect_stems, effect_arm_stem, and effect_stems_color.
There are three related but distinct Stems FX control paths:
-
Shared FX-rack stem routing: Use
effect_stems <stem>when the normal deck FX rack should route to one stem or stem group. -
Named stem FX slots: Use a stem slot name as the first parameter to normal
effect_*verbs when the effect instance should live in a separate stem-specific slot. Adion's forum guidance givesrhythmexamples and saysvocalsis separate from numeric slots 1/2/3. Current known named stem FX slots arevocals,bass,instru,rhythm,melody,hihat, andkick. -
Pad FX stem targets: Use
padfx ... 'stemfx:<stem>'for a quick pad effect that applies only to that stem while the other stems continue playing.
Examples:
effect_stems 'vocal' & effect_active 1
effect_select 'vocals' 'Reverb'
effect_active 'vocals'
effect_slider 'vocals' 1 50%
effect_slider 'vocals' 'echo' 1 50%
effect_show_gui 'vocals' 'Reverb'
effect_select_multi 'vocals' 'Echo Out'
effect_active 'vocals' 'Echo Out'
padfx 'reverb' 75% 'stemfx:vocal'
The naming is easy to trip over: padfx uses the singular target string stemfx:vocal, while the separate vocal effect slot is documented in forum examples as vocals.
Use effect_select_multi 'vocals' '<effect>' when multiple effects should remain loaded/active on the same vocal stem FX slot. Query or toggle each instance with effect_active 'vocals' '<effect>', not just effect_active 'vocals', when separate pad LEDs should reflect separate effects.
User-provided local observation on 2026-06-01 found that named stem FX slots such as vocals and rhythm keep their loaded effect during the current session and across track loads, but reset after a VirtualDJ restart. This note is about loaded effect selection only; active state, slider values, and multi-effect contents still need a recorded build-specific persistence pass.
This makes named stem FX slots useful as volatile, pad-assigned state: a pad can load/activate an effect for a stem without rewriting the performer's persistent FX1-FX6 rack, and VirtualDJ eventually clears that assignment on restart.
padfx parameter values are applied when the pad effect starts, but padfx should not be treated as private per-pad state. User-provided testing showed that another pad can call the same effect/stem target and alter the active parameter values. User testing also found that placing effect_disable_all 'padfx' immediately before a new padfx chain can stop the new chain from activating. Use effect_disable_all 'padfx' as a separate cleanup control. If a preset must become a visible/restart-persistent rack chain, use deliberately owned FX1-FX6 slots; otherwise keep pad-owned performance effects in padfx or named stem FX slots.
This direct parameter syntax is one reason padfx fits volatile pad-owned FX. A Pad FX can say "run Cut now with these values" in one command. A persistent rack slot is treated more like stored device state, so effect_select and effect_active do not take inline slider values; use effect_slider / effect_button steps when a pad intentionally rewrites that rack state.
melovocal and melorhythm may exist as named stem FX slots, but they need local testing before being treated as confirmed.
Observed pad XML selected-state pattern:
effect_select 'vocals' 'reverb' ? effect_active 'vocals' : off
Caution:
- Older official forum posts from 2021 reported inconsistencies between regular slot FX and special slots such as
colorfx. - Treat any ColorFX-plus-stems behavior as build-sensitive and test it on the exact VirtualDJ build you use.
- The full list of named stem FX slot strings is not published in the official appendix. Do not assume aggregate names such as
instrumentaloracapellaare valid named stem FX slots unless they are tested in the target build.
That first caution is intentionally dated because the forum guidance is older than the current manual.
Source: Official, Official forum, Community, Local test
Some FX verbs are workflow helpers rather than separate audio engines:
-
effect_bank_save <n>/effect_bank_load <n>: Save and recall deck FX slots 1-6. Use these for persistent rack snapshots; use expliciteffect_select/effect_slidermacros only for pad pages that intentionally own/reprogram the rack. -
effect_3slots_layout,effect_select_popup,effect_select_toggle,effect_list, andeffect_list_edit: UI/list helpers for exposing VirtualDJ's native FX panel behavior. Do not use selector popups as state queries. -
effect_arm_*: Controller-style armed FX workflow: choose target deck/path, choose effect, choose participating slots, then activate. Prefer direct slot verbs in pad pages unless the controller workflow is intentionally armed. -
effect_releaseslider*andis_releasefx: Release-FX-specific controls, separate from ordinary deck FX sliders. Exact selection/query behavior still needs a focused local fixture. -
effect_fxsendreturn*: Hardware/software send-return routing helpers. Treat as mixer-context-specific, not default deck FX. -
effect_command: Plugin-specific command channel. Built-in BeatGrid plugin UI uses commands such asset 00,get 00, andcur 0; do not generalize those strings across effects.
Source: Official, Built-in skin, Inference
The current native effects appendix is the authoritative list for built-in effects, video effects, and transitions.
High-frequency audio effects to design around first:
- Echo
- Echo Out
- Reverb
- Beat Grid
- Flanger
- Filter
- Noise
- Phaser
- Loop Roll
- VinylBrake
- Stutter Out
For the current full list, use the official appendix instead of hard-coding old plugin menus into your docs.
Source: Official
Pad pages are generic VDJScript containers. A page can be Hot Cues, Stems, Loop Roll, Slicer, Sampler, Stems+FX, Scratch, Keycue, Loop, Saved Loops, Manual Loop, Remix Points, Custom Buttons, a controller-specific FX page, or a user page. The official Pads Editor describes each pad as specific to its page and mappable to any VDJScript action.
For skins and controller displays that need to operate on whatever page is currently selected, use the generic pad verbs:
pad <n>triggers the current page's pad action.padshift <n>triggers the current page's shifted pad action.pad_has_action <n>checks whether the current page defines a push action for that pad.pad_has_pressure <n>andpad_has_color <n>check optional pressure/color behavior.pad_color <n>returns the current page's pad color;pad_button_color <n>returns controller-ready color feedback.pad_has_param 1/2,pad_param,pad_param2,pad_has_menu, andpad_menuexpose the current page's parameters and menu.
Bundled desktop and Remote skins follow this model: pad buttons use action="pad 1", shifted actions use rightclick="padshift 1", labels use textaction="pad 1", and colors use pad_color 1. This keeps the skin agnostic to whether the active page contains hot cues, FX, sampler slots, loops, or blank custom buttons.
Do not use sampler-specific queries such as sampler_loaded to decide whether a generic pad has a current-page action. "Empty" is page-specific: an empty Hot Cue pad may still be intentionally active because it sets a cue, an empty Saved Loop or Remix Point pad may create data, an empty Sampler pad may record when the bank is unlocked, and a blank Custom Button may have no action at all.
Source: Official, Built-in skin
The current pads and sampler manuals say:
- the Sampler page shows the first eight pads of the active bank
- Parameter 2 cycles samples in the bank when there are more than eight
- right-click or shift stops a triggered sample
- an empty sampler pad can record a sample when the bank is unlocked
- locking a sampler bank prevents recording and deletion
Source: Official
This subsection is only about custom pages that intentionally operate on the Sampler. It does not define generic pad-page pushability.
Use page-aware methods when the UI should follow the visible 1-8, 9-16, 17-24, and later pages:
sampler_padsampler_colorsampler_pad_pagesampler_pad_volume
Use absolute-slot methods when the UI should always target the same underlying sample slots:
sampler_playsampler_stopsampler_loadedget_sample_nameget_sample_colorsampler_volume
Practical rule:
- visible sampler-pad UI: page-aware
- fixed sampler-slot utility controls: absolute
Use SAMPLER READ ONLY.xml for the current confirmed read-only multi-page pattern. Reference - Page Aware Sampler.xml is retained as a legacy page-aware sampler example, but it uses the now-unreliable sampler_loaded <n> 'auto' guard.
Source: Official, Local test, Inference
The current verbs page documents sampler_loaded <n> as a fixed slot query.
VirtualDJ forum examples and older local examples use sampler_loaded <n> 'auto' beside sampler_pad <n> 'auto'. The installed/public Loop Recorder.xml pad page uses the unquoted form sampler_loaded <n> auto. Neither form is documented as official behavior.
Local diagnostic testing showed this pattern is not reliable for page-aware sampler empty-slot checks:
- Test page: Reference - Sampler Loaded Test.xml
- Build: VirtualDJ 8.5.9307 / 18.0.9336
- Date: 2026-05-21
- Setup: sampler bank page 2 (
9-16), slot 8 loaded, slot 16 empty - Result:
sampler_loaded 8 'auto'andsampler_loaded 8 autoreturned true whilesampler_loaded 16andsampler_loaded 16 autoreturned false
Use sampler_loaded with the absolute sampler slot behind the visible sampler pad. Page 2 pad 8 should be guarded by sampler_loaded 16; keep sampler_pad 8 for the visible page-aware sampler action/label. Quoting auto does not change this behavior in the tested build.
Source: Official, Community, Published pad page, Local test
For sampler pages that must never record into empty slots, use sampler_pad_page to branch by the visible text range and then guard with the absolute slot behind the pad:
sampler_pad_page & param_equal "1 to 8"with pad 8 checkssampler_loaded 8sampler_pad_page & param_equal "9 to 16"with pad 8 checkssampler_loaded 16- in a 16-pad layout, pad 16 on
"9 to 16"checkssampler_loaded 24
Use sampler_pad <pad> for loaded actions and nothing for empty actions. Do not include sampler_rec, sampler_assign, or drop= on a read-only page. For intentionally blank names, return get_text ' ' rather than an empty string; local testing showed empty strings can fall back to visible slot numbers on later pages.
Working example: SAMPLER READ ONLY.xml
Source: Local test, Inference
When a sampler slot is empty and the page should not record, assign, or edit it, nullify the sampler action with an explicit false branch instead of leaving the conditional incomplete:
<pad10 name="`sampler_loaded 10 ? sampler_pad 10 : get_text ' '`" color="sampler_loaded 10 ? sampler_color 10 : dim" query="sampler_loaded 10 ? sampler_play 10 ? blink 1bt : on : off">sampler_loaded 10 ? sampler_pad 10 : nothing</pad10>If the page defines shift_pad<n> entries, give the shifted pads their own color="" expression. Skin frameworks that render shifted pad state may read the shifted pad color separately; without a shifted color, empty or shifted sampler pads can fall back to the skin/default button color instead of matching the normal pad.
<shift_pad10 name="`sampler_loaded 10 ? sampler_pad 10 : get_text ' '`" color="sampler_loaded 10 ? sampler_color 10 : dim">sampler_loaded 10 ? sampler_edit 10 : nothing</shift_pad10>Source: Local test, Inference
Current official options worth knowing:
-
samplerSpanAcrossDecksWhen set toyes, a 16-sample bank makes deck 2 automatically show9-16 -
samplerIndependentDeckBanksEach deck and master can have their own sample bank -
displayTimeSelects elapsed, remain, or total display mode
Source: Official
A forum thread published on 2025-09-23 reported inconsistent sampler sync behavior on controller pads in VirtualDJ 2025 builds, especially when the triggering deck was not the master deck. The same thread shows:
- a workaround suggested by CTO Adion on 2025-09-26: try
deck master sampler_pad <n> - the original poster later reporting on 2025-10-04 that support resolved the issue in an Early Access update
Practical takeaway:
- treat master-deck sampler workarounds as build-specific
- do not document them as timeless behavior
Source: Official forum
Useful filter building blocks:
- comparison operators
- logical operators
- date and time filters
- tag filters
- mixing and library filters
Typical patterns:
genre contains house
bpm > 120 and bpm < 130
year >= 2020
type = video
The official appendix remains the best exhaustive source here, so keep repo docs focused on patterns you actually use instead of copying the whole appendix into local markdown.
Source: Official
High-value official options for skin and pad authors:
-
filterDefaultResonanceSets the amount of resonance applied by the filter -
fxProcessingChooses whether effects are processed pre-fader or post-fader -
resetFXOnLoadStops all effects when a new song loads -
globalQuantizeSets beat, measure, or quarter quantization -
smartLoopAuto-adjusts loop points for seamless loops -
quantizeSetCueAuto-aligns newly set cues according to quantization
Script note:
setting 'filterDefaultResonance' 75%
setting 'fxProcessing' 'post-fader'
Source: Official
The SDK still documents a flat skin package:
skin.xml- image file
- optional preview file
It does not document runtime support for loading arbitrary extra XML modules from the main skin file.
Source: Official
Use build-time modularity:
- keep source XML split into
defs/andpanels/ - compose with XInclude or another XML preprocessor locally
- build one flattened
skin.xmlbefore install
This is the pattern demonstrated by ModularSkeleton. Its flattened build/skin.xml output is the installed-form reference.
Why:
- easier maintenance
- reusable classes and panel slices
- installed output still matches the official flat package model
Source: Inference
Use ModularSkeleton as the starting point.
It demonstrates:
- build-time XInclude flattening with
xmllint --xinclude - named color defines and reusable class defines with placeholders
<panel>,<deck>, and<visual>composition patterns- a flat
build/skin.xmloutput ready for installation
Official docs:
- VirtualDJ Skin SDK
- Custom Browser
- Skin Button
- Skin SDK Dropzone
- Skin Panel
- Skin Default Colors
- Skin SDK Visual
- List of VDJScript verbs
- VDJScript language overview
- List of Options
- List of Native Effects
- Pads manual
- Sampler manual
- Atomix Productions acquires AdionSoft
Official forum guidance cited for method choices:
- Border Color using placeholder
- effect_colorfx & effect_stems_color ?
- BUILD 7403 - Multiple stems fx can be used at the same time?
- Default filter and color fx filter
- Skin text action; visibility or visual?
- Virtual Dj 2025 Sampler Sync
- No longer possible to access 16 samples from controllers with 8 x 2 pads?
- Aditional xml for Skins
- XML Variables in Skin and Database
- Sending MIDI CC Commands
- Script/Param/Variable Maths
Community forum examples cited for method choices:
- How to map specific Fx?
- ONE EFFECT ON ONE STEM ON A CONTROLLER
- Legacy Echo's Name?
- set local variable on init
Repo examples:
- 32 Samples.xml
- AUTO CUES.xml
- COLOR FX.xml
- CUE.xml
- CUE 16.xml
- CUE SCAN.xml
- PLAY 16.xml
- PUSH FX.xml
- Reference - Slot FX.xml
- Reference - ColorFX.xml
- Reference - Page Aware Sampler.xml
- SAMPLER.xml
- SAMPLER SIMPLE.xml
- TRANSPORT.xml
- Built-in skins
- ModularSkeleton README
- ModularSkeleton built skin
- GraveRaver Build Demo