color
scheme

March 27, 2025

Ifthe officially recommended folder structure for application bundles in macOS has always felt a bit arbitrary and arcane to you, this guide is for you. These methods are seemingly undocumented, but have worked for years.


Method 1:

Simply add .app to the filename of your binary executable. Amazingly, this works. You won't get the benefits of a .plist file, and your resources will live outside of the bundle (unless you compile them into the binary itself). To add an icon to your app, see the first method in section on this below.

The dumbest possible way

Method 2:

Put your binary and associated resources inside a folder, and add .app to the name of this folder. That's right, you never needed to create all those folders normally found in app bundles. It's worth noting that this simpler structure is more or less how Apple recommends iOS applications be put together, but it works for mac as well.

<plist>
<dict>
    <key>CFBundleExecutable</key>
    <string>your_binary</string>
    <key>CFBundleName</key>
    <string>Your Application</string>
</dict>
</plist>
What more do you need?



Adding an icon to your application

Here are a couple ways to add an icon to your app. You can use a PNG, ideally resized to a power of two. Optionally, follow this guide to make a proper icon set with various scales. Note that it can take some prodding for the cached icon to be updated in Finder. This can be done with touch /path/to/App.app in Terminal. To get the path to your app, right click on it, hold the option key, and select "Copy as Pathname."

Method 1:

  1. From Finder, copy the file for your icon. This can be a single PNG.
  2. Open the info panel of your application with "Get Info" from the context menu.
  3. Select the icon in the upper left.
  4. Paste with ⌘+v

Notes:

Method 2:

  1. Copy your icon file inside your bundle folder. Give it a name like "Icon.png"
  2. Create a Info.plist file inside your application bundle, with the following contents:
<plist>
<dict>
    <key>CFBundleIconFile</key>
    <string>./Icon.png</string>
</dict>
</plist>

Notes:

About resource loading on macOS:

Loading resources on macOS that differs from other operating systems: When loading a resource in code using a relative directory (with C fopen() or the like), most operating systems default to the executing directory. MacOS does not. When launching an application from Finder, MacOS defaults to the user's home folder when resolving a relative directory.

A common pitfall here is that MacOS will default to the executing directory when launching from the command line, which is how the developer will be launching their code when debugging, so this discrepancy can go unnoticed until a release build is made. So, care must be taken to change directories to the executing directory from code. The executing directory should be relative to the binary, not the containing app bundle.

In practice, for me this means changing the directory once at the top of my program entry point. If you happen to be using Raylib, this can be done as simply as:

ChangeDirectory(GetApplicationDirectory())

I recommend taking a look at the source for this function here.

If you are not using Raylib, depending on your language, finding the executing directory could be more difficult. It is simple in C#:

Environment.CurrentDirectory = AppDomain.CurrentDomain.BaseDirectory;

For Odin, I recommend something like the following:

package my_app
import "core:os"
import "core:sys/darwin/Foundation"

main :: proc() {
    when ODIN_OS == .Darwin {
        resourcePath := Foundation.Bundle_mainBundle()->resourcePath()->odinString()
        os.set_current_directory(resourcePath)
    }
}

bundlePath() may also be used, but resourcePath() works in my minimal case as well as in the case that you opt for a full vanilla folder structure for your bundle.