Custom Scheduling Help

Hello. I want to set custom scheduling for all my decks. I want the buttons to schedule new and learning cards like this:
Again: 2m
Hard: 15m
Good: 1d
Easy: 5d

For cards I’m reviewing I’d probably want longer intervals but I’ll change that once I can figure out how to manually change these times first.

I also understand that selecting Good or Easy buttons will apply a multiplier to all of the buttons the next time the card appears. I want to be able to customize by how much it multiplies those times.

Currently, for a card that I’m “learning”, the times are:
Again: 10m
Hard: 15m
Good: 1d
Easy: 2d

For cards I’m “reviewing” and also for cards that are “new”, the times are:
Again: 1m
Hard: 6m
Good: 10m
Easy: 5d

I am sorta new to Anki and I have already done SO much research but am still super confused. I’d really appreciate the help :slight_smile:

I copied the code from a link in the Anki manual.

function is_new() {
  if (states.current.normal?.new !== undefined) {
    if (states.current.normal?.new !== null) {
      return true;
    }
  }
  if (states.current.filtered?.rescheduling?.originalState !== undefined) {
    if (Object.hasOwn(states.current.filtered?.rescheduling?.originalState, 'new')) {
      return true;
    }
  }
  return false;
}
function is_learning() {
  if (states.current.normal?.learning !== undefined) {
    if (states.current.normal?.learning !== null) {
      return true;
    }
  }
  if (states.current.filtered?.rescheduling?.originalState !== undefined) {
    if (Object.hasOwn(states.current.filtered?.rescheduling?.originalState, 'learning')) {
      return true;
    }
  }
  if (states.current.normal?.relearning !== undefined) {
    if (states.current.normal?.relearning !== null) {
      return true;
    }
  }
  if (states.current.filtered?.rescheduling?.originalState !== undefined) {
    if (Object.hasOwn(states.current.filtered?.rescheduling?.originalState, 'relearning')) {
      return true;
    }
  }
  return false;
}
function is_review() {
  if (states.current.normal?.review !== undefined) {
    if (states.current.normal?.review !== null) {
      return true;
    }
  }
  if (states.current.filtered?.rescheduling?.originalState !== undefined) {
    if (Object.hasOwn(states.current.filtered?.rescheduling?.originalState, 'review')) {
      return true;
    }
  }
  return false;
}


if (is_new()) {
  if (states.again.normal?.review) {
    states.again.normal.review.scheduledSecs = 2 * 60;
  }
  if (states.hard.normal?.review) {
    states.hard.normal.review.scheduledSecs = 12 * 60;
  }
  if (states.good.normal?.review) {
    states.good.normal.review.scheduledSecs = 12 * 60 * 60;
  }
  if (states.easy.normal?.review) {
    states.easy.normal.review.scheduledSecs = 5 * 24 * 60 * 60;
  }
  // For learning/relearning cards
} else if (is_learning()) {
  if (states.again.normal?.review) {
    states.again.normal.review.scheduledSecs = 2 * 60;
  }
  if (states.hard.normal?.review) {
    states.hard.normal.review.scheduledSecs = 12 * 60;
  }
  if (states.good.normal?.review) {
    states.good.normal.review.scheduledSecs = 12 * 60 * 60;
  }
  if (states.easy.normal?.review) {
    states.easy.normal.review.scheduledSecs = 5 * 24 * 60 * 60;
  }
  // For review cards
} else if (is_review()) {
  if (states.again.normal?.review) {
    states.again.normal.review.scheduledSecs = 2 * 60;
  }
  if (states.hard.normal?.review) {
    states.hard.normal.review.scheduledSecs = 12 * 60;
  }
  if (states.good.normal?.review) {
    states.good.normal.review.scheduledSecs = 12 * 60 * 60;
  }
  if (states.easy.normal?.review) {
    states.easy.normal.review.scheduledSecs = 5 * 24 * 60 * 60;
  }
}

Let me start by saying – I have only the barest idea how Custom Scheduling works, so I can’t help you with your code. I am responding instead to encourage you not to do this. [Which is obviously not the advice you asked for, so feel free to disregard all of this.]

You’re sorta new to Anki, so you might not be aware that the default scheduling algorithm (SM-2), with the default settings, right out of the box, is a solid workhorse that has been educating folks quite well for many years. It’s customizable in a lot (but not all) of the ways that you’re talking about.

You also might not be aware that the very high bar SM-2 had set for spaced-repetion algorithms has since been completely blasted through by FSRS! :sweat_smile: It’s even better than SM-2, and once personalized, it does better at scheduling for 98 or 99% of users (the stat keeps going up, so I’ve lost track). The smart money right now is on: just enable FSRS, optimize your parameters, and start studying.

If you do know about those, then I suppose you’re thinking: that might work for others, but won’t be right for you. I mean this genuinely, and with respect – what are you basing that on? :person_shrugging:t4: Even if you’re trying to mimic a system that has worked for you in the past, you’re overlooking the built-in systems that might be substantially better and/or more efficient at helping you learn.

So, if you want to know more about how to head in the built-in-scheduling-algorithm direction, just let me know. :+1:t4:

[It also looks like you’re trying to set up a system where the intervals on the answer buttons are always the same, regardless of how many times you’ve studied a card. (Again, I don’t know JS so I might not be understanding that correctly!) That seems like such a painful thing to do to Anki, which wants to help you space out your reviews and study less. :face_holding_back_tears: ]

1 Like

I’ll also second Danika’s post to consider what exactly you wanted to use Anki for and whether this scheme of yours makes sense.

But anyway, if you want to do custom scheduling, here’s a whole general how-to that ought to be useful for anyone wanting to do stuff with custom scheduling. Perhaps this could be its own forum thread… I haven’t tried modifying learning steps myself but I’m pretty sure what I describe for changing steps etc. is how it works.

Firstly

You can generally figure anything not mentioned here out by inspecting the states object. You’ll need the Webview Inspector addon to see the browser console in Anki. I had to double-check a lot of things too.

console.log(states);

How learning steps work

  • The new state occurs only on the first review. On the next review the state will be learning or review depending on the number of learning steps.
  • The base interval the you modify with custom scheduling is what is defined in the deck setting’s learning steps.

If you have at least 1 learning step

  • Answering Again at any learning step (including the first) resets the card
    to the first learning step. The card’s state will still be learning.
  • Answering Hard keeps you at the current learning step. The card’s state will still be learning
    • The interval shown for Hard is half of the next learning step, so half of what the interval shown for Good
    • If there is no next learning step (the current is last) the interval shown is 1.5 times the current step’s interval
  • Answering Good, the next state will be review if this is the last learning step, or learning if there is a next learning step.
  • Answering Easy at any learning step will skip all learning steps and the next card state will be review

If you have no learning steps

  • All buttons graduate the card to review state, the card’s state will never be learning because the state is new for the first review.

Relearning steps after answering Again

Exactly the same as above but replace learning with relearning

Some helper code

Answer button object you modify

It’s complicated…

This is different depending on whether you’re in a normal deck or a filtered deck + the card state + the answer button itself. And, if in a filtered deck, whether you’ve checked Reschedule cards based on my answers in the this deck and if so, which answer buttons return the card to the original deck…

For example, for the Good answer button:

  • states.good.normal
    1. You’re in a normal deck.
    2. Or, you’re in a filtered deck and answering this returns the card to the original deck

Filtered deck with Reschedule cards based on my answers in the this deck checked (the default):

  • states.good.filtered.rescheduling.originalState
    1. You’re in a filtered deck and answering this keeps the card in the filtered deck

Filtered deck with Reschedule cards based on my answers in the this deck unchecked:

  • states.good.filtered.preview containing { scheduledSecs: <number>, finished: true/false }

Filtered deck

When Reschedule cards based on my answers in the this deck is unchecked

  • learning and relearning steps don’t exist. Cards just stay in the filtered deck and repeat according to the intervals defined in the filtered deck settings.
  • You could make those intervals grow with custom scheduling!

Again

The object you modify to change what the Again button does for a review card changes depending on whether

  • you’re in a normal deck
  • in a filtered / custom study deck
  • and whether there are relearning steps or not

And then also whether the card is currently in learning or releaning.

// Card is currently review or there are no learning steps
const againRevObj = states.again.normal?.review // normal deck, no relearning steps
  || states.again.normal?.relearning?.review // normal deck, have relearning steps
  || states.again.filtered?.rescheduling?.originalState?.review // filtered deck, no relearning steps
  || states.again.filtered?.rescheduling?.originalState?.relearning.review // filtered deck, have relearning steps

// Card is currently in a learning step or new
const againLearnObj = states.again.normal?.learning // normal deck
  || states.again.filtered?.rescheduling?.originalState?.learning // filtered deck

// Card is currently in a relearning step, there are TWO objects to modify

// Review object, whose scheduledDays is what the eventual interval will be after
// the last relearning step. This is lowered a again if you answer again in relearning!
const againRelearnRevObj = states.again.normal?.relearning?.review // normal deck
  || states.again.filtered?.rescheduling?.originalState?.relearning?.review // filtered deck
// Learning object, since answering again returns the card to the first relearning step
// you have this. Modifying this can change what relearning step it goes or the relearning
// interval. Deleting this would make the card not go back to relearning but instead go
// back to being a review card!
const againRelearnLearnObj = states.again.normal?.relearning?.learning // normal deck
  || states.again.filtered?.rescheduling?.originalState?.relearning?.learning // filtered deck

Hard

const hardObj = states.hard.normal // normal deck
  || states.hard.filtered?.rescheduling?.originalState // filtered deck
// Card is currently review or there are no learning steps
const hardRevObj = hardObj.review

// Card is currently in a learning step or new
const hardLearnObj = hardObj.learning

// Card is currently in a relearning step: two objects
const hardRelearnRevObj = hardObj.relearning?.review
const hardRelearnLearObj = hardObj.relearning?.learning

Good

const goodObj = states.good.normal // normal deck
  || states.good.filtered?.rescheduling?.originalState // filtered deck
// Card is currently review, there are no learning steps, or this is the last learning step
const goodRevObj = goodObj.review

// Card is currently in a learning step and there is a next learning step
const goodLearnObj = goodObj.learning

Easy

const easyObj = states.easy.normal // normal deck
  || states.easy.filtered?.rescheduling?.originalState // filtered deck
// Easy answer always defaults to skipping learning/relearning steps so in all states
// you'll only have this
const easyRevObj = easyObj.review

Getting the current review object

For example, you want to

  • get the current easeFactor so you can modify the next easeFactor in <answer>RevObj.normal.review
const revObj = states.current.normal?.review
    || states.current.normal?.relearning?.review
    || states.current.filtered?.rescheduling?.originalState?.review
    || states.current.filtered?.rescheduling?.originalState?.relearning.review

const currentEaseFactor = revObj?.easeFactor;
const previousInterval = revObj?.scheduledDays;

Modifying learning steps

  • If you have <answer>LearnObj, answering this keeps the card in learning
  • If you have <answer>RevObj: answering this graduates the card to review

As per the above description of how learning steps work and the answer button object variables.

If you have learning steps, you’ll have

  • againLearnObj
  • hardLeardObj
  • goodLearnObj (if there is a next step)
  • or goodRevObj (if the current step is last)
  • and easyRevObj

With zero learning steps you’ll have

  • <answer>RevObj for all answers

Modifying interval or the next step

  • <answer>LearnObj.remainingSteps: which learning step is the next one. If you have 5 learning steps, setting this 5 means going back to the first step.
  • <answer>LearnObj.scheduledSecs: interval to wait, default value is defined by your learning steps definition in the deck settings and the behaviour described above for Hard

When answering gradutes the card to review

  • easyRevObj.scheduledDays: As described above, answering Easy graduates the card to review state. Review state doesn’t have scheduledSecs, they have scheduledDays. I think making intervals less than a day for review cards is not possible, the scheduler will always set a review card’s interval to 1 day.

Modifying relearning steps

Mostly same as above, replace learning with relearning. But as mentioned in the answer button objects, relearning steps can have TWO objects in them:

  • review: defines the eventual interval after the last relearning step
  • and learning: if there is a next step or you’re not advancing in steps (hard & easy)
    The learning object is omitted by default for the Easy answer relearning object.

Reminder on modifying interval

Remember that learning and relearning states have scheduledSecs and review states have scheduledDays. As mentioned, I’m not sure if it’s possible to make review intervals less than 1 day but you’ll find out by trying.

Extra advanced

Modifying whether an answer graduates a learning card to review or not

I haven’t tried but I imagine you could do this with a card in learning:

// Make answering easy skip one learning step instead of graduating to review
const nextRemainingSteps = goodLearnObj?.remainingSteps;
// Skip a step if there are two steps still left
// this will be false, if nextRemainingSteps is undefined (not in learning or current step is already the last)
if (nextRemaningSteps >= 2) {
  delete states.easy.normal.review;
  states.easy.normal.learning = {
    remainingSteps: nextRemainingSteps - 1,
    scheduledSecs: 60, // Set some interval
    elapsedSecs: 0, // This is always zero, does nothing I guess?
    easeFactor: 2.5 // Need to set some factor value for SM-2
  };
}

Or the same idea applied for answering Hard for a review card. Making it put the card into relearning could be possible too?

2 Likes

I actually had to go back and check how exactly did the states work in filtered decks and with relearning steps etc. and made some edits to the guide as I learned a bunch of stuff…

Most importantly:
You can actually get custom repeating intervals in filtered decks without using custom scheduling by unchecking Reschedule cards based on my answers in the this deck and defining the intervals for each answer button.

This will not store the reviews you do to the collection as scheduling is turned off but perhaps you could just keep your cards in a filtered deck forever?

1 Like

Thanks kings for trying to help me.
However, in the midst of the confusion, I turned to another software: Mochi.
Much much simpler.
Anyways thanks again!
I’ll try to delete this discussion now

I couldn’t find out how, oops! :laughing:

We can’t have you deleting the valuable responses from other folks. It’s fine for the post to stay here, and enjoy your other app.

1 Like