Widget Examples

Hi everyone,

I’m trying to gather a few examples of scripts that work with the new widgets in iOS 14, so people have a place to find inspiration for their own widgets. I would love if you would share your scripts that work with widgets in this thread :blush:

If you would like to try creating your own widgets with Scriptable, you can join the TestFlight beta here: https://testflight.apple.com/join/uN1vTqxk

Note that the TestFlight beta requires that you run the iOS 14 beta.


Here’s a script that shows the latest news from MacStories in a widget.

let items = await loadItems()

if (config.runsInWidget) {
  let widget = createWidget(items)
} else {
  let item = items[0]

function createWidget(items) {
  let item = items[0]
  let authors = item.authors.map(a => {
    return a.name
  }).join(", ")
  let rawDate = item["date_published"]
  let date = new Date(Date.parse(rawDate))
  let df = new DateFormatter()
  let strDate = df.string(date)
  let w = new ListWidget()
  w.backgroundColor = new Color("#b00a0f")
  let titleTxt = w.addText(item.title)
  titleTxt.textColor = Color.white()
  let authorsTxt = w.addText("by " + authors)
  authorsTxt.textColor = Color.white()
  authorsTxt.textOpacity = 0.8
  let dateTxt = w.addText(strDate)
  dateTxt.textColor = Color.white()
  dateTxt.textOpacity = 0.8
  return w
async function loadItems() {
  let url = "https://macstories.net/feed/json"
  let req = new Request(url)
  let json = await req.loadJSON()
  return json.items

function extractImageURL(item) {
  let regex = /<img src="(.*)" alt="/
  let html = item["content_html"]
  let matches = html.match(regex)
  if (matches && matches.length >= 2) {
    return matches[1]
  } else {
    return null

function decode(str) {
  let regex = /&#(\d+);/g
  return str.replace(regex, (match, dec) => {
    return String.fromCharCode(dec)

This script shows the Corona related numbers: https://gist.github.com/planecore/e7b4c1e5db2dd28b1a023860e831355e

The script was developed by @planecore.

This script shows a daily text snippet: https://gist.github.com/mattapperson/114e5267d5bc736fbe616b6205f3df92

Developed by @mattapperson.

This script shows a random image: https://gist.github.com/jacopocolo/0e28c4af2e28c9de058480da0c5d4582

Developed by @jacopocolo.

This script shows the XKCD comic of the day: https://gist.github.com/rudotriton/9d11ce1101ff1269f56844871b3fd536

Developed by @raigojerva.

This script shows a graph from Charty: https://gist.github.com/0507spc/85ac637e6f46c466c47332a55a6411f4

It depends on this shortcut: https://www.icloud.com/shortcuts/9a2c3dc4c8934db0a770a46eb18ff807

Developed by @cranies.

1 Like

This script shows the menu in a company’s cantina: https://gist.github.com/Casperbart/d8850fcdb1d7549502ce68f65854f997

Developed by @cassebart.

1 Like

@simonbs I’m trying to display this 800x800 image (exported by Charty):

I want it to fill the entire widget, but unfortunately this is what I get instead…

It does not seem to matter whether I set an imageSize (nor which width/height I use) or not.

What can be wrong here?

(I’m essentially using Steven Crane’s code, but with a different sized image - so my experiments are all on line 26 in his code)

Hi, it’s Steve here, it may be due to the widget export profile I have with Charty:

Try that and see if that helps (you may need to alter values to suit).

1 Like

Here is my Reddit code, it is not the cleanest, I copied / pasted / trial and error hacked until I got what I wanted. I have set the click to run script which then opens Reddit on that subreddit.

// Shows latest news from MacStories in a table.
// The table shows an image from the article, if available, and the title of the article.
let rReddit = "Scriptable"
let url = "https://www.reddit.com/r/" + rReddit + "/new.json?limit=6"
let req = new Request(url)
let json = await req.loadJSON()
let items = json.data.children
let appURL = "reddit:///r/" + rReddit

if (config.runsInWidget) {
  let widget = createWidget(items)
} else {

function createWidget(items) {
//   let item = items[0]
  let w = new ListWidget()
  w.backgroundColor = new Color("#47761E")

let header = w.addText("r/" + rReddit)
header.textColor = Color.black()


    for (item of items) {
      var myDate = new Date(item.data.created_utc * 1000)
      var myFormDate = addZero(myDate.getHours()) + ":" + addZero(myDate.getMinutes())
      let titleTxt = w.addText("• " + myFormDate + " - " + item.data.title)
        titleTxt.textColor = Color.white()
        titleTxt.textSize = 14
  var nowDate = new Date()
  var nowFormDate = addZero(nowDate.getHours()) + ":" + addZero(nowDate.getMinutes())
  let footer = w.addText("Last Updated: " + nowFormDate)
  footer.textColor = Color.black()
  return w

function addZero(i) {
  if (i < 10) {
    i = "0" + i;
  return i;

You’re so tempting me to try the iOS 14 beta… :slight_smile:


Thanks for posting that!

Will give that a try later today or this weekend (and probably start with the same widget size you use instead of a square one).

Using exactly your dimensions seems to work better!

Note: I intentionally made the background of the widget white to see what’s happening

@cranie Can you explain the dimensions? You use 1600x800 in Charty, but 300x1400 in Scriptable?

So excited for this!

1 Like

I built a now-playing widget for Radio Paradise which shows album artwork and metadata. It queries a simple REST api (json) and loads the image. Once touched it opens the flac stream of Radioparadise in VLC (via callback-url).

That’s the script: https://gist.github.com/marco79cgn/195f8d5332069b0cadf1bb7cfd7a304a

1 Like

I built a widget for informing about a storm nearby. It using some Polish SOAP service (http://burze.dzis.net), but should work for most of Europe (just replace coords). And… you need API key.

async function fetchData(position) {
  const DDtoDM = (coord) => {
    const coordInt = Math.floor(coord)
    return coordInt + (coord - coordInt) * 0.6;

  const KEY = 'KEY_KEY_KEY';
  const RADIUS = '12'; //km
  const stormURL = 'https://burze.dzis.net/soap.php';

  const requestXML = `
  <soapenv:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/' xmlns:soap='https://burze.dzis.net/soap.php'>
     <soap:szukaj_burzy soapenv:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
        <y xsi:type='xsd:string'>${DDtoDM(position.longitude).toFixed(2)}</y>
        <x xsi:type='xsd:string'>${DDtoDM(position.latitude).toFixed(2)}</x>
        <promien xsi:type='xsd:int'>${RADIUS}</promien>
        <klucz xsi:type='xsd:string'>${KEY}</klucz>


  const req = new Request(stormURL);
  req.method = 'POST';
  req.headers = {
    'Content-Type': 'text/xml',
    'SOAPAction': '\'https://burze.dzis.net/soap.php\''
  req.body = requestXML;
  const response = await req.loadString();


  const parser = new XMLParser(response);

  var processedItem = '';

  var returned = {
    liczba: -1,
    odleglosc: -1,
    kierunek: 'n/a',
    okres: '0',

  parser.didStartElement = (name) => {
    processedItem = name;

  parser.foundCharacters = (value) => {
    switch (processedItem) {
      case 'liczba':
        returned.liczba = parseInt(value, 10);
      case 'odleglosc':
        returned.odleglosc = parseInt(value, 10);
      case 'kierunek':
        returned.kierunek = value;
      case 'okres':
        returned.okres = parseInt(value, 10);

  parser.didEndElement = (name) => {
    processedItem = '';



  return returned;

function createWidget(position, data) {
  let addressString = position.name;
  let headlineString = "No storms";
  let detailsString = "—";

  if (data.odleglosc > 0) {
    headlineString = "Storm nearby!";
    detailsString = `${data.liczba} strikes from ${data.kierunek} in last ${data.okres} min. Closest in ${data.odleglosc} km.`;

  const w = new ListWidget();
  w.backgroundColor = new Color("#20504F");

  const headline = w.addText(headlineString);
  headline.textColor = new Color("#fff");

  const details = w.addText(detailsString);
  details.textColor = new Color("#fff");

  const address = w.addText(addressString);
  address.textColor = new Color("#fff");


  return w;

const run = async (getPosition, getData, onFinish) => {
  const position = await getPosition();
  const data = await getData(position);

  onFinish(position, data);

const getStaticPosition = () => {
  return {
    longitude: 51.00,
    latitude: 18.00,
    name: 'Home',

await run(
  (position, data) => Script.setWidget(createWidget(position, data)),

1 Like

I have created super simple scriptable widget which queries api for Brainkeeper app (personal assistant for foreign vocabulary) to get random word from my list of words I collected. Super cool to have always foreign words in my sight to remember words better. :slight_smile:
Thanks for this Scriptable!

1 Like

what if i wanted to make a widget like the coronavirus one but with a gradient background? Sorry, I’m new to coding

Several of the other widgets in this thread have a gradient background. Try taking a look at those to see how they do it. You’ll want to keep an eye out for mentions of LinearGradient and widget.backgroundGradient.

1 Like