MacOS Bundles (and Packages) Explained

Table of Contents

When is a file more than just a file? When it’s actually a folder that Finder is lying to you about. Every program, every kernel extension, and everything Time Machine creates on a network share is.. a bundle. Even the Photos Library file in ~/Photos… well you get the point. Let’s unpack these things, shall we?

First, a little note: while I’m going to use the terms bundle and package almost interchangeably throughout this (following Apple’s own lead), there is a difference:

  • A package is a directory that Finder presents as a singular file.
  • A bundle is a package with a standardized structure that holds executable code and its resources.

But, yes, Finder does indeed lie to you. Of course, the natural first question is why? Well, since their main usage is delivering applications, they make for easy transport (since anyone who’s installed something knows the 1-step process), and they mean that users actually have to go out of their way to make breaking changes, like misplacing a critical resource file away from the app that needs it.

It’s possible to go on with a mac the entire time and not really every realize that these ‘files’ aren’t files. Even the ever-loved and ever-resented kernel extensions that Hackintoshers need… .kext files are packages:

The inner contents of a Kext

Viewing Bundles

To view the contents of a bundle, there’s two main ways: Finder and the Terminal.

Terminal

The Terminal never lies, it properly represents bundles as what they really are: fancy folders. It even shows the name extension that Finder hides:

List of the Applications folder showing lots of “.app” folders

And you can just poke around like a normal folder:

Inside of an application bundle

Finder

If you right-click (or ⌘-click) a bundle, there will be a new item: “Show Package Contents”, which you can use to open the folder like it was… a folder:

“Show Package Contents” option

Package contents, shown

Making Your Own

There’s actually three ways to do so:

  • Use an extension like .kext, .app, or .bundle (not an exhaustive list)
  • Use an extension that a program has defined in its Info.plist as a package type
  • Set the package bit (b)

To put that second option simply, you need a LSTypeIsPackage key with an appropriate value in the CFBundleDocumentTypes key. Really, if you’re a programmer that needs the feature, you already know about it, and if you’re anyone else, this all means nothing and you can ignore it.

For the third, you need (at least, the way I’ve discovered needs) the Xcode command line tools. These will give you two commands: GetFileInfo and SetFile. And yes, they are shell commands that are capitalized. I’m glad that tab-completion in Zsh is a thing, and that Zsh is the default Terminal shell in Catalina.

If you GetFileInfo a directory, you’ll see this:

Output of GetFileInfo

The most important part there is the attributes line, with that letter cluster. Now, the encoding here is that a lowercase letter means that attribute is not set, and an uppercase letter means that attribute is set. The only ones we care about right now are b, c, and later, e

To mark a directory as a bundle, say, the directory MyBundle, then run:

$ SetFile -a B MyBundle

Note: the space there is important, if you type -aB instead of -a B, you’ll get this error:

ERROR: File Not Found. (-43)  on file: -aB

The folder’s icon won’t change, but it now has the “Show Package Contents” option, and double-clicking it does nothing. If you’d like to change its icon, we’ll get to that in a second.

Using the Extensions

Naming your folder with a name that ends in .app, .bundle, .framework, .kext, .plugin… all result in a bundle format. Note that names are given special meaning, making a folder named MyBundle.app, for example, will just result in an icon that pretty much says “broken application”, and an error on double-click:

Application may be damaged or incomplete

However, just .bundle has no real associations with it, feel free. It will show the Kext LEGO-brick thing, and it will complain that there’s no app available to open it. If you want to hide the .bundle suffix, you can either right-click, Get Info, and under “Name & Extension”, check “Hide extension”:

Get Info panel with Hide extension checked

or, you can SetFile -a E MyBundle, because the e flag is to hide the extension.

There’s just one other thing to play around with here: custom icons

Custom Icons

This is where the c attribute that I mentioned earlier comes in.

If you look closely, in the top left corner of the Get Info panel, there’s a little thumbnail of the folder’s icon. This can be selected. If you click to select it then press ⌘-v to paste, it will swap out the icon for the image you just pasted in. Note that, when selected, it can also be copied as image data, making it possible to copy the look of one app or other file onto another.

Take, I don’t know, my logo:

My logo

If we double-click to load that in Preview, then ⌘-a to select all, ⌘-c to copy, go over to the info panel for the bundle, select the thumbnail, and ⌘-v to paste, we get this:

Changed icon, desktop

Changed icon, info panel

And if you ever want to revert it all back to default, just SetFile -a c MyBundle to clear it.