Skin Extension Script API

Last modified by Eleni Cojocariu on 2026/09/30 18:26

Reference

The calls that pull a skin extension into the response being built, from Velocity and from Java. Pulling is what puts the generated script or link element on the page; Skin Extensions describes what happens on either side of it.

Pulling from Velocity

Seven pull methods are exposed to Velocity, one per source the code can come from.

CallWhere the code comes from
$xwiki.jsx.use('Space.Page')An XWiki.JavaScriptExtension object on a wiki page.
$xwiki.ssx.use('Space.Page')An XWiki.StyleSheetExtension object on a wiki page.
$xwiki.jsfx.use('path/to/file.js')A JavaScript file of the current skin, or of the resources folder of the web application.
$xwiki.ssfx.use('path/to/file.css')The same, for a style sheet.
$xwiki.jsrx.use('path/to/file.js')A JavaScript file inside a JAR, in WEB-INF/lib or brought in by an installed extension.
$xwiki.ssrx.use('path/to/file.css')The same, for a style sheet.
$xwiki.linkx.use($url, $parameters)Any address. The parameters become the attributes of the generated link element rather than a query string.

The argument of jsx and ssx is the reference of the wiki page holding the object, not the object's name field, which the plugin ignores entirely (see Skin Extension Object Fields):

{{velocity}}
#set ($discard = $xwiki.ssx.use('My.CSS'))
#set ($discard = $xwiki.jsx.use('My.JavaScript'))
{{/velocity}}

linkx exists for the files an installed extension ships rather than the wiki, a WebJar above all:

#set ($discard = $xwiki.linkx.use($services.webjars.url('artifact-id', 'path/to/file.css'),
  {'type': 'text/css', 'rel': 'stylesheet'}))

jsfx and ssfx accept a boolean in place of the map, $xwiki.jsfx.use('path/to/file.js', true), which is the same as passing {'forceSkinAction': true}.

Only jsx and ssx read the use field of an object. A skin file, a file inside a JAR and a link are loaded by an explicit pull and by nothing else.

Parameters of use()

All seven take a map as second argument. For six of them every entry is appended to the generated URL as &key=value, key and value both URL encoded so that a value cannot break out of the element being written; linkx is the exception named above. Four names have a meaning of their own: three that are passed, and language, which the plugins add by themselves.

ParameterApplies toDefaultWhat it does
languagejsx and ssxthe locale of the requestAdded on its own, so the translations the extension uses resolve in the reader's language.
minifyjsx, ssx, jsrx, ssrxtruefalse serves the source unminified. On jsfx and ssfx it takes effect only together with forceSkinAction.
forceSkinActionjsfx and ssfxfalseRoutes the file through the skin action, which evaluates it as Velocity. Read and then dropped, never appended to the URL.
deferthe three JavaScript extensionstruePuts defer='defer' on the generated script element. The instance default is the xwiki.plugins.skinx.deferred.default property of xwiki.cfg.

Any other entry of the map arrives at the extension as a request parameter, which is how a parsed extension is given something to work with; see Generate Skin Extension Content with Velocity.

While debugging, adding minify=false to the query string of the page being read is enough: the plugins copy it onto every extension URL of that page, so it does not have to be passed call by call. The instance-wide switch is the debug.minify property of xwiki.properties, true by default.

Deferred execution

A JavaScript extension does not hold the page up: the browser finishes parsing the document and runs the script after that. Pass {'defer': false} for a script that has to run earlier.

Pulling from Java

Java code injects the org.xwiki.skinx.SkinExtension role under one of the seven hints:

@Inject
@Named("jsx")
private SkinExtension jsx;

// ...

this.jsx.use("My.JavaScript");

Each of those components is a thin wrapper that finds the matching plugin by its hint and forwards the call. It is the only supported way in from Java: a pull made any other way is lost when a cached asynchronous block is rendered a second time.

FAQ

A parameter passed to jsfx or ssfx never reaches the file. Why?

A skin file is served straight from the skin, with no query string, and the parameters are dropped. They are appended only when the file goes through the skin action, so pass {'forceSkinAction': true} along with them.

Related

Get Connected