How to make the iPhone Duo fold counter widget: free Scriptable script and 10 minute setup

The fold counter widget going around social media is a short Scriptable script fired by the iPhone Duo's new open and close trigger. Here is our free script and the ten minute setup.

Official video from Apple on YouTube.

You have seen the widget on Facebook and Instagram this week: a dark card on someone's iPhone Duo that says how many times they have opened the phone, with a bar for each day. People keep asking in the comments which app it is. There is no app. It is a short script in Scriptable, a free scripting app, fired by a new Shortcuts trigger that only the iPhone Duo has.

The short answer: you need three free pieces. Scriptable draws the widget and saves the count. A Shortcuts automation on the iPhone Duo's new "Opened or Closed" trigger runs the script each time you open the phone. The script below ties them together. Setup takes about ten minutes, costs nothing, and the count never leaves your phone. We wrote our own version from scratch, and you can copy it in one tap.

Key takeaways

  • The iPhone Duo ships with iOS 27.1, which adds a Shortcuts automation that runs when the phone is opened or closed. That trigger is what makes an automatic fold counter possible.
  • Scriptable is free on the App Store. Shortcuts cannot draw a custom widget on its own, so Scriptable does the drawing.
  • The widget updates on iOS's schedule, not instantly. The count is saved the moment you open the phone, but the number on screen can lag by a few minutes.
Two hands hold up iPhone Duo devices, one folded and one unfolded
Image: Apple

At a glance

What you need for the fold counter widget. Device facts from Apple; app facts from the Scriptable App Store listing and documentation.
ItemDetails
PhoneiPhone Duo. The open and close trigger exists only on the Duo, which ships with iOS 27.1
ScriptableFree on the App Store, with optional tips. The listing says it collects no data
ShortcutsBuilt into iOS. No download
The scriptBelow on this page, about 250 lines of JavaScript. No internet access, no account
Widget sizesSmall and medium on the Home Screen; circular, rectangular and inline on the Lock Screen
Cost$0
TimeAbout 10 minutes

How the fold counter works

Three parts pass the count along. Shortcuts notices that you opened the phone and runs the script with one word, fold. The script adds one to today's total and saves a small file inside Scriptable's own folder. When iOS next refreshes the widget, the same script reads that file and draws the numbers and the last seven days as bars.

How the fold counter passes the count alongA four step flow from top to bottom. Step 1: you open the iPhone Duo. Step 2: the Shortcuts automation Opened or Closed, set to Opened, runs the Scriptable script with the word fold. Step 3: the script adds one to today's count and saves it on the phone. Step 4: when iOS refreshes the widget, the script draws the total and a seven day bar chart.1. You open the phoneThe hinge reports the inner display is open2. Shortcuts reactsThe Opened automation runs the script3. Script savesToday's count goes up by one, on your phone4. Widget drawsTotal, this week and 7 bars, at refresh
The automation does the counting; the widget only reads and draws.

Step 1: add the script to Scriptable

  1. Install Scriptable by Simon B. Støvring from the App Store.
  2. Open it, tap + to start a new script, and paste in everything from the box below. On this page, the Copy button copies the whole script.
  3. Tap the title at the top and rename the script Duo Folds. The Shortcut in step 2 looks for this exact name.
  4. Tap the play button once. A menu opens with Preview widget, Add one fold, Undo last fold and Reset to zero. Pick Preview widget to see the empty widget.
Duo Folds script for Scriptable
// Duo Folds: a fold counter widget for iPhone Duo, made with Scriptable.
// Written for vaelolab.com. Free to copy, change and share.
//
// How it works
// 1. A Shortcuts automation ("Opened or Closed", set to Opened) runs this
//    script with the text "fold" every time you open the phone.
// 2. The script adds one to today's count and saves it on your phone.
// 3. The Home Screen widget reads the saved counts and draws the chart.
//
// Commands you can send from Shortcuts (Run Script, Parameter):
//   fold   add one fold (use this in the automation)
//   undo   remove the last fold
//   reset  clear every count
// Tap the play button in Scriptable to preview the widget or fix the count.
//
// Nothing leaves your phone. The script never uses the internet.

// ---------- settings you can change ----------
const TITLE = "DUO FOLDS"
const ACCENT = new Color("#6fcf9f")      // today's bar
const BAR = new Color("#3b404c")         // other days
const BG_TOP = new Color("#20232b")
const BG_BOTTOM = new Color("#14161b")
const TEXT = Color.white()
const MUTED = new Color("#9aa0ab")
const MIN_GAP_SECONDS = 3                // ignore a second trigger within 3 seconds
const KEEP_DAYS = 120                    // how many days of history to keep
const FILE_NAME = "duo-folds.json"

// ---------- storage ----------
const fm = FileManager.local()
const path = fm.joinPath(fm.documentsDirectory(), FILE_NAME)

function load() {
  if (!fm.fileExists(path)) return { total: 0, days: {}, last: 0 }
  try {
    const d = JSON.parse(fm.readString(path))
    return { total: d.total || 0, days: d.days || {}, last: d.last || 0 }
  } catch (e) {
    return { total: 0, days: {}, last: 0 }
  }
}

function save(d) {
  fm.writeString(path, JSON.stringify(d))
}

function dayKey(date) {
  const y = date.getFullYear()
  const m = String(date.getMonth() + 1).padStart(2, "0")
  const day = String(date.getDate()).padStart(2, "0")
  return y + "_" + m + "_" + day
}

function prune(d) {
  const cutoff = new Date()
  cutoff.setDate(cutoff.getDate() - KEEP_DAYS)
  const oldest = dayKey(cutoff)
  for (const k of Object.keys(d.days)) {
    if (k < oldest) delete d.days[k]
  }
}

function addFold(d) {
  const now = Date.now()
  if (now - d.last < MIN_GAP_SECONDS * 1000) return false
  const k = dayKey(new Date())
  d.days[k] = (d.days[k] || 0) + 1
  d.total += 1
  d.last = now
  prune(d)
  save(d)
  return true
}

function undoFold(d) {
  const k = dayKey(new Date())
  if (!d.days[k] || d.total === 0) return
  d.days[k] -= 1
  d.total -= 1
  save(d)
}

function lastSevenDays(d) {
  const out = []
  for (let i = 6; i >= 0; i--) {
    const date = new Date()
    date.setDate(date.getDate() - i)
    out.push({
      letter: "SMTWTFS"[date.getDay()],
      count: d.days[dayKey(date)] || 0,
      today: i === 0
    })
  }
  return out
}

// ---------- drawing ----------
function addLabel(stack, text, font, color) {
  const t = stack.addText(text)
  t.font = font
  t.textColor = color
  t.lineLimit = 1
  t.minimumScaleFactor = 0.6
  return t
}

function header(stack) {
  const row = stack.addStack()
  row.layoutHorizontally()
  row.centerAlignContent()
  const icon = row.addImage(SFSymbol.named("iphone").image)
  icon.imageSize = new Size(13, 13)
  icon.tintColor = MUTED
  row.addSpacer(6)
  addLabel(row, TITLE, Font.semiboldSystemFont(13), MUTED)
}

function chart(stack, days, height) {
  const max = Math.max(1, ...days.map(x => x.count))
  const barMax = height - 34
  const row = stack.addStack()
  row.layoutHorizontally()
  row.bottomAlignContent()
  row.spacing = 6
  for (const day of days) {
    const col = row.addStack()
    col.layoutVertically()
    col.size = new Size(20, height)
    col.addSpacer()
    const num = col.addStack()
    num.addSpacer()
    addLabel(num, String(day.count), Font.mediumSystemFont(11), day.today ? TEXT : MUTED)
    num.addSpacer()
    col.addSpacer(3)
    const bar = col.addStack()
    bar.size = new Size(20, Math.max(5, Math.round(barMax * day.count / max)))
    bar.cornerRadius = day.count === 0 ? 2.5 : 6
    bar.backgroundColor = day.today ? ACCENT : BAR
    col.addSpacer(5)
    const letter = col.addStack()
    letter.addSpacer()
    addLabel(letter, day.letter, Font.semiboldSystemFont(11), day.today ? TEXT : MUTED)
    letter.addSpacer()
  }
}

function buildWidget(d, family) {
  const days = lastSevenDays(d)
  const week = days.reduce((s, x) => s + x.count, 0)
  const today = days[6].count
  const w = new ListWidget()
  w.refreshAfterDate = new Date(Date.now() + 15 * 60 * 1000)

  // Lock Screen sizes
  if (family === "accessoryCircular") {
    w.addAccessoryWidgetBackground = true
    const t = addLabel(w, String(today), Font.boldRoundedSystemFont(20), TEXT)
    t.centerAlignText()
    const s = addLabel(w, "today", Font.systemFont(9), TEXT)
    s.centerAlignText()
    return w
  }
  if (family === "accessoryInline") {
    addLabel(w, "Folds today " + today + ", all time " + d.total, Font.systemFont(12), TEXT)
    return w
  }
  if (family === "accessoryRectangular") {
    addLabel(w, "Duo folds", Font.semiboldSystemFont(12), TEXT)
    addLabel(w, String(d.total), Font.boldRoundedSystemFont(22), TEXT)
    addLabel(w, "Today " + today + "  Week " + week, Font.systemFont(11), TEXT)
    return w
  }

  const g = new LinearGradient()
  g.colors = [BG_TOP, BG_BOTTOM]
  g.locations = [0, 1]
  w.backgroundGradient = g

  if (family === "small") {
    w.setPadding(14, 14, 14, 14)
    header(w)
    w.addSpacer()
    addLabel(w, String(d.total), Font.boldRoundedSystemFont(40), TEXT)
    addLabel(w, "all time", Font.mediumSystemFont(12), MUTED)
    w.addSpacer(8)
    const row = w.addStack()
    row.spacing = 4
    addLabel(row, "Today", Font.mediumSystemFont(13), MUTED)
    addLabel(row, String(today), Font.boldSystemFont(13), ACCENT)
    return w
  }

  // Medium (and large): numbers on the left, last 7 days on the right
  w.setPadding(16, 18, 16, 18)
  const body = w.addStack()
  body.layoutHorizontally()
  body.bottomAlignContent()
  const left = body.addStack()
  left.layoutVertically()
  header(left)
  left.addSpacer()
  addLabel(left, String(d.total), Font.boldRoundedSystemFont(44), TEXT)
  addLabel(left, "all time", Font.mediumSystemFont(13), MUTED)
  left.addSpacer()
  const wk = left.addStack()
  wk.spacing = 4
  addLabel(wk, "This week", Font.mediumSystemFont(14), MUTED)
  addLabel(wk, String(week), Font.boldSystemFont(14), TEXT)
  body.addSpacer()
  chart(body, days, 120)
  return w
}

// ---------- run ----------
const data = load()
const param = (args.shortcutParameter || (args.plainTexts && args.plainTexts[0]) || "")
  .toString().trim().toLowerCase()

if (param === "fold" || param === "open") {
  addFold(data)
  Script.setShortcutOutput(data.total)
} else if (param === "undo") {
  undoFold(data)
  Script.setShortcutOutput(data.total)
} else if (param === "reset") {
  save({ total: 0, days: {}, last: 0 })
  Script.setShortcutOutput(0)
} else if (config.runsInWidget) {
  Script.setWidget(buildWidget(data, config.widgetFamily))
} else if (config.runsInApp) {
  const a = new Alert()
  a.title = "Duo Folds"
  a.message = "All time: " + data.total + ". Today: " + (data.days[dayKey(new Date())] || 0) + "."
  a.addAction("Preview widget")
  a.addAction("Add one fold")
  a.addAction("Undo last fold")
  a.addDestructiveAction("Reset to zero")
  a.addCancelAction("Close")
  const choice = await a.presentAlert()
  if (choice === 1) addFold(data)
  if (choice === 2) undoFold(data)
  if (choice === 3) save({ total: 0, days: {}, last: 0 })
  if (choice >= 0 && choice <= 3) {
    await buildWidget(load(), "medium").presentMedium()
  }
} else {
  Script.setWidget(buildWidget(data, config.widgetFamily))
}
Script.complete()

Prefer another accent colour? Change ACCENT near the top. Want the title to say something else, such as your shop name? Change TITLE. Everything else can stay as it is.

Step 2: make the open automation

  1. Open Shortcuts, go to the Automation tab and tap + to make a new automation.
  2. Find the Opened or Closed trigger. MacRumors reports it sits with the Display automations.
  3. Choose Opened only. If you also tick Closed, every fold counts twice.
  4. Choose Run Immediately and turn off Notify When Run, so nothing pops up each time you open the phone.
  5. For the action, add Scriptable then Run Script. Pick Duo Folds, and in Parameter type fold.
  6. Tap the action's arrow and turn off Run In App, so Scriptable does not open on top of whatever you were doing.

Menu wording can vary a little by language and iOS version. What matters is the combination: the Opened state, run without asking, and the word fold passed to the script.

Step 3: put the widget on your Home Screen

  1. Touch and hold an empty part of the Home Screen, tap Edit, then Add Widget.
  2. Choose Scriptable and pick the medium size for the version with the seven day chart, or small for just the total and today.
  3. Touch and hold the new widget, tap Edit Widget, and set Script to Duo Folds.
  4. For the Lock Screen, edit the Lock Screen, add a Scriptable widget in the circular, rectangular or inline slot, and pick the same script.
The Duo Folds widget in medium, small, rectangular and circular sizes, showing a total of 331, a seven day bar chart and today's count of 9
Preview of our script's layout with sample numbers, rendered in a browser simulation of Scriptable's widget tools. Not a screenshot from an iPhone Duo.

Common problems and fixes

  • The number does not change right away. iOS decides when widgets refresh. The script asks for a refresh every 15 minutes, but iOS may wait longer. The count itself is already saved; tap the play button in Scriptable and choose Preview widget to see the latest total.
  • Every fold counts twice. The automation is set to Opened and Closed. Edit it and keep only Opened.
  • The count stays at zero. Check the script is named exactly Duo Folds in the Run Script action, and that the parameter is fold. Then open the phone once and preview the widget again.
  • A missed or extra count. Tap the play button in Scriptable and use Add one fold or Undo last fold. The script also ignores a second trigger within three seconds, which stops quick double triggers from inflating the total.
  • Scriptable opens every time. Turn off Run In App on the Run Script action.
  • You want the count on two devices. The script saves to the phone only, on purpose. The trigger only exists on the Duo anyway.

What we checked, and what we did not

  • Checked: the trigger's existence and name from MacRumors and iPhoneSoft reporting; the Scriptable functions the script uses from Scriptable's official documentation; and the script's counting, three second duplicate guard, undo, reset and all five widget layouts, run against a simulation of Scriptable's tools on October 29, 2026.
  • Not checked: we have not run it on an iPhone Duo. Exact menu names inside Shortcuts come from news reports, not from Apple's own help pages, which did not list the trigger when we looked.

Our take

As a widget, this is a fun way to see how often you actually reach for the big screen, and a good first project if you have never written a Shortcut. Set it up in ten minutes and enjoy the chart.

The more useful part for a business owner is the trigger underneath it. "Opened" is a new moment iOS can react to, and the same automation can do work instead of counting. Opening the phone could open your booking calendar or point of sale app, since Shortcuts can open any app. Closing it could lock the screen, the automation MacRumors and others have shown. If you only build one hinge automation, build the one that saves you a tap every time you open the phone at the counter. Keep the counter if you like the chart; skip it if your Home Screen space is better spent on a calendar or sales widget.

Frequently asked questions

Is there an app that counts folds on the iPhone Duo?

The widget going around social media is made with Scriptable, a free scripting app, and a Shortcuts automation. You can build the same thing with the free script on this page; no paid app is needed.

Does the fold counter work on other iPhones?

No. The Opened or Closed automation trigger is specific to the iPhone Duo and iOS 27.1. On other iPhones the script still runs, but nothing would tell it the phone was opened.

Does the script drain the battery or use data?

It runs for a moment each time you open the phone, saves a tiny file and never connects to the internet. The widget refresh schedule is controlled by iOS.

Is my fold count private?

Yes. The count is stored in Scriptable's folder on your phone. The script sends nothing anywhere, and Scriptable's App Store listing says the app collects no data.

How much does the iPhone Duo cost in Canada?

Apple Canada lists the iPhone Duo from $2,999 for 256GB, $3,299 for 512GB, $3,899 for 1TB and $4,799 for 2TB, before tax.

Can I reset the count?

Yes. Tap the play button on the script in Scriptable and choose Reset to zero, or run the script from Shortcuts with the parameter reset.

Sources

Related guides

All guides

Tools for this job

What each tool costs and where its free plan stops.

All AI Tools

Wave

Invoicing and bookkeeping

Invoicing and bookkeeping software with a free Starter plan and a paid Pro plan priced in Canadian dollars.

Free plan

Calendly

Booking and scheduling

An online booking page that lets customers pick an open time from your calendar, with a free plan for one type of appointment.

Free plan

ChatGPT

AI assistants

A general AI assistant for drafting, rewriting, brainstorming and answering questions, with a free plan and paid individual and team plans.

Free plan