iOS 16 introduced new lock screen widgets! Rather that installing various third party widdgets, I want to create my own using Scriptable. Here is a simple “universal” widget to launch an application or a Shortcut (bonus: or even the home screen too!)

This is a tutorial on how to create a lock screen widget with Scriptable, which is free and does not collect any data. You will need Scriptable from the AppStore plus JavaScript experience! Here I go through a few things I tried... but if you want the TL;DR - just skip to the fourth script.

Aside: A gripe with the Apple’s lock screen widgets: the battery widget (circular), the fitness widgets (rings), the weather widget (guage) and other Apple widgets all look way too different! Can’t we have nice consistent circular widgets?

Lock Screen Widget to Launch an Application

Scritable has a great feature in that it can launch a URL from a lock screen widget - in essence this is a lock screen “launcher”, that’s all it is!

To configure this:

  • customize your Wallpaper > lock screen from Settings (or any other way)
  • then add a circular Scriptable widget,
  • and finally, tap on the newly added widget
  • this brings up a configuration page, where the When Interacting = Open URL option can be set:

Scriptable Open URL

When tapped, the widget can launch a deep link / universal link via a URL / URI Scheme to run the associated application (I’m aware I’m using these terms incorrectly as if they were interchangeable). In my case, WhatsApp’s URL Scheme is whatsapp:// but you can search the Internet for many other applications. Not voching for this list, but for example, TechRegister’s “Always-Updated List of iOS App URL Scheme Names & Paths for Shortcuts”

But we are getting ahead of ourselves: we first need a Scriptable script, which render the widget to display in the lock screen.

Script 1: Blank widget

For a very clean lock screen, simply use an invisible widget. This is also a great spacer if you want, e.g. to right align a single widget.

Simply use a minimum scaffold like so:

let widget = new ListWidget()
if (config.runsInAccessoryWidget)
  Script.setWidget(widget)
else
  widget.presentSmall()
//widget.presentAccessoryCircular()

Tips:

  • When run from the lock screen, runsInAccessoryWidget is true and setWidget() is called - without the latter Scriptable displays an error message in the widget!
  • On the other hand, widget.presentSmall() shows the widget when run from from Scriptable itself - this can be dispensed with, but is helpful for testing.
  • I am aware there is widget.presentAccessoryCircular() is better for testing - IHMO the function does not render greyscale and transparency the way it should be.

Script 2: Text widget

For a widget to display text, the minimum code is:

let widget = new ListWidget()
let stack = widget.addStack()
stack.addText(Script.name).centerAlignText()
//stack.addText(Script.name)
if (config.runsInAccessoryWidget)
  Script.setWidget(widget)
else
  widget.presentSmall()

Tips:

  • I don’t even need to hardcode the text - one can simply display the script name instead, using Script.name.
  • Funnily enough, it’s possible to use emojis in the text which render in full color!

Script 3: Widget with an in-line Icon

I figured why not just keep the code self-contained, with an “embedded” icon instead? Sure! Just encode the icon Base64 (text) first.

let widget = new ListWidget()
let stack = widget.addStack()
//stack.setPadding(8, 8, 8, 8)
//stack.backgroundColor = Color.gray()
let data = Data.fromBase64String("...base64...")
let image = Image.fromData(data)
stack.addImage(image)
if (config.runsInAccessoryWidget)
  Script.setWidget(widget)
else
  widget.presentSmall()

To convert an icon to its Base64 representation:

  • On macOS, in Terminal, run base64 -i image.png.
  • On macOS, iOS or iPadOS, use the Shortcuts App Base64 Encode action.

I won’t post the icon nor its Base64 representation due to copyright, but my final lock screen widget output is as below. The Scriptable widget is the rightmost:

Scriptable Lock Screen Widgets

Regarding icons

For more details on the lock screen circular widgets, refer to Apple’s human interface guidelines for widgets.

Some notes regarding icons:

  • Icons should be in greyscale, as system will only lock screen widgets in greyscale. I use a greyscale PNG.
  • Icons are 76x76 pts. Assuming 2x or 3x retina display, icons need to be at least 152x152 or 228x228 pixels to remain sharp.
  • The system will mask icons so that they are circular - anything at the corners will therefore be truncated.
  • Opacity (transparency) controls how much of the background wallpaper will be visible (though blurred).
  • Documentation states that “brighter gray values provide more contrast, and darker values provide less contrast.”
  • Since many icons are black on transparent, just invert them so that the icon is solid white instead.

A few more tips:

  • Unless you are in Dark Mode, running the script in Scriptable renders a white icon on a white background - in which case, nothing is visible! Uncomment then backgroundColor line to resolve this for easier testing. However, this is not required when running as a lock screen widget.
  • My icon is right to the edge of the space given - if you want to programmatically add padding, use setPadding().

BTW I did try SFSymbol in Scriptable - unfortuantely, I cannot find a way to easily convert a symbol to an image with a White foreground instead of Black.

Script 4: Widget that loads a PNG icon file

The previous script requires effort to encode icons in every Script but also means there are no dependencies to distribute. Alternatively...

Here is how to dynamically load a PNG image. The PNG needs to be in the iCloud Drive Scriptable folder (use the Files application > under locations, iCloud Drive):

let widget = new ListWidget()
let stack = widget.addStack()
let param = args.widgetParameter
if (param?.endswith(".png")) {
  let files = FileManager.iCloud()
  let file = filesdocumentsDirectory() + "/" + param
  files.downloadFileFromiCloud(file)
  let image = f.readImage(file)
  stack.addImage(image)
} else {
  let text = stack.addText(param ?? Script.name())
  text.font = Font.headline()
  text.centerAlignText()
}
if (config.runsInAccessoryWidget) 
  Script.setWidget(widget)
else
  widget.presentSmall()

This time, make sure that after you add the widget, either:

  • Configure the Parameter field with an image filename of your choice which must end with .png.
  • Or just specify text to display, leaving it empty (null) will default to the Scriptable script name, and a space will be an icon-less (invisible) widget.

Scriptable Open URL with a Parameter

Tips:

  • It’s possible to use the FileManager.local() folder instead - I use the iCloud folder simply because that is were Scriptable stores all .js scripts.
  • Note the Scriptables readImage documentation states that:

The function will error if the file does not exist or if it exists in iCloud but has not been download. Use fileExists(filePath) to check if a file exists and downloadFileFromiCloud(filePath) to download the file. Note that it is always safe to call downloadFileFromiCloud(filePath), even if the file is stored locally on the device.

Launch a Shortcut

How about running Shortcuts from widgets? Sure! There is an URL Scheme to do just that, as documented in Apple’s Shortcuts User guide

Just use shortcuts://run-shortcut?name=[name]&input=[text|clipboard]&text=[text] where name is the exact name of the Shortcut. Remember to URL encode, e.g. ` (space) is encoded as%20. I do not useinputandtext` parameters myself, but read the documentation to understand what they are for.

Alas, the Scriptable CallbackURL() function is not supported in a widget, otherwise I could use the x-callback-url with Shortcuts to return values from Shortcuts and display them in a widget.

Bonus: Launch SpringBoard (Home Screen)

One final “launcher” - how about launching SpringBoard instead? Often this is referred to as “exit to iOS Home Screen”, but in this contents its more accurately tap-to-unlock, as opposed to swipe-up-to-unlock.

I hate swiping from the very bottom of the lock screen to unlock my phone, since I cannot easily reach it with my thumb one-handed. I would rather have an widget... Scriptable has granted my wish!

Enter an undocumented Scriptable function and a Scriptable script with just this single line:

App.close()

This time, When Interacting is set to Run Script:

Scriptable Run Script

There will be a flash as the phone unlocks, opens Scriptable, runs runs the script above, which then forces Scriptable to close. If Reduce Motion is turned on, this is less noticeable.

BTW, obviously this script will not show any icon in the widget, but now you know a few methods to draw text or icons, this is left as an excercise for the reader!

Have fun!

Small update 14 Dec 2022: Bugs!