[Userscript] Burn Progress

:warning: This is a third-party script/app and is not created by the WaniKani team. By using this, you understand that it can stop working at any time or be discontinued indefinitely.

This is the Burn Progress userscript version 1.2.

The script adds a gold-colored progress bar at the very top of the dashboard showing your overall progress toward memorizing items with Wanikani:

The gold bar at top indicates I’ve burned 37% of the 8,995 currently published items, and have “seen” (burned or am still in the process of reviewing) 63%.

I’m currently at level 36 of 60 (or 60% “done”) which is pretty close to the “seen” value shown above, but I prefer to think of myself as only 37% “done” since I’ve only burned about a third of the total items on Wanikani.

I borrowed most of the actual logic from @Pep95 's Total Progress Bar userscript, but I tried to simplify the display significantly (exposing fewer details and using the default WK styling).


This is my very first userscript and although at least a few users have reported success, it’s still possible that I’ve screwed something up.

While I can’t guarantee that this script won’t cause your computer to explode, I’m reasonably confident that most users won’t experience any problems.

Please do let me know if you do run into any issues by adding a comment to this thread. I’ll do my best to resolve them promptly.


Installation Instructions

  1. Install Tampermonkey or your preferred script manager (installation instructions in this thread )

  2. Install the Wanikani Open Framework user script. This must load before Burn Progress!

  3. Browse to the Burn Progress script on greasyfork then click the “Install this script” link. You’ll have a chance to review the script before truly installing.

  4. As always, review the script to ensure it doesn’t do anything evil. At a minimum, ensure the @include and/or @match lines only match URLs within the wanikani.com domain. This script only requires get (read) permissions (@grant none).


Why I wrote this script

I wanted a visually simple “overall progress” bar on my dashboard.

To me, “overall progress” should answer the question: “How many of the items on Wanikani have I successfully memorized?”.

The engineering motto is: “If you can’t measure it you can’t improve it.” One of the most important corollaries is, “Since you improve what you measure, be careful to measure what you want.”

The best metric currently available that I know of is the ratio of burned items to total items.

Burning an item literally means I’ve answered a review correctly at least nine times, with a four-month gap before the final review. It’s not a perfect metric because I’ve almost certainly forgotten at least a few burned items, but it works very well for me.

Why not just use your “level” like normal people?

To me, leveling up just unlocks more unseen items, so focusing on levels seems like focusing more on getting into college rather than graduating!

I can’t help but feel that my current level is basically “internet points”: It has little value outside of Wanikani. Burned items, though, tell me how many of the roughly 9,000 or so kanji and vocabulary items on WK I’ve successfully memorized. That’s a pretty good proxy for how large a vocabulary I’ve built and how well I can now read Japanese.

Note that even burning every item on WK doesn’t indicate a huge degree of fluency in Japanese. There is still much more grammar and vocabulary to learn, and even “burning everything” provides no indication regarding speaking and understanding spoken Japanese.

In my professional life, I’ve also learned the importance of simplicity. The fact that the data is already available on my dashboard screen doesn’t diminish my desire at all. It’s the difference between data and information: A single prominent (if oversimplified) metric is often more valuable than a wall of supporting data.

Today, all of us use our “level” as the oversimplified metric for our overall progress. While there’s nothing wrong with that, I’d rather focus less on my level and more on my burns (final graduation exam results rather than getting into the next school year).

Personally, I feel “I’m on level 36 of 60” is less meaningful than “I’ve burned (‘learned’) 38% of the available items on WK.”

Design, implementation, and explaining the output

Design thoughts:

@Pep95 's Total Progress Bar userscript was very close to what I wanted. It already contained all the information I was interested in.

But I wanted something much simpler that also fit better with the (current) dashboard styling. Specifically, I wanted something that looked pretty close to the current level progress bar.

The KISS principle applied: I just wanted a simple bar graph with no dialogs to edit preferences or whatever. I also didn’t care to graph the percentages of all 9 stages. For the purposes of a progress bar, I just cared whether an item was burned (stage 9), in-progress (stages 1-8), or unseen (currently locked or stage0 lessons I’ve yet to review).

I wanted the script to be useful even for new users, however. It would be silly to only show “0 of 8995 Burned” for the first several months to new users. That’s why the bar shows a deeper gold color for burned items, and a lighter gold for the remaining seen but un-burned. Even beginners will soon have an appreciable number of “seen” items, even if they haven’t yet burned any.

In addition, users of the “free” subscription on levels 1 to 3 only have access to a much smaller number of study items. The script checks for this, though, and will display, e.g., “349 total free items: 28 seen” instead of “8995 total items: 5595 seen (3260 burned)” as is currently presented on my screen at level 36.

Burned is a strict subset of seen. That is: seen = burned + in-progress.

I’ve also strived to make the presentation of the progress bar responsive. It should render correctly for any reasonable display size. Please let me know if it doesn’t!

Lastly, it seems that using gold coloring rather than the standard dark gray to represent burned items is becoming a bit of a standard (likely due to @rfindley 's nifty Golden Burn userscript). He seems to know something about this scripting stuff. :slight_smile:


Known issues

For some reason, WK API + WKOF + @Pep95 's code is telling me I’ve currently burned 3260 items, but the default WK dashboard shows only 3258 items in that bucket. I’m not 100% sure I know why, but the most likely reason appears to be that the character (また) was moved from level 2 to level 51 after I’d already burned it.



I’m particularly grateful to @Pep95, @Saimin, @Kumirei, and @rfindley for helping me create this script.

I’m an only sporadic programmer and it took me a while to build up my chops again. I’d never have succeeded without “borrowing” code from my betters.

I’d also like to give a shoutout to all the absolutely astonishing “free software” tools involved in building this:

  • Visual Studio Code
  • Git
  • Github
  • Tampermonkey
  • Greasyfork
  • MDN
  • HTML/CSS/Javascript
  • Wanikani API
  • Wanikani Open Framework

The fact that professional tools like these are freely available never ceases to amaze me.

Finally, thanks to Koichi, the team, and the terrific community here for keeping me motivated to improve my Japanese!


Looks good to me, well done!

One very minuscule programmery suggestion: the burnsBar div could get a id="burnProgress" maybe, so that the div is identifiable.

Also, i feel like the bar takes a bit much space/padding, not sure if you can do something about it.

Compare to the Heatmap, 2 Cool 4 Progress, and Show Number of Learned Kanji scripts:

There are two things that I noticed regarding the config you use for loading the items from wkof:

  const config = {
    wk_items: {
      options: {
        review_statistics: true,
        assignments: true,
      filter: {
        level: "1..+0",
        srs: "1..8",
  1. You are requesting the review_statistics without ever using them.
  2. Your script only works because you have a typo in the config object: the keyword is filters, not filter. With your current version, the filters you specify are never actually applied.

I think this config object is what you actually want:

  const config = {
    wk_items: {
      options: {
        assignments: true,

Since you need the total number of WK items, you have to load all items unfiltered. If you want that seen or burnt items from future levels are not included in your progress bar, you could filter them yourself. If you don’t do that, things like the “bug” that you mentioned might happen:

At least my assumption is that the two missing burned items are the kanji and the vocabulary item for 又, which were both recently moved from level 2 to level 51.

EDIT: One more thing: "1..8" does not work with config.wk_items.filter.srs. This notation is only accepted by config.wk_items.filter.level. For srs, you have to write "1,2,3,4,5,6,7,8" (if you actually need this wkof filter at some point).


Thank you both for trying it out and providing feedback!

Sheesh. I blindly copied that over from the original script and never looked at it again. I spent the bulk of my effort just working on the styling of static values, then spent a total of about five minutes stealing @Pep95 's code to calculate the totals per stage. Since the script appeared to work, I didn’t even look at the config.

from Imgflip Meme Generator

Fixed in the current version.

Does the class of “burn-progress-container” not suffice to identify the div? I realize id must be unique while classes can be reapplied, but I’m unsure if an id is really warranted here. It’s extremely unlikely I’ll ever want to style anything else this way on the page, but my preference would be to leave it as a class regardless.

The relevant CSS is the margin line that applies 30px of bottom margin on the burn-progress-container div:

.burn-progress-container {
        box-sizing: border-box;
        margin: 0 0 30px;
        padding: 12px;
        text-align: right;

The order of the numbers is top, right, bottom, [left]. Since the left isn’t currently specified, it takes the 0 value from the right.

Please edit the margin line your copy and let me know what values look best to you.

I’m glad to see it looks okay with a dark theme, and with very few unseen items. I hadn’t tested with themes.

I’d love it if someone at the other end of the spectrum were to try it as well. If anyone reading this is on an early level and hasn’t yet burned any items, I’d greatly appreciate your trying out the script.


Since I’m fortunate enough to have script developers on this thread, I thought I’d ask:

What are the current best practices for developing and debugging Wanikani (and WKOF) userscripts?

Automated unit and integration tests are probably overkill, but what’s the best way to push your code from vscode to tampermonkey and refresh the page? I’m embarassed to admit that I was just copy/pasting into the tampermonkey edit window.

Ah, it does. I always use document.getElementById(), which is a bit more handy, but document.getElementsByClassName() works too.

0px. It seems like you don’t need a margin at all, even WK’s own elements don’t have any. Maybe it’s handled by other parts of the layout.
Here’s a screenshot with 0px and the computed margins (0px) of WK’s lessons and reviews section:

i also just copy pasted into tampermonkey, but i also only made a single simple script.

1 Like

Done. Version 0.4 now has 0 margin all the way around the container.


1 Like

Try document.querySelect() and document.querySelectAll() and you’ll never go back.

1 Like

ah, the querySelector* methods are nice, though it seems like getElementById is faster:

Original comment with flawed benchmark

(the first of the live benchmarks at the bottom of the page still works)

And getElementsByClassName seems to be even faster than by id, somehow.

It makes sense that id lookup is faster because of indexing/hashmaps.
querySelector actually searches the DOM tree from top to bottom depth first.

Though for a relatively small DOM tree like Wanikani’s and single operations i guess it’s not that much of a difference.

That’s probably because getElementsByClassName() returns a live HTMLCollection, which is basically just the search criteria stored in an object. Only once you try to access the elements inside the HTMLCollection, the query is actually executed. Just calling getElementsByClassName() does not do any of the actual work. At least as far as I know.

1 Like

For me it looks like this:

1 Like

And for me, it looks like this:

@Venyasin’s screenshot shows that wkof only returns the items in the first three levels for free accounts, so the progress bar fills much faster. And the left corners of the seen-bar are not rounded.

I’m not even using a separate editor. I just directly write my code in the Tampermonkey editor.

1 Like

You’re right, the benchmark was flawed for getElementsByClassName! Here’s the proper result:

Adding [0] forces the HTMLCollection to evaluate, and shows the real speed.
Forked and saved the benchmark:

(reposting for better post order)

1 Like

Oooh. Two good catches.

I forgot that if there are no burns I need to round the left edge of the “seen” bar. That’s easy to fix.

I’m unsure of the best way to fix the free account issue, though. I think the best approach might just be to expose an additional message if a user is only seeing a limited number of unseen items. But this begs a few questions:

  1. How should I detect this condition? Should I check for a level <= 3? Or should I use a heuristic like, say, totalItems < 5000?

  2. What should the message say? “Total items are reduced for free accounts”?


[Also, “venyasin” made me smile! I should have thought to just create a free account.]

  1. You can check if options["Subscription Status"] === "free"

  2. Maybe it is sufficient to just change the displayed text below the progress bar to “349 total free items” :thinking:

What are you talking about? I definitely don’t know them :innocent:


The chief architect at one of my prior companies liked to say that “Early optimization is the root of all evil”.

Personally, I think the thought is mostly true for performance optimization but far less true for quality and security — they aren’t things you want to add after the fact!


I like both of these. Implementing as soon as I finish my reviews. :slight_smile:

1 Like

I agree, i was just thinking in principle and trying to figure out more generally what’s faster. If I know getElements is faster than getElementsByClassName, i’ll try to use it whenever possible over the other.

1 Like

Okay. Both are now implemented. That @Venyasin person seemed helpful and friendly: I wonder if we could persuade them to post a new screenshot?

I’m confident enough with this now that I bumped the version to 1.0. I’ll update the original post accordingly.

Thanks for all the excellent feedback @Saimin, @Sinyaven, and @Venyasin!

One question: where is the options global variable documented? I’m glad I didn’t have to query the User object to get the subscription level. Very handy.


Seems to work with version 1.0:

1 Like