25 Jan 2025

Make any webpage a standalone app (even if it’s not a PWA)

Sometimes it’s nice to be able to use webapps as first-class desktop apps rather than inside a browser tab.

This is easy enough to accomplish for websites that offer PWAs, but for those that don’t I usually use a combination of these two tools to get a more “app like” experience:

  • Webcatalog - these guys have a full catalog (hence the name) of pre-packaged apps that you can install with one click (and iirc with some fiddling you can set it up custom sites too).

    But the main perk is that they’ve figured out how to make Google apps not realize they’re running as dedicated apps, which Google, in their wisdom, often makes difficult. So use this if you want to run Gmail or Google Calendar as dedicated apps.

    Their main drawback is that there’s a limit on how many apps you can install (that you can lift by paying them), and they’ve made some questionable changes to the defaults lately: in recent versions they’ve added a somewhat annoying “toolbar” anti-feature that inserts a webcatalog toolbar over the top of the app, and when you click a link it now shows an intrusive popup asking you where you want the link to open. That said, both of these minor irks can be disabled in the settings, so I still use it.

  • Multi is an open source app that can turn any website into an app. It’s simple, and the gui is pretty minimal (you have to edit some JSON yourself), but it reliably gets the job done.

And if you’re more the DIY type, it’s actually pretty easy to roll your own using just Chrome itself with the help of its --app flag. Here’s an example script I sometimes use for this purpose:

#!/bin/sh

echo "What should the Application be called (no spaces allowed e.g. GCal)?"
read inputline
name=$inputline

echo "What is the url (e.g. https://www.google.com/calendar/render)?"
read inputline
url=$inputline

echo "What is the full path to the icon (e.g. /Users/username/Desktop/icon.png)?"
read inputline
icon=$inputline




chromePath="/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome"
appRoot="$HOME/Applications"



# various paths used when creating the app
resourcePath="$appRoot/$name.app/Contents/Resources"
execPath="$appRoot/$name.app/Contents/MacOS" 
profilePath="$appRoot/$name.app/Contents/Profile"
plistPath="$appRoot/$name.app/Contents/Info.plist"

# make the directories
mkdir -p  $resourcePath $execPath $profilePath

# convert the icon and copy into Resources
if [ -f $icon ] ; then
    sips -s format tiff $icon --out $resourcePath/icon.tiff --resampleWidth 128 >& /dev/null
    tiff2icns -noLarge $resourcePath/icon.tiff >& /dev/null
fi

# create the executable
cat >$execPath/$name <<EOF
#!/bin/sh
exec $chromePath  --app="$url" --user-data-dir="$profilePath" "\$@"
EOF
chmod +x $execPath/$name

# create the Info.plist 
cat > $plistPath <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" “http://www.apple.com/DTDs/PropertyList-1.0.dtd”>
<plist version=”1.0″>
<dict>
<key>CFBundleExecutable</key>
<string>$name</string>
<key>CFBundleIconFile</key>
<string>icon</string>
</dict>
</plist>
EOF