What Makes an macOS "Package" Show as a Package?

A package is just a directory, apparently, but somehow Finder and Path Finder know it's a package.

How is this indicated in the file system? I've tried a few changes to a directory to make it a package but to no avail.


Short answer

To register a document as a package, the developer of DayOne would have had to modify the document type information in the DayOne app's information property list (Info.plist) file.

Long Answer

A package is a file system directory used by macOS and iOS. It's basically a directory that will appear as a single file, but which may in fact contain a preserved state of files and folders. So, a folder could have a number of subfolders and many files, but as a package it will be displayed to users like a single file. For example, in macOS the Finder will see a package as a single file to avoid users mucking around with the actual contents. However, users can view the contents through various methods such as right-clicking on the package and selecting Show Package Contents from the context menu.

Apple's Bundle Programming Guide, available on their Developer site, has a whole section on Document Packages. Below are some relevant quotes:

To register a document as a package, you must modify the document type information in your application’s information property list (Info.plist) file. The CFBundleDocumentTypes key stores information about the document types your application supports. For each document package type, include the LSTypeIsPackage key with an appropriate value. The presence of this key tells the Finder and Launch Services to treat directories with the given file extension as a package. For more information about Info.plist keys, see Information Property List Key Reference.

Document packages should always have an extension to identify them—even though that extension may be hidden by the user. The extension allows the Finder to identify your document directory and treat it as a package. You should never associate a document package with a MIME type or 4-byte OS type.

Source: Bundle Programming Guide

On the off-chance you can't access the document, let me know and I'll include some additional quotes from the page.


As I have made a document-based application for macOS, I may help you understand a bit. Although @Monomeeth's answer is right, dealing with GUI is a lot better than Info.plist. People usually use another path. They subclass NSDocment, which provides the basis for file/bundle saving and loading. Then, people go to Xcode and under the "General" tab, define a document type, then go to "Exported UTIs" and export an UTI that conforms to com.apple.package. They read in with the NSFileWrapper class. This way, people won't have to do some tedious work with stuff like NSData.

If you want to create a bundle, check this answer. Simply create an empty macOS app and edit the "Exported UTIs" sections to taste.


Had the same question, and found all the answers far from satisfactory. Here's my take (after 2 weeks of research).

Most important thing to understand: MacOS (also iOS, iPadOS tvOS, watchOS) maintains some dynamic database of registered Universal Type Identifiers. Can't elaborate much here - big subject - but I'll say that a formal UTI can be paired with several "tags" (identifiers from other "spaces", like for example --- a filename Extension.

Every time a new application hits the Mac file-system, the OS peers into its Info.plist, and adds whatever new type definitions it finds there to its database.

The MacOS Framework responsible for communicating with this database is called LaunchServices and I now saw many of its APIs are deprecated and are to be replaced with the new UniformTypeIdentifiers framework - only available in MacOS Big Sur (11.x) and onward.

These frameworks are used by Finder, and all other facilities, to determine what data type to use for specific File, Directory, Package, data-in-memory, Paste-board object etc.

Some higher-level Cocoa objects (NSURL, NSFileManager, NSWorkspace and others) also wrap around these frameworks, to provide type-related functionality (e.g. to give you the URLs of applications that can open a specific data-type, or to know whether a file/directory is actually a document's package, or to have an educated guess about a file for which you do NOT have an application on your Mac).

To directly answer the OP's question - I was NOT able (even looking down at kernel level) to find a "package bit" or "bundle bit" you can set to a directory - but EVEN IF IT EXISTS, it will NOT determine OS behavior towards some directory. It is always LaunchServices being consulted on the matter.

Another important distinction is between a Bundle and a Package. A "Package" is a file-system-object (file, directory, alias, whatever) that is to be presented and manipulated AS IF it was a single object (document file). If anything relies on the mysterious "bundle bit" --- it is the "Package" aspect of a directory/file/whatever.

A "Bundle" on the other hand, is a FORMAL STRUCTURE of a directory, including an Info.plist with required entries, and other elements (such are apps, kernel-extensions, plugins etc.) that can be a package (like .app) or not (like .framework).

I don't know if OPs question comes from their development needs, but I'll provide here a few related APIs that can be helpful.

BOOL isPackage = [[NSWorkspace sharedWorkspace] isFilePackageAtPath:_filePath ]; // will only help with EXISTING path. DO READ the documentation in the header for this method.

NSURL *fileURL = [NSURL fileURLWithPath:_documentPath isDirectory:YES];
NSError *error = nil;
NSNumber *value = nil;
isPackage = ([fileURL getResourceValue:&value forKey:NSURLIsPackageKey error:&error] && [value boolValue]); // a more powerful alternative - read header for more "resource keys" you can use in this method - e.g. for determining content-type and not only the package. Again, this is meant to be used for existing files/directories.

 // this one is lower-level Core-Foundation tool that will work for non-existing objects - a "what if" question to LaunchServices.  
 CFArrayRef types = UTTypeCreateAllIdentifiersForTag( kUTTagClassFilenameExtension, (__bridge CFStringRef _Nonnull)(_filenameExtension), kUTTypePackage);
 CFIndex typeCount = CFArrayGetCount(types);
 switch (typeCount) {
     case 0: _isDocument = NO; break; // no package UTI with this filename extension is known to LaunchServices.
     case 1: {
          CFStringRef type = CFArrayGetValueAtIndex(types, 0); 
          isPackage = ! CFStringHasPrefix(type, CFSTR("dyn.")); // LaunchServices may synthesize a dynamic UTI when it doesn't know a filename extension.
          break;
     }
     default: isPackage = YES; break;
 }
 CFRelease(types);