Deprecated: Return type of I::current() should either be compatible with Iterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 62

Deprecated: Return type of I::next() should either be compatible with Iterator::next(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 91

Deprecated: Return type of I::key() should either be compatible with Iterator::key(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 71

Deprecated: Return type of I::valid() should either be compatible with Iterator::valid(): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 101

Deprecated: Return type of I::rewind() should either be compatible with Iterator::rewind(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/i.php on line 53

Deprecated: Return type of Collection::count() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /home/public/kirby/toolkit/lib/collection.php on line 80

Deprecated: parse_str(): Passing null to parameter #1 ($string) of type string is deprecated in /home/public/kirby/toolkit/lib/url.php on line 135
One Tap Less | Recursive Actions with Drafts and GoodTask

Recursive Actions with Drafts and GoodTask

This Week changed its name to GoodTask, this article was updated accordingly.

It has been a while since I wrote about Drafts as I've been mesmerized by the possibilities the new Launch Center Pro opens. However, you still can't beat the ease to create recursive actions in Drafts, remember how these look like? GoodTask, an improvement over the built-in Reminders got x-callback-url support recently. Turns out they're a great match.

Let's start with a single line action, so you can learn how to build everything from scratch:

goodtask://x-callback-url/add?text=[[title]]

This one will create a new reminder with the first line of our text. Let's return to Drafts after we're done.

goodtask://x-callback-url/add?text=[[title]]&x-success={{drafts://}}

Simple, right? Remember that in Drafts and LCP, when the content is wrapped by curly brackets it is url encoded. But let's chat a little on how a recursive action works. Let's pretend you have a list of reminders you'd like to add to GoodTask, you write them down, one item per line. You can only add one reminder each time, so you need a method to add your reminder to GoodTask, bounce back to Drafts and run the same action again, this time skipping the first line because we already added it. This must go in a loop until our list is empty.

Let's consider that our action is named Recursive Week. Sending only the first line is easy, we did that in our first example using the [[title]] parameter, now we must remove the first line and we use [[body]] for it because it collects the whole draft but the first line. This is what we got so far:

goodtask://x-callback-url/add?text=[[title]]&x-success={{drafts://x-callback-url/create?text=[[body]]}}

Creating our loop is actually quite simple since Drafts supports calling its own actions and so we gonna call itself. That works because the action is re-called using the [[body]] parameter, so what was your second line in your initial document, becomes the first on this new trigger, the [[title]], which is exactly what our action is looking for. Just remember to manually encode the action name, so it goes as Recursive%20Week.

Recursive Week (level 1):

goodtask://x-callback-url/add?text=[[title]]&x-success={{drafts://x-callback-url/create?text=[[body]]&action=Recursive%20Week&afterSuccess=Delete&allowEmpty=NO}}

You may have noticed that I included two additional parameters, afterSuccess defines what happens to the note after the action succeds, since you don't need multiple copies of your reminders, you can avoid archiving them using Delete. You can also set it to Archive or Nothing to force different outcomes. If this parameter is not set, Drafts will use the default you configured in the in-app Settings.

The other parameter, allowEmpty is only relevant for loops since Drafts can now trigger actions from an empty sheet, so our loop won't return an error and break the chain when we're done with our tasks. Using NO we tell Drafts to stop the action if the document is blank.

See? Not so scary now, is it? However, I carry some bad news, this action is probably useless if you can afford a bit less control and use Drafts' List in Reminders action. If we gonna take GoodTask for a catwalk, we better unleash its true potential as it supports multiple parameters, such as list, due, priority, URL and notes, and yes, we can also include that to our recursive action with a few tweaks.

We gonna build an action including the priority parameter as well. First we gotta add it to our base action:

goodtask://x-callback-url/add?text=[[title]]&priority[[line|2]]

Notice that now I use the [[line]] parameter, which points out to a specific line of the document, in this case the second line. Yes, you guessed it correctly, [[line|1]] is exactly the same as [[title]]. But now we also want to skip our second line from the loop and for that we'll use [[line|3..]], which means we tell Drafts to grab the third line and every other existing line after it. You could also limit the selection like [[line|3..6]], which would collect lines 3, 4, 5 and 6 only. This is how our final action looks like then:

Recursive Week (level 2):

goodtask://x-callback-url/add?text=[[title]]&priority=[[line|2]]&x-success={{drafts://x-callback-url/create?text=[[line|3..]]&action=Recursive%20Week&afterSuccess=Delete&allowEmpty=NO}}

But what if we decide we want to select to which list our reminder is set. Also, we don't want to type our list everytime1. So, yeah, I'm bringing Launch Center Pro into the mix.

Ok, now I want to specify everything on Drafts, then jump to Launch Center Pro only to use the [list] parameter, add the reminder to GoodTask and start our loop. Since we're now calling Launch Center Pro first, we gotta change our url to its own. Let's get back to our base url because things are going to get nasty.

launchpro://?url={{goodtask://x-callback-url/add?text=[[title]]&priority=[[line|2]]&list=[list:Choose your list|Pessoal|Lembretes|GoodTask]}}

Here's our first problem: encoding the callback parameters, since we're already wrapping everything on curly brackets you can't use it again, so we have to manually encode everything, however, if I do that, how will you tell Drafts to send [[line|3..]] as well? This is an encoding nightmare because it requires multiple layers.

Recursive Week (level 99):

launchpro://?url=goodtask%3A%2F%2Fx-callback-url%2Fadd%3Ftext%3D{{[[title]]}}%26priority%3D{{[[line|2]]}}%26list%3D%5Blist%3AChoose%20your%20list%7CPessoal%7CLembretes%7CThis%20Week%5D%26x-success%3D%7B%7Bdrafts%3A%2F%2Fx-callback-url%2Fcreate%3Ftext%3D{{[[line|3..]]}}%26action%3D{{Recursive%20Week}}%26afterSuccess%3DDelete%26allowEmpty%3DNO%7D%7D

There's so much encoding here you don't even want to know. First, we manually encode our GoodTask action to run it on Launch Center Pro. That's the first layer. Since we're only using our Drafts parameters later, wrap them in curly brackets so they're sent to GoodTask encoded. Even our [list] is encoded now, it looks like this: [list:Choose your List|Pessoal|Lembretes|GoodTask], then we call our callback action and that's where things get messy, if you decode this action, you'll realize that I wrapped the x-success in curly brackets, that's because I want Launch Center Pro to handle the url encode of this block later. With the second encode ready, I'm able to encode the Drafts parameters individually to be used later, that's why I wrap both the [line|3..] and the action parameter with curly brackets, so the content it sends to Launch Center Pro is already encoded twice 2. Then Launch Center Pro will encode the whole thing again.

This is really hard to visualize like this, so let's try out an example. Consider we have a draft like this one:

Take garbage out
2
Do the laundry
3

This is the action received by Launch Center Pro:

goodtask://x-callback-url/add?text=Take%20garbage%20out&priority=2&list=[list:Choose your list|Pessoal|Lembretes|GoodTask]&x-success={{drafts://x-callback-url/create?text=Do%20the%20laundry%0A3&action=Recursive%20Week&afterSuccess=Delete&allowEmpty=NO}}

The callback is interpreted by GoodTask like this:

drafts://x-callback-url/create?text=Do%20the%laundry%0A3&action=Recursive%20Week&afterSuccess=Delete&allowEmpty=NO

Always consider that you lose a layer of encoding for each bounce between apps, therefore, we basically encode the callback on Launch Center Pro so it is unaltered when it reaches GoodTask. Did I daze you? Sorry, I know url encoding can be a pain in the derrière, but it is important to understand how it works to achieve actions of this sort channeling the powers of Drafts and Launch Center Pro into the apps you use everyday.


  1. Although GoodTask has a great feature where it identifies the list based on its initials, so h would lead to the list Habits, for example. 

  2. Remember parameters are already encoded once, so we also have to manually encode the action name to achieve the same result.