Programmatically Set Custom Icons for macOS Applications Using Extended File Attributes Without…
Fri Jan 10 2025
Programmatically Set Custom Icons for macOS Applications Using Extended File Attributes Without Invalidating the Signature
Have you ever right-clicked on an application in Finder > Get Info and pasted a custom image for the application? If you haven’t, give it a try. If you look into the application’s metadata with a tool like stat after customizing the icon, it will appear like nothing had changed. Most of the traditional file metadata is stored within the file’s inode, but there is a set of extra metadata as well. These are called extended attributes. You can view some extended attribute values with a tool like mdls, but you won’t get a full picture.
Helpful commands
The most helpful tool for working with extended attributes will be xattr. Extended attributes can be thought of as key-value pairs, and xattr will allow you to interact with them. Here are some useful commands; you can see the full list in the manual page by running man xattr.
- xattr -l <filename> (list active extended attributes)
- xattr -p <attr_name> <filename> (show a specific extended attribute)
- xattr -w <attr_name> <attr_value> <filename> (write to an extended attribute)
- xattr -d <attr_name> <filename> (delete an extended attribute record)
You can add the -x flag to most of these to use hexadecimal.
Setting the custom icon
Now that we know how to manipulate extended attributes, we can set the custom icon for an application or folder (for our purposes they act the same). For this tutorial, I will be setting a custom icon for an app called MyApp located at /Applications/MyApp.app.
First we’re going to have to set the FinderInfo attribute. You can learn more about it here. FinderInfo is a fixed 32 byte value that we can initialize with the following:
xattr -wx com.apple.FinderInfo '00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00' /Applications/MyApp.app
Note the -x flag, which indicates we are passing in a hexadecimal value.
The meaning behind each byte is not incredibly well documented, but if you look hard enough through apple docs, there are definitions for different functionality. We’ll start by enabling the kHasCustomIcon flag, which you can see here. It’s pretty clear that this will signal to macOS that we are using a custom icon. The docs tell us this flag is 0x0400. The header bytes are in the first 8 bytes of the value, so we will start our flag in the 9th byte, which gets us this command:
xattr -wx com.apple.FinderInfo '00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00' /Applications/MyApp.app
If you run this command on an application such as xattr -wx com.apple.FinderInfo '...(find above)' /Applications/<YOURAPP>.app, you will see that the icon turns into the default folder icon, which is the fallback if you don’t provide an icon. MacOS will look for this icon in a file called Icon\r. This will be located at the top level inside of the directory/app you are customizing. You can see this location in an app by right clicking in Finder > Show Package Contents.
Let’s create this icon file.
- cd /Applications/MyApp.app/ (cd into your folder or application)
- touch Icon$'\r' (create a blank icon file)
Setting FinderInfo for the icon file is optional, but a good practice an teaching opportunity, so let’s do it.
xattr -wx com.apple.FinderInfo '69 63 6F 6E 45 4C 49 46 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00' /Applications/MyApp.app/Icon$'\r'
If you look at the command above, you will see some new content in the header and a new flag. The first 4 bytes set the file type (icon) and the second 4 set the creator code (ELIF). Both of these are optional, but I would recommend setting up your header if this is a production application. Lastly you will see that instead of 0x04 the flag is set to 0x40. This is the kIsInvisible flag, which you can see defined here. This will set the file to be hidden. The icon file is generally hidden, and you can see this in Finder by doing “⌘ + ⇧ + .” after manually setting a custom icon.
Next is the key step. We’re going to create a ResourceFork, which is a largely deprecated part of the OS, but is still used for file icons like this. To create a resource fork, we will have to create a resource definition. We can call this file resource.r and create it with touch resource.r.
Also make sure you have a .icns file at the same level as the resource file. If you don’t have a .icns file, you can create one from a png with sips or another tool. With sips use a file with dimensions 256x256, 512x512 or another supported size.
sips -s format icns yourpng.png --out newicon.icns
At this point, your directory or application package should something look like this when you run ls:
- MyApp.app (or a folder)
- └── Icon? (A ‘?’ will be show instead of the ‘\r’)
Somewhere else or inside of that directory you should have these two files:
- └── resource.r
- └── newicon.icns
If your icns file is named newicon.icns and it’s at the same level as your resource.r file, you should set your resource file to something like this:
read 'icns' (-16455) "newicon.icns";
The -16455 is a designated id for custom icons set by Apple. And the “newicon.icns” can be changed to a path.
You can alternatively pass in the raw icns image data like this:
data 'icns' (-16455) {
$'hex here...';
$'more hex...';
}
You can get the hex of an file with xxd like xxd -p newicon.icns
Once the resource description is finalized, we can compile it with Rez. This is pretty simple:
Rez resource.r -o /Applications/MyApp.app/Icon$'\r'
After that you should be all set!
You can verify the signature integrity with this:
codesign — verify — verbose /Applications/MyApp.app
Troubleshooting
If you don’t see the icon changed yet don’t worry. Check the the icon by right clicking > get info. The icon displayed is cached and does not always revalidate quickly. Try quiting finder with killall Finder. If the application is in your Dock, you may need to remove it and re-add it. If it is on your Desktop, it also may help to move it into Documents and back. A few other potential ways to force the cache to refresh:
- qlmanage -r cache
- sudo rm -rf /Library/Caches/com.apple.iconservices.store
If none of these work, wait a few minutes and send me a note in the comments.
—
If this article was helpful, give me a follow on medium & give a 👏!