To write user scripts, you will have to learn at least some of the programming language that they are written in:JavaScript.
Try these links:
Also, it would definitely help if you tried usingone of our scripts and got it working. The rest of this tutorial assumes you know where the various things are (all explained atWikipedia:User scripts § How do you install user scripts?).
Starting out, it may be easier to modify an existing script to do what you want, rather than create a new script from scratch. This is called "forking". To do this, copy the script to a subpage, ending in ".js",[n. 1] of your user page. Then,install the new page like a normal user script.
Although you can write a script directly in yourcommon.js page orskin.js (such as vector.js) page, it is usually better to create a new subpage for it in the formYourUserName/title.js, wheretitle is the name of your script. That keeps your main js pages from getting cluttered and is helpful when you have multiple scripts installed. You will also want toinstall the new user script.
To make aHello world program, insert the following code into yourUser:YourUserName/common.js file:
importScript('User:YourUserName/hello-world.js');
Next, create the pageUser:YourUserName/hello-world.js, and insert this code:
$('#bodyContent').prepend('<p>Hello world!</p>');
This will write "Hello world!" on every page, below the title, until you remove the code. User scripts are written inJavaScript, and both of the above code snippets are in JavaScript. The second snippet usesJQuery, a JavaScript library that specializes in manipulatingHTML.$ is a JQueryfunction that lets us target theHTML element we want.#bodyContent is a string inCSS selector syntax, and means target the HTML element with propertyid="bodyContent" .prepend is a JQuery function that inserts HTML code as a child of the#bodyContent element.<p>Hello world!</p> is the HTML code to be inserted.
We will be writing a user script by modifying your common.js. For the purpose of this tutorial, we will write a simple version of theQuick wikify module, which adds the{{Wikify}} maintenance template to the top of an article when you click a link called "Wikify" in the "More" menu. To begin, changeMODULE_NAME in the module template to "Qwikify". Your template should look like this:
// Qwikify$(document).ready(function(){MODULE_CODE;});
InMODULE_CODE, we want to add the "Wikify" tab, so we will use theaddPortletLink() function (requiring themediawiki.util module). ReplaceMODULE_CODE with a call to this function. Then we will bind an event handler so that when this link is clicked, we will call another function nameddoQwikify() that will actually execute the code. Thename is what is shown on the tab, so set that to'Wikify'. Most tabs have an ID ofca-name, so set the ID to'ca-wikify'. The title (also known asmouseover orrollover text) should be something like'Mark for wikification'.
Lastly, we use jQuery's.click() to listen for clicks on this link, and when that happens, execute a function. After we calldoQwikify(), it saysevent.preventDefault(). Since we clicked on a link, we need to tell the browser to prevent its default behavior (going to the URL,'#'). We want the page to stay right where it is at, so to prevent the browser from following the link, we prevent that and do our own custom action.
Altogether, your new function should look like this:
// Make sure the utilities module is loaded (will only load if not already)mw.loader.using('mediawiki.util',function(){// Wait for the page to be parsed$(document).ready(function(){// See the "Portlets (menus and tabs)" subsection belowvarlink=mw.util.addPortletLink('p-cactions','#','Wikify','ca-wikify','Mark for wikification');$(link).click(function(event){event.preventDefault();doQwikify();});});});
Now, we must write our actualdoQwikify() function. It will edit the edit box, so we need to get the name of that and its form. Viewing the source of the page shows that the form is namededitform and the textbox is namedwpTextbox1, meaning that the actual text isdocument.editform.wpTextbox1.value. To add{{wikify}} (and two new lines), we simply do:
document.editform.wpTextbox1.value="{"+"{wikify}}\n\n"+document.editform.wpTextbox1.value;
(We separate the two "{" brackets in the front of the wikify template so it doesn't get expanded when we write this code on the wiki.)
Finally, we want to submit the form for the user. Luckily, JavaScript has a built-in function just for this namedsubmit(). To submit our editing form, usedocument.editform.submit(). Your code should now look something like this:
functiondoQwikify(){document.editform.wpTextbox1.value="{"+"{wikify}}\n\n"+document.editform.wpTextbox1.value;document.editform.submit();}
And that's it! Combine it all together and it should look like this:
// Make sure the utilities module is loaded (will only load if not already)mw.loader.using('mediawiki.util',function(){// Wait for the page to be parsed$(document).ready(function(){// See the "Portlets (menus and tabs)" subsection belowvarlink=mw.util.addPortletLink('p-cactions','#','Wikify','ca-wikify','Mark for wikification');$(link).click(function(event){event.preventDefault();doQwikify();});});});functiondoQwikify(){document.editform.wpTextbox1.value="{"+"{wikify}}\n\n"+document.editform.wpTextbox1.value;document.editform.submit();}
Save this to yourUser:YourUserName/common.js page. Then go visit a page such as theSandbox, go into the "More" menu, click "Wikify", and watch the user script add the maintenance tag for you.
All Wikipedia pages include some built-inMediaWiki JavaScript code, with variables and functions that can be used in user scripts. Some of them were already mentioned ($(),importScript(),mw.util). This code is generally loaded asResourceLoader modules (some of it preloaded, some loaded on demand) and ends up in properties of these globally available objects:
Some commonly accessed properties ofmw includemw.config,mw.user.options,mw.util,mw.Title,mw.loader, andmw.hook.OO.ui is the namespace ofOOUI. Seemw:ResourceLoader/Core modules for more details.
The followingdevelopment environments can be used to develop and test your script.
The best and most recommended way to load a JavaScript file during development is from your local web server (see below for an easy way to install a web server). Put this string in your/common.js:
mw.loader.load('https://localhost/wikipediatest.js');
In some environments, you need to write this as:[1]
mw.loader.load('http://127.0.0.1/wikipediatest.js');
Then run anyweb server on your computer and create thewikipediatest.js file in the appropriate folder. The code inside this file will be executed as if it was inside your personal script.
You can edit yourwikipediatest.js file with any text editor, perhaps with syntax highlighting and other convenient features, save the file and simply reload any Wikipedia page to see the results. (You do not need to wait, and if your web server is nice or you set it right, you do not even need tobypass your browser cache.)
Most modern code editors and IDEs allow you to set up a localhost server – eg. useatom-live-server inAtom, andLive Server inVS Code.WebStorm andPhpStorm have the feature built in, without requiring an extension. You can also use a third party program such asNode.js'snpx http-server command (video tutorial), orXAMPP.
If you havePython installed, you can runpython -m http.server from command-line from the folder your script is in.
On Windows, you could also use for exampleTinyWeb, less than 100 kbyte on disk and not requiring installation. Save and unziptinyweb.zip for example intoc:\Program Files\Tinyweb, create a shortcut totiny.exe, and add an argument in shortcut properties — path to your folder withwikipediatest.js and any fileindex.html (required). Start TinyWeb with this shortcut; unload it with Task Manager.
Note that this method doesn't work inOpera 9.50 (and later) due to added security restrictions, seeOpera 9.50 for Windows changelog: "Local servers can use remote resources, but not vice versa". InChrome, it may be necessary toenable SSL, otherwise the script will refuse to load.
Some browsers allow you to automatically execute your JavaScript code on specific web pages. This way you do not have to be logged in to Wikipedia. One example isTampermonkey. However, making user scripts work with one of these extensions might require some modifications to the script code.
You can run pieces of code on already loaded pages via the JavaScript console. See theguide for doing this in Chrome. It works similarly in most other browsers. In addition,Chromium-based browsers have asnippets feature where short pieces of JavaScript code can be saved and debugged.
Once you have finished the user script code, you can save it as a page so that others can import it. By convention, scripts are in your userspace and have titles ending in ".js",[n. 1] for example "User:YourUsernameHere/MyCoolScript.js". Others can theninstall the new script.
You can use anything from a simpletext editor, to a more feature-packedcode editor orIDE. Here are some recommended editors, by operating system.
These are typically built into browsers, in their DevTools window. Debuggers allow you to step debug (go through your JavaScript code line-by-line, hover over variables to see their values, etc.)
The personaluser module (built from /common.js, /common.css and optionally the skin-specific files for the current skin; seeabove) andgadgets are loaded on all pages. Most scripts will want to manipulate elements on the page; to do so the page needs to be ready (it may not be the case at the time the modules are loaded). We can defer execution of code by using a special function.
$(document).ready(...)One option is.ready() fromjQuery.
// Define our main functionfunctionmyScript(){// ... code ...};// Schedule it to run after the HTML page is parsed$(document).ready(myScript);// This shorthand is also validjQuery(myScript);
Since the function is called only once, many users prefer to shorten this code with ananonymous function:
$(document).ready(function(){// ... code ...});// OrjQuery(function(){// ... code ...});
Note:$ andjQuery are the same object; choosing between them is purely a matter of opinion.
Many scripts use this function simply to add some script interface, such as a link in a portlet. Then the main part of the code is executed after the user clicks on that link.
mw.hook('wikipage.content').add(...)However, if your code works with the content part of the page (the#mw-content-text element), you should use the'wikipage.content'hook instead. This way your code will successfully reprocess the page when it is updated asynchronously and the hook is fired again. There are plenty of tools that do so, ranging from edit preview to watchlist autoupdate.
Be sure to only work with the descendants of the$content element that your handler function takes and not the whole page. Otherwise, you may end up running the same code for the same elements many times. Note that the'wikipage.content' hook may be firedreally many times.
Be cautious about what comes in the$content argument of the handler function. You should not assume it is the#mw-content-text element. It can be a small portion of the page, e.g. when it is previewed.
Code that works with page content and avoids the aforementioned pitfalls may look like this:
mw.hook('wikipage.content').add(function($content){const$target=$content.find('.targetClass');if($target.length){// Do things with $target}// Only perform some operations when it is #mw-content-text in the argumentif($content.is('#mw-content-text')){const$note=$('<div>').addClass('myScript-note').text('MyScript has successfully processed the content!');$content.prepend($note);}});
If your code works with page content and adds event handlers to DOM elements, then, instead of hooking to'wikipage.content' and looking for elements to attach event listeners to when it is fired, you may attach one event listener to an element outside of the content area or the wholedocument but filter events by a selector (seejQuery's documentation). That is, instead of writing$content.find('.targetClass').on('click',...) you can write$(document).on('click','.targetClass',...).
EveryHTML element is a node in aDOM model allowing scripts to access the element, for example, on the following HTML page.
<formname="frmname"id="frmid"><textareaname="txtname"id="txtid"></textarea><inputid="neighbor"/></form>
We can find elementtextarea:
id:$('#txtid')tag:$('textarea')$('#neighbor').prev()$('#frmid').children('form')name:$('#frmid [name="txtname"]')ThejQuery API reference is an excellent source for documentation.
Many scripts are supposed to work only on some pages. You can check:
if(mw.config.get('wgAction')==='history'){// Continue only on history pages.
if(mw.config.get('wgCanonicalNamespace')==='User_talk'){// Continue only on User_talk pages.
if(mw.config.get('wgPageName')==='Article_name'){// Continue only for the article "Article name".
functionfunc_start(){if($('#editForm').length==0)return;//No edit form ? exit// …
Portlets are MediaWiki's name for groups of links located in the topbar and sidebar. Here is a diagram of portlet ID's.

<divid="p-myname"class="portlet"><h5>Header</h5><divclass="body"><ul><liid="..."><a...> //Links<liid="..."><a...> ...
There is a special function inmediawiki.util,mw.util.addPortletLink() that simplifies the process of adding your own links to portlets. The advantage of using this function is that your code should work across all skins, and not break when these skins change their HTML. Its parameters, in order:
portletId – ID of the targetportlethref – link URL'#' if you do not need to open a page and want to use a JavaScript listener instead.text – human-readable link textid (optional) – unique ID of the itemtooltip (optional) – helpful text appearing on mouse hoveraccesskey (optional) –keyboard shortcut keynull if you do not need it.$( '[accesskey=x]' ) in the console to see if 'x' is already used.nextNode (optional) – element that this will be added in front of// Several examples of portlet links// Adds a link to your js file to the toolbox. tb = toolboxmw.util.addPortletLink('p-tb',mw.util.getUrl('Special:MyPage/common.js'),'My JS','pt-myvector','Visit your js file');// Add a link to the edit page for your Notes in your personal links// Note: We assume that short/pretty URLs are in use with ?action, ideally you would check for that.mw.util.addPortletLink('p-personal',mw.util.getUrl('Special:MyPage/Notes')+'?action=edit','My notes','pt-mynotes','Edit your personal notes');// Adds a link to prefix index for the current page to the toolboxmw.util.addPortletLink('p-tb',mw.util.getUrl('Special:Prefixindex/'+mw.config.get('wgPageName')),'Prefixindex','tb-prefixindex');// Adds a link to logs for your accountmw.util.addPortletLink('p-personal',mw.util.getUrl('Special:Log/'+mw.config.get('wgUserName')),'My logs','pt-mylogs');
Or you can use JQuery. Simply attach it in another place with.append(),.prepend(),.before(), or.after().[1][2]. Warning: This is fragile. You may get it working on a couple skins, but a couple other skins may look broken.
// Add a clickable button on the edit article page, above the edit summary.$('.editOptions').prepend('<button type="button">Do Things</button>');// Add a listener to your button, that does something when it is clicked.$('#my-custom-button').click(function(e){// do things});
To hide an element, you can use JQuery's.hide() function.
// Example: remove special characters toolbar from edit page$('#editpage-specialchars').hide();// Or modify the CSS directly$('#editpage-specialchars').css('display','none');
Or you can do it by placing code incommon.css:
#editpage-specialchars{display:none;}
You can add menus usingmw.util.addPortlet() (seedocumentation). The menu will not show up until you put a portletLink in it. If you add a menu adjacent to #p-cactions, it will be a dropdown menu in the Vector and Vector 2022 skins, with the correct dropdown HTML added for you.
mw.util.addPortlet('p-twinkle','TW','#p-cactions');mw.util.addPortletLink('p-twinkle','#','Tag');mw.util.addPortletLink('p-twinkle','#','CSD');
The most important element on the edit page is a<textarea> with the article text inside. You can reference it with
var$textbox=$('#wpTextbox1');
You can manipulate it using thejquery.textSelection ResourceLoader module.
var$textbox=$('#wpTextbox1');$textbox.textSelection('setContents','This is bold!');$textbox.textSelection('setSelection',{start:8,end:12});$textbox.textSelection('encapsulateSelection',{pre:'<b>',post:'</b>'});// Result: Textbox contains 'This is <b>bold</b>!', with cursor before the '!'
Or you can grab<textbox>'s text, create astring, modify it, then write it back. Note; other editing tools might not recognise your changes or cause conflicts if you use this methodology instead of the textSelection api.
// Get value.letvalue=$('#wpTextbox1').val();// Your code goes here. Do things to value. RegEx, .replace(), concatenate, etc.// Then write it back.$('#wpTextbox1').val(value);
WikiEditor is now the default toolbar when editing the source code of articles, but some users are still using the original toolbar. You can turn on and off WikiEditor by checking and unchecking the "Enable the editing toolbar" check box inyour preferences.[n. 2][n. 3]
There is another edit panel under textarea. Usually it is generated fromMediaWiki:Edittools byExtension:CharInsert and consists of a lot of JavaScript links. In the English Wikipedia, this approach was replaced byMediaWiki:Gadget-charinsert.js andMediaWiki:Gadget-charinsert-core.js.
You can automate the editing of a page using the following code template:
mw.loader.using("mediawiki.user",()=>{$.post(mw.config.get('wgScriptPath')+'/api.php',{action:'edit',title:"[Page title]",text:"[Text]",summary:"[Edit summary]",token:mw.user.tokens.get('csrfToken'),// This is the user token required to authorize the edit.format:'json'}).then(function(r){if(r.error){mw.notify(r.error.info,{type:'error',title:'Error while trying to edit'});// Sends an error message if unable to edit the page.}});});
Sometimes you may want to add or remove something from the DOM, but another user script edits the same area of the DOM. It can be random which user script finishes first, creating arace condition.
One way to coordinate this is use themw.hook interface. Perhaps the other script sends awikipage.content event when it is done, or can be modified to do so (or you can ask the maintainer).
Another way to avoid this is to use aMutationObserver.
If you want your users to be able to manually set configuration variables, one way to do this is to have them placewindow.scriptNameSettingName = 'value here'; in their common.js file. Then within your user script, you can read this value withif ( window.scriptNameSettingName == 'value here' ).
Notice that "scriptName" is one of the pieces of the variable name. This is important to help make sure the variable is unique.
Do not uselet scriptNameSettingName = 'value here'; in the common.js file. If the user forgets the setting, you may get undeclared variable errors.
If you want your user script to write and save configuration settings as it is running, you may want to have it write to its own .js file in the user's userspace. Seetwinkleoptions.js orredwarnConfig.js for examples.
You may want to place the following code at the top and bottom of your user script, in a comment. This will help prevent bugs, such as~~~~ turning into your hard-coded signature when you save the page.
//<nowiki>Yourcodehere.//</nowiki>
If you need to print <nowiki> or </nowiki> tags within your user script, use a trick such asconst tag = '</' + 'nowiki>'; to keep from messing up the nowiki tag on line 1 and on the last line.
Do not declare named functions in the global namespace. For example, this is bad:
functionsubmitEdit(){/* do stuff */}$(function(){/* main code here */});
What if another of your user scripts also declares asubmitEdit() function? This can lead torace conditions and hard-to-trace bugs. Instead, use classes named after your script, or place all your functions inside of animmediately invoked function expression (IIFE) such as$(function {});. JavaScript allowsnested functions.
$(function(){functionsubmitEdit(){/* do stuff */}/* main code here */});
AJAX (asynchronous JavaScript and XML) is a popular name for a web programming technique that queries the server or fetches content without reloading the entire page. This is great for API requests. We do not have access to the SQL database in front end code, so theMediaWiki action API (or one of theother APIs) is the main way we retrieve data.
MediaWiki provides some modules with helper functions facilitating the use of its API. The main modules available are
If your script makes use any method or code provided by these modules, remember to indicate the dependencies withmw.loader.using or, in case of gadgets, on its definition atMediaWiki:Gadgets-definition.
This API has several advantages especially when dealing with POST requests. It provides automatic token refresh and retry, handles various error situations and does parameter request building for several common use cases like rolling back a revision.
Be sure to follow theuser agent policy by setting a user agent header (see code there). See alsomw:API:Etiquette.
mediawiki.apiNote: make sure to addmediawiki.api to your dependencies!
functiondoSomethingWithText(wikitext){/* .. */alert('The wikitext of the page is:\n\n'+wikitext);}functiondoSomethingInCaseOfError(){/* .. */console.log('err');}(newmw.Api()).get({prop:'revisions',rvprop:'content',rvlimit:1,indexpageids:true,titles:'Wikipedia:Sandbox'}).then(function(data){varq=data.query,id=q&&q.pageids&&q.pageids[0],pg=id&&q.pages&&q.pages[id],rv=pg&&pg.revisions;if(rv&&rv[0]&&rv[0]['*']){doSomethingWithText(rv[0]['*']);}}).catch(doSomethingInCaseOfError);
$.getJSON(mw.util.wikiScript('api'),{format:'json',action:'query',prop:'revisions',rvprop:'content',rvlimit:1,titles:'Wikipedia:Sandbox'}).then(function(data){varpage,wikitext;try{for(pageindata.query.pages){wikitext=data.query.pages[page].revisions[0]['*'];doSomethingWithText(wikitext);}}catch(e){doSomethingInCaseOfError();}}).catch(doSomethingInCaseOfError);
Fetching a page content can be done using jQuery$.ajax, which does an HTTPGET request.
$.ajax({url:mw.util.getUrl('Wikipedia:Sandbox')}).then(function(data){alert('The remote page contains:\n'+data);}).catch(function(){alert('The ajax request failed.');});
Scripts can perform common actions (like editing, protection, blocking, deletion, etc.) through theAPI. These actions require an edit token, valid for any action during the same session. (However, you should get a new token for different tasks in case this changes in the future.)
The code below shows how to edit a page, but it can easily be adapted to other actions by reading theAPI documentation.
mediawiki.apiNote: make sure to addmediawiki.api to your dependencies!
// Edit page via the mw.Api module.// postWithEditToken( {} ) may be used instead of postWithToken("csrf", {} )// for actions such as editing that require a CSRF token.// The line "text: info.text," will cause the call// to replace entire page content with supplied data.// alternatively, one can append or prepend the data to the page, by using// "appendtext: info.text," or "prependtext: info.text," instead.// when using "appendtext", it is possible to append the text to a specific section,// by setting the optional field "section".functioneditPage(info){varapi=newmw.Api();api.postWithToken("csrf",{action:'edit',title:info.title,text:info.text,// will replace entire page contentsummary:info.summary}).done(function(data){alert('Page edited!');}).fail(function(code,data){console.log(api.getErrorMessage(data).text());});}editPage({title:'User:'+mw.config.get('wgUserName')+'/Sandbox',text:'Cool! It works! :-) ~~'+'~~',summary:'Trying to edit my sandbox [[Project:User scripts/Guide|using API]]...'});
// Edit page (must be done through POST)// the line "text: info.text," will cause the call// to replace entire page content with supplied data.// alternatively, one can append or prepend the data to the page, by using// "appendtext: info.text," or "prependtext: info.text," instead.// when using "appendtext", it is possible to append the text to a specific section,// by setting the optional field "section".functioneditPage(info){$.ajax({url:mw.util.wikiScript('api'),type:'POST',dataType:'json',data:{format:'json',action:'edit',title:info.title,text:info.text,// will replace entire page contentsummary:info.summary,token:mw.user.tokens.get('csrfToken')}}).then(function(data){if(data&&data.edit&&data.edit.result&&data.edit.result=='Success'){alert('Page edited!');}else{alert('The edit query returned an error. =(');}}).catch(function(){alert('The ajax request failed.');});}editPage({title:'User:'+mw.config.get('wgUserName')+'/Sandbox',text:'Cool! It works! :-) ~~'+'~~',summary:'Trying to edit my sandbox [[Project:User scripts/Guide/Ajax|using AJAX]]...'});
Security warning: Do not load Wikipedia pages that do not end in .js into your script using this method, because anybody can edit those pages.
lettitle="User:YourName/YourScript.js";mw.loader.load("https://en.wikipedia.org/w/index.php?title="+title+"&action=raw&ctype=text/javascript");
JSON is useful when you want to import complex data into your script. For example, maybe you have a bot that publishes certain data to a Wiki page regularly, and you want your script to read that data.
Careful withctype. Set it toraw for normal Wiki pages, andapplication/json for pages where a template editor or admin has set theContent Model to JSON.
letjsonData;lettitle="User:YourName/YourData.json";$.getJSON(mw.config.get('wgScriptPath')+'/index.php?action=raw&ctype=application/json&title='+title,function(data){jsonData=data;});
Some user scripts also use some CSS code, or even are built with CSS only. Then you need to code and test CSS code. That can be done viaediting yourUser:YourUserName/common.css page.
For local development, you can load a CSS file from your local web server (see the previous section for an easy-to-install web server). Put this line at the top of your/common.css:
@import"http://localhost/wikipediatest.css";
Note! Such@import statements must come before any other declarations in your CSS, except comments.
An alternative way is to put this line in your/common.js instead:
mw.loader.load('http://localhost/wikipediatest.css','text/css');
Once you have finished the CSS code, you either need to paste it into your/vector.css if it is only for personal use. Or if it is for use by others then you should upload it to for instanceUser:Yourname/yourscript.css. Then other users can import it by putting the following line in their/common.js file. Note, that is in their ".js", not their ".css".
importStylesheet('User:Yourname/yourscript.css');
If the CSS should be used together with a user script written in JavaScript then you can make it easy for the users. Simply put the line above in the JavaScript code for your user script, then the users only need to "install" your JavaScript.
For completeness, in case someone wonders, users can import yourUser:Yourname/yourscript.css from their/common.css too. This of course has the advantage that it works even if the user has JavaScript disabled. Although it takes this slightly complex line of code:
@import"/w/index.php?title=User:Yourname/yourscript.css&action=raw&ctype=text/css";