This is the talk page for discussing improvements to the AddRailModule page.

Move Rail Module

Is it possible to move the extra rail module to the last slot of the rail? (I don't want the module I created to be shown at the top.)

Manere (talk) 17:56, May 29, 2016 (UTC)

Maybe provide an option for the position. Agent Zuri Profile Message Wall Blog 10:38, July 5, 2017 (UTC)

Multiple rail modules

Hey! Is it possible to adapt this script to make it able to add multiple rail modules? For instance, a template:RailModule and a template:RailModule2 or something. Also, the doc states that you can rename Template:RailModule to whatever you want but doesn't explain how, does it involve somehow? I think the doc should explain that, mostly for users that want to put the rail module in MediaWiki namespace instead.--V Technobliterator TC 17:00, June 6, 2016 (UTC)

I have made this quick draft. It uses the railModules variable in which you store the pages you'll be collecting the extra modules from.
var railModules = ['Foo', 'Template:Bar', 'Template:RailModule', 'Template:RailModule2'];
    $.each(railModules, function(i, title) {
        $.get('/index.php?title=' + title + '&action=render', function(html) {
            if (!html.trim()) return;
            $('<section class="module"></section>')

-- Dorumin 17:34, June 6, 2016 (UTC)

Oh, perfect. I could likely use something very similar to this. Thanks!--V Technobliterator TC 12:57, June 7, 2016 (UTC)
Could you make your script look for a page like Template:RailModules (or even better: MediaWiki:Custom-Rail-Modules) as well which works like MediaWiki:ImportJS? Benefit: The user can add options separated by pipes. And you could add a class to them (e.g. for styling).
Agent Zuri Profile Message Wall Blog 10:41, July 5, 2017 (UTC)

Main Page

Is there a way to force this module to show up on the Main Page? I know I can just input the {{RailModule}} Template directly into the Main Page, but then it wouldn’t have the...uh, the border I guess, that surrounds Rail Modules. Is there a way to make the module show up on the main page with the border, or a way to emulate the border with a div or something?
~Ursuul 12:08, January 20, 2017 (UTC)

Yeah, it's me again. You could probably use <mainpage-rightcolumn-start /> + <mainpage-endcolumn /> and surround your content with something like <div id="mp-railModule" style="border:1px solid #000;"></div>. I do something like that on my test wiki's main page.
Alternatively, you could use float:right; to replicate a RailModule's righthand placement, like so:
<div id="mp-railModule" 
style="border:1px solid #000; float:right; padding:0 5px 0 5px; width:30%;">
Content here
You can adjust the width by altering the percentage. CoH  22:19, January 20, 2017 (UTC)

Sitewide Module

It looks pretty close to a normal module. You are literally the single-most helpful person on all of Wikia. Thanks so much! :D
~Ursuul 08:59, January 21, 2017 (UTC)

Hey, do you happen to know how to make the “Create Blog Post” button on boxed Bloglists go away? I’m fine with it on the sitewide module, but on the MainPage module it just gets in the way. The lines are scrunched up a bit. The module itself is the same width as usual, but the Bloglist is wider for some reason inside the div than it is inside the RailModule.
~Ursuul 09:13, January 21, 2017 (UTC)

vs Main Page Module

I suppose you could do...
.WikiaBlogListingBox .wikia-button {
Alternatively, you can use these styles below to duplicate the look of the actual rail module, as shown in the image.


.WikiaBlogListingBox h2 {
.WikiaBlogListingBox ul {
.WikiaBlogListingPost:first-child {
.WikiaBlogListingBox .wikia-button {
	margin:20px 15px 0 0;
CoH  11:03, January 21, 2017 (UTC)
Screen Shot 2017-01-21 at 6.21.31 AM

If I use padding to replicate that on the main page, will that shrink the Bloglist down to the appropriate size? (also what element would I add the padding to? Like how?)

────────────────────────────────────────────────────────────────────────────────────────────────────I tried them both individually & together. The first option doesn’t remove the line, the second one does. Both of ’em together looks even better without the random button, but now there’s a bit of a large space where the old button was. In addition, in the RailModule, the “See More” button is inside the box, whereas in the Div the “See More” button is outside the box & to the left instead of the right. What should I do to fix those? Should I set
.WikiaBlogListingPost:first-child {

to 0px? Would that remove the extra space where the button used to be?
~Ursuul 11:18, January 21, 2017 (UTC)

EDIT: The space is on both the RailModule as well as the Div now ;-;
~Ursuul 11:41, January 21, 2017 (UTC)

Okay test this:
.WikiaBlogListingBox ul {
.WikiaBlogListingPost:first-child {
.WikiaArticle .WikiaBlogListingBox ul {
.WikiaBlogListingBox .more {
    float: right;
    font-size: 10px;
    font-weight: bold;
CoH  11:45, January 21, 2017 (UTC)
I should also note that since you apparently cloned the actual rail module classes on the main page, changes to the CSS will affect both, which is likely what you're experiencing. To affect only the main page, prefix the main page-only rules with CoH  11:56, January 21, 2017 (UTC)
Rail Module

Site-wide Rail

Didn’t quite work. See images.
Response to Addendum: Ok, I’ll try it. Like this?
Main Module

Beautiful. Literally almost perfect besides the upper space & the fact that the line came back. .WikiaBlogListingBox ul {
} .WikiaBlogListingPost:first-child {
} .WikiaArticle .WikiaBlogListingBox ul {
} .WikiaBlogListingBox .more {
    float: right;
    font-size: 10px;
    font-weight: bold;
Yes. That should affect only the main page. We will deal with the line and unused space once we get these bugs worked out. I should have realized you had used the rail module classes. My bad. CoH  12:07, January 21, 2017 (UTC)

────────────────────────────────────────────────────────────────────────────────────────────────────Ok it’s in the Wikia.css. Here’s what it looks like now.
~Ursuul 12:11, January 21, 2017 (UTC)

Okay, much better. Now give me a second to sort out this border and spacing issue on both pages. CoH  12:14, January 21, 2017 (UTC)
This is janked because of the shared classes, but this should work: .WikiaArticle h2 {
} .WikiaBlogListingBox ul .WikiaBlogListingPost:first-child {
} .WikiaBlogListingBox {
.WikiaBlogListingBox ul .WikiaBlogListingPost:first-child {
I would toss this at the bottom, below any of the other blog module rules. I have no idea how poorly all these workarounds are going to interact with each other.
CoH  12:25, January 21, 2017 (UTC)
Gotcha, I’ll move it to the bottom now. Meanwhile, do you want me to toss you sysops? Because the padding vanished when I added this. Look.
~Ursuul 12:29, January 21, 2017 (UTC)
Sysops me in five minutes. I have to step away from the computer for a few minutes. If you wish, perhaps you should revert or comment out all the changes. At this point, I just need to start again and ensure that nothing is interfering. CoH  12:32, January 21, 2017 (UTC)

Why is there no warning that this could violate the ToU?

Removing stuff from the sidebar rail is definitely a ToU violation, but I don't think you're free to add anything you want (especially at the top). -- Fandyllic (talk · contr) 20 Jun 2017 2:20 PM Pacific

Because its not a ToU violation. There are other scripts that do the same thing (ex DiscordIntergrator) that add things to the rail, and they can be used sitewide. Also it adds to the bottom of the rail, not the top. --Sophie 23:01, June 20, 2017 (UTC)


The FANDOM recirculation module overlaps with this when scrolling down. leviathan_89 @fandom 09:58, August 4, 2017 (UTC)

<noinclude> Tags

Attempting to transclude a documentation (& categorize the template) causes the documentation to be loaded in the right rail even when surrounded in <noinclude> tags. Is there a way to make the script ignore content between such tags, as normal templates do?
UrsuulTalkCMDate4:31 AM Friday, August 18, 2017 (UTC)

Additional Modules

"(Allow multiple modules, make {{PAGENAME}} usable, switch to parsing with action=parse to make <choose> usable)"

This was said in one of the edit summaries, but I still do not understand how to add another module.—|| Grudgeholderr ||— 01:55, March 3, 2018 (UTC)

Proposal to add backwards-compatible per-module prepend configurability

The current implementation supports multiple modules (via window.ARMModules) and prepend configurability (via window.ARMPrepend) for the entire sequence of modules. I'd like to propose extending this script to permit per-module prepend configurability. A sample patch and small set of sanity tests follow.

+++ Proposed Changes
@@ -7,9 +7,9 @@
     if (!$rail.exists()) {
-    function place($module) {
-        var $ads;
-        if (window.ARMPrepend) {
+    function place(conf) {
+        var $ads, $module = conf.section;
+        if (conf.prepend) {
             $ads = $('#top-right-boxad-wrapper, #top-boxad-wrapper, #NATIVE_TABOOLA_RAIL').last();
             if ($ads.exists()) {
@@ -30,9 +30,10 @@
     (window.ARMModules || ['Template:RailModule']).forEach(function(el) {
+        var conf = ((typeof(el) === 'string') ? {page: el, prepend: window.ARMPrepend} : el);
         $.get(mw.util.wikiScript('api'), {
             action: 'parse',
-            text: '{' + '{' + el + '}}',
+            text: '{' + '{' + + '}}',
             title: mw.config.get('wgPageName'),
             format: 'json'
         }).done(function(d) {
@@ -40,13 +41,14 @@
                 'class': 'railModule rail-module',
                 html: d.parse.text['*']
+            conf.section = $section;
             if (loaded) {
-                place($section);
+                place(conf);
             } else {
-                loadQueue.push($section);
+                loadQueue.push(conf);
-            mw.hook('AddRailModule.module').fire(el);
+            mw.hook('AddRailModule.module').fire(;
     if (railClass && railClass.split(/\s+/).indexOf('loaded') === -1) {
// expectation: both modules are appended
window.ARMModules = ['MediaWiki:Communitypage-tasks-header-welcome', 'MediaWiki:Communitypage-subheader-welcome'];
// expectation: both modules are prepended
window.ARMPrepend = true;
window.ARMModules = ['MediaWiki:Communitypage-tasks-header-welcome', 'MediaWiki:Communitypage-subheader-welcome'];
// expectation: first module is prepended, second module is appended
window.ARMModules = [{page: 'MediaWiki:Communitypage-tasks-header-welcome', prepend: true}, 'MediaWiki:Communitypage-subheader-welcome'];
// expectation: first module is appended, second module is prepended
window.ARMPrepend = true;
window.ARMModules = [{page: 'MediaWiki:Communitypage-tasks-header-welcome', prepend: false}, 'MediaWiki:Communitypage-subheader-welcome'];

puxlit (talk) 06:35, January 25, 2019 (UTC); edited 07:27, January 25, 2019 (UTC)

Kocka has given me their provisional blessing; changes applied. puxlit (talk) 13:21, January 28, 2019 (UTC)

Proposal to cache module pages in localStorage

As of revision 112227, ARM tries to cache responses via smaxage/maxage. However, since the parse API module's cache mode is anon-public-user-private, and due to what appears to be an overzealous VCL rule, the net effect is that: (a) clients still have to make a request per page load; and (b) the only caching that occurs is at the edge for anon users. I'd like to propose using localStorage in a manner similar to I18n-js to cache module pages and thereby minimise the number of requests. Broad points against such a change (as voiced when the idea was floated in #javascript) include: (a) potential GDPR issues; and (b) potential localStorage bloat. Thoughts? puxlit (talk) 12:53, February 22, 2019 (UTC)

me personally is not a big fan of keeping unspecified amount of data in the persistent cache, especially if it has so strong limitations (right now, chrome allows to use 5mb). due to nature of ls and current site structure, all languages will have shared ls (one ls per origin), that means the script will store cache on per-language basis. the more languages has wiki, the more amount of ls will be used. 2nd problem is cache itself: it can't be stored for too long time (i do expect for 1 hour of max age). why: because of long-living cache, and virtually absent methods of cache invalidation, it will be... surprising for users that will change rail template and will wait for visible effect at reasonable amount of time. thus, the size and time issues leaves to us the only option (outside of db engine): session storage. ss will be cleared once tab\window is closed, so it solves both problems. at the same time, this solution itself is part of the problem: cache can't be stored for long time and can't be shared by different tabs, so the real benefit will be for refresh-like actions only. long story short: doesn't worth it, like for me.
ps long-living (ls) cache should be optional and turned off by default, in order to keep users awared about it; when short-living (ss) might be used by default. Fngplg (talk) 17:48, February 22, 2019 (UTC)
RE: localStorage bloat, I wouldn't expect the combined size of ARM-supplied rail module pages to exceed more than a few kilobytes for the majority of wikis, nor would I expect the average user to visit more than a handful of wikis on the same origin.
RE: cache duration, currently maxAge ∈ [0, 86400] seconds, with five minutes as the default. For wikis where admins know their rail modules rarely change, setting maxAge to one day is a sensible decision, for which this proposed caching mechanism would save one request per page load where ARM would be active.
RE: cache invalidation, I agree that browser-guaranteed purging mechanisms (like sessionStorage) would be ideal, though if ARM is running on load for most pages anyway, it would have ample opportunity to clean up after itself.
RE: using sessionStorage, whilst it's not impossible to share KVs across tabs, it might well be more complexity than is warranted. I suppose IndexedDB is an option… puxlit (talk) 18:40, February 22, 2019 (UTC)
user that going through bunch of lang wikis is not so rare case: when one renaming page with interlang links on it, the one might want to go to all these langs in order to update links. there is might be solution: time-based cache as shared parent for lang-based one. smth like: armcache: {expire: time, langs: {lang1: cache1, lang2: cache2}} when expire reached, kill all {lang}. sure, it will force to update module data on all lang-specific wikis, but we agreed that will be relatively rare case. it will solve ls overloading issue, effectively keeping data for only 1-few recently visited langs Fngplg (talk) 19:03, February 22, 2019 (UTC)
Mmm I'm partial to this approach. puxlit (talk) 20:08, February 22, 2019 (UTC)
Alternatively, we could just use the Cache API and forgo caching for browsers that don't support it. puxlit (talk) 11:49, February 23, 2019 (UTC)
according to mdn, the only difference would be autopurging by browser, when cache is full. in trade for more complex code Fngplg (talk) 11:58, February 23, 2019 (UTC)


It is a bit more annoying without async/await, but it's still tolerable. Here are some rough patches against revision 112686.

--- <>
+++ [patch for client-side caching with localStorage]
@@ -77,7 +77,64 @@
     function getPages(context, pagesToMaxAge) {
-        return fetchPages(context, Object.keys(pagesToMaxAge));
+        var deferred = $.Deferred(),
+            cache = null,
+            cacheIsDirty = false,
+            pagesToHTML = {},
+            pagesToFetch = [];
+        try {
+            cache = JSON.parse(localStorage.getItem('AddRailModule-cache'));
+        } catch (e) {}
+        if (!((typeof cache === 'object') && (cache.version === 0) && (typeof cache.entries === 'object'))) {
+            cache = {version: 0, entries: {}};
+            cacheIsDirty = true;
+        }
+        // Purge stale or corrupt entries
+        $.each(cache.entries, function (url, response) {
+            if (!((typeof response.expires === 'number') && (response.expires > && (typeof response.html === 'string'))) {
+                delete cache.entries[url];
+                cacheIsDirty = true;
+            }
+        });
+        // Populate results from cache
+        Object.keys(pagesToMaxAge).forEach(function (page) {
+            var url = mw.util.getUrl(page);
+            if (cache.entries.hasOwnProperty(url)) {
+                pagesToHTML[page] = cache.entries[url].html;
+            } else {
+                pagesToFetch.push(page);
+            }
+        });
+        if (pagesToFetch.length === 0) {
+            deferred.resolve(pagesToHTML);
+        } else {
+            // Fetch cache misses
+            fetchPages(context, pagesToFetch).done(function (fetchedPagesToHTML) {
+                $.each(fetchedPagesToHTML, function (page, html) {
+                    pagesToHTML[page] = html;
+                    if (pagesToMaxAge[page] > 0) {
+                        cache.entries[mw.util.getUrl(page)] = {
+                            expires: + (pagesToMaxAge[page] * 1000),
+                            html: html,
+                        };
+                        cacheIsDirty = true;
+                    }
+                });
+                deferred.resolve(pagesToHTML);
+            }).fail(function () {
+                deferred.reject();
+            });
+        }
+        return deferred.always(function () {
+            if (cacheIsDirty) {
+                localStorage.setItem('AddRailModule-cache', JSON.stringify(cache));
+            }
+        });
     function prepareMods(context, mods) {
--- <>
+++ [patch for client-side caching with Cache API]
@@ -77,7 +77,76 @@
     function getPages(context, pagesToMaxAge) {
-        return fetchPages(context, Object.keys(pagesToMaxAge));
+        if (!('caches' in window)) {
+            return fetchPages(context, Object.keys(pagesToMaxAge));
+        }
+        var deferred = $.Deferred();
+'AddRailModule-v0').then(function (cache) {
+            var pagesToHTML = {},
+                pagesToFetch = [];
+            function purgeCache() {
+                // Purge stale or invalid entries
+                return cache.keys().then(function (requests) {
+                    return Promise.all( (request) {
+                        if (!request.url.startsWith(window.location.origin)) {
+                            return cache.delete(request);
+                        }
+                        return cache.match(request).then(function (response) {
+                            var expires;
+                            if (response) {
+                                expires = Date.parse(response.headers.get('Expires'));
+                                if (!(response.ok && (expires > {
+                                    return cache.delete(request);
+                                }
+                            }
+                        });
+                    }));
+                });
+            }
+            function fetchFromCache() {
+                // Populate results from cache
+                return Promise.all(Object.keys(pagesToMaxAge).map(function (page) {
+                    return cache.match(new Request(mw.util.getUrl(page))).then(function (response) {
+                        if (response) {
+                            return response.text().then(function (html) {
+                                pagesToHTML[page] = html;
+                            });
+                        } else {
+                            pagesToFetch.push(page);
+                        }
+                    });
+                }));
+            }
+            function fetchMissingFromOriginAndResolve() {
+                if (pagesToFetch.length === 0) {
+                    deferred.resolve(pagesToHTML);
+                } else {
+                    // Fetch cache misses
+                    fetchPages(context, pagesToFetch).done(function (fetchedPagesToHTML) {
+                        $.each(fetchedPagesToHTML, function (page, html) {
+                            pagesToHTML[page] = html;
+                            if (pagesToMaxAge[page] > 0) {
+                                cache.put(new Request(mw.util.getUrl(page)), new Response(html, {headers: {
+                                    'Expires': (new Date( + (pagesToMaxAge[page] * 1000))).toUTCString(),
+                                }}));
+                            }
+                        });
+                        deferred.resolve(pagesToHTML);
+                    }).fail(function () {
+                        deferred.reject();
+                    });
+                }
+            }
+            purgeCache().then(fetchFromCache).then(fetchMissingFromOriginAndResolve);
+        });
+        return deferred;
     function prepareMods(context, mods) {

puxlit (talk) 03:38, February 24, 2019 (UTC)

it seems like .cache version was reimplemented via localstorage. ls part will be much simplier, if u will not try to mimic .cache: just store ls.cache as .setItem(, {expires: date, data-lang:o}) where data-lang is data-en, data-ru etc, o is {template1: data, template2: data}, data is parsed data, that might be injected directly to the dom. then check ls.cache for .expires. note: it will not preserve data-fr and data-ru on en wiki, cuz u just don't need it. it just stores all caches for some period. Fngplg (talk) 09:29, February 24, 2019 (UTC)
I actually started with the localStorage implementation, then translated it to use the Cache API. I'm not a fan of using one expiry timestamp to invalidate the whole cache, as maxAge is configurable per-module, there's no compelling reason to flush the cache every time a user switches language wikis, iteration cost is likely to be small, and honestly the invalidation logic is not complex. puxlit (talk) 10:07, February 24, 2019 (UTC) something simple. do u relly need per-module cache? anyway, all of them will be loaded simultaneously. Fngplg (talk) 10:43, February 24, 2019 (UTC)
Changes implemented in Special:Diff/112227/113211. I'd originally planned to further document the changes prior to review, but somebody submitted the changes around 2019-04-09T16:55Z. This script was added to Kocka's GDPR audit in Special:Diff/117254. puxlit (talk) 05:27, May 30, 2019 (UTC)

Proposal to special-case handling of modules containing AjaxPolls

Recently, Zurgat noted that rail modules containing AjaxPolls no longer appeared to work. The primary root cause appears to be a change on my part: to batch the request for multiple rail modules together, the HTML returned by the API now goes through a $.parseHTML(…), which has the effect of stripping out the script tags that AjaxPoll inserts.

Since the client-side JS does a single-shot init (and doesn't listen on something sensible like wikipage.content), preserving these script tags will still lead to a race, especially on pages with AjaxPolls.

To mitigate these effects, I'm proposing for ARM to: (i) identify rail modules containing AjaxPolls and disable client-side caching; and (ii) execute the following workaround.

mw.hook('wikipage.content').add(function ($elem) {
    var $polls = $elem.filter('section.railModule.rail-module').find('.ajax-poll>form');
    if (!$polls.exists()) {
    if (!window.AjaxPoll) {
        // AjaxPoll has not yet loaded.
        // Loading `ext.wikia.ajaxpoll` only gets us the CSS, and not the JS; see <>.
        // Instead, we'll use JSSnippets; see <>.
            dependencies: [
            callback: function () {
            id: 'AjaxPoll.init',
    } else if (window.AjaxPoll.initialized) {
        // AjaxPoll has already loaded, but we're not sure if _our_ polls have been wired up.
        // We'll manually wire ourselves up if we think it's necessary; see <>.
        $polls.each(function () {
            var events = $._data(this, 'events');
            if (!events || !events.submit) {

puxlit (talk) 08:46, May 30, 2019 (UTC)

Per discussion, resolved by preserving script tags; no special-casing required.

YouTube Subscribe Button, Facebook Activity, Twitter Activity And RSS Feeds Not Supported In AddRailModule's Template


In regards to this awesome template and extension feature, I've came across a few issues while using this feature on my own wiki.

YouTube Subscribe Buttons (added as a template into the template of RailModule), Twitter Activity (added as a template into the template of RailModule), Facebook Activity (added as a template into the template of RailModule) and RSS Feeds (added as a template into the template of RailModule) are not supported in the template of RailModule even if you purge the templates and purge the template of RailModule afterwards, wait 1 hour and then wait 24 hours, the issue still persists. Apart from that issue, the rest of the template and extension works and functions correctly from multiple various browsers with each browser being fully updated.

  1. Followed installation instructions found at AddRailModule.
  2. Configured and installed the extension and template as per the instructions.
  3. Implemented AddRailModule to MediaWiki:ImportJS on my own wiki.
  4. Created various templates for the sidebar. Added templates into the template page on my own wiki at Template:RailModule.
  5. Purged all template pages added to the RailModule template before saving and purging the RailModule template.
  6. Checked the template out on various browsers (browsers updated before checking).

Why doesn't the template not support YouTube Subscribe Buttons, Facebook Activity, Twitter Activity and RSS Feeds? I've tried the codes without them being templates and tried them as templates, the issue still persists.

Any suggestions on how to fix this issue is most appreciated.

Warren Woodhouse | Blog | Talk | Videos | Quote: "It's a funny thing, ambition. It can take one to sublime heights or harrowing depths. And sometimes they are one and the same." - Quote By: Emily Kaldwin (Dishonored, video game) 14:21, November 2, 2019 (UTC)

Hey there, we've made changes to AddRailModule and YouTubeButton that, once approved, should address the issues you've been having. However, I strongly urge you to cut down on the inordinate amount of clutter you're putting in your rail. puxlit (talk) 13:52, November 4, 2019 (UTC)
Community content is available under CC-BY-SA unless otherwise noted.

Fandom may earn an affiliate commission on sales made from links on this page.

Stream the best stories.

Fandom may earn an affiliate commission on sales made from links on this page.

Get Disney+