Change the default app for a file type on a Mac: making it stick

Setting the default app for a file type on a Mac takes about five seconds, and a surprising number of people do it more than once for the same file type. The setting is applied, it works, and a week or a software update later the old app is opening those files again. Or it holds for .txt and does nothing for .md, even though both are plain text and both were sitting in the same folder when the change was made.

Neither is a bug. macOS records this preference against something more specific than a file extension, and several ordinary events overwrite it. Understanding what is actually being stored makes the difference between setting it once and setting it every month.

The setting is keyed to a content type, not an extension

macOS identifies files by a Uniform Type Identifier, a reverse domain string that describes what the file is. The extension is only one of the clues used to arrive at it. mdls will show the identifier for any file:

mdls -name kMDItemContentType notes.md

For four ordinary text files the answers are all different:

Extension Content type identifier
.txt public.plain-text
.md net.daringfireball.markdown
.csv public.comma-separated-values-text
.json public.json

This is the single most useful fact about the whole mechanism. Changing the default app for .txt files changes nothing about .md files, because the preference was written against public.plain-text and a Markdown file is not that type. It is a related type, and the relationship is visible too:

mdls -name kMDItemContentTypeTree notes.md

That returns a list including public.item, public.text, public.plain-text and net.daringfireball.markdown. Markdown conforms to plain text, which is why a text editor can open it at all, but conformance is not identity, and the handler lookup uses the specific type first.

The practical consequence is that a reader who wants one editor for everything they consider "text" has several separate changes to make, one per identifier, and the list is longer than expected once .log, .yml, .conf and .sh are included.

The two changes Finder offers

Apple's own documentation splits this into two operations, and they behave very differently.

Opening a file once with a different app leaves the default alone. Any of these work: drag the file onto the app icon in Finder or the Dock, select the file and choose File followed by Open With, Control-click the file and choose Open With, or open the app and use File followed by Open.

Changing the default permanently goes through Get Info. Select the file, choose File followed by Get Info, or Control-click and choose Get Info. Expand the "Open with" section, choose the app from the pop up menu, then click Change All. Apple's note on this is worth reading literally: do not click Change All if the intent is only the one selected file. Choosing the app in that menu without clicking Change All sets an override on that single file and leaves every other file of the type untouched.

That per file override is its own source of confusion. A file carrying an override opens in the chosen app no matter what the system default says, and nothing in Finder's list view indicates which files carry one. A folder where "some of them open in the right app and some do not" is usually a folder with a mix of overridden and non overridden files, not a broken system setting.

If a file will not open in the chosen app at all, Apple's guidance is that the file may be damaged or simply not recognised by that app, and trying a different app is the diagnostic.

Where the choice is actually stored

The system default handlers live in a property list inside the user's preferences:

~/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist

Reading it shows the shape of the record:

plutil -p ~/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist

Each entry under LSHandlers pairs a thing to be opened with the bundle identifier of the app that should open it. A typical entry carries LSHandlerContentType set to something like com.adobe.pdf and LSHandlerRoleAll set to com.apple.preview.

Two details in that structure explain behaviour people find strange.

First, the app is recorded as a bundle identifier, not as a path. Two copies of the same application, one in the Applications folder and one still sitting in Downloads, share an identifier, and the system opens whichever copy it has registered. Deleting the copy that was intended to win does not change the preference, because the preference never named a location.

Second, some entries carry LSHandlerURLScheme instead of a content type. The default browser, the default mail client, and every custom scheme an app installs are handled by the same list through a different key. This is why the default browser setting lives in System Settings rather than in Get Info: it is not a file type at all.

Five reasons the setting comes undone

An app re-registers itself. Applications declare the document types they can open in their own bundle, and macOS reads those declarations when an app is installed, updated, or moved. An app that declares itself the preferred handler can reclaim a type on its next update. Nothing was lost, the claim was simply renewed by the other side.

The chosen app went away. If the recorded bundle identifier no longer resolves to an installed app, the system falls back to whatever else claims the type. Uninstalling an editor quietly hands its file types to the next candidate.

The change was made on a file with an override. Selecting a file that already carries a per file override and then setting the default from its Get Info window produces an inconsistent result: that file behaves, its neighbours do not.

The extension changed but the type did not, or the reverse. Renaming notes.txt to notes.md moves the file to a different content type and therefore to a different handler. Files arriving from other systems without any extension may have no usable type at all, and those fall back to a generic handler regardless of what has been configured.

The registration database is stale. LaunchServices keeps a database of registered applications, and it can fall out of step with the disk after a heavy round of installs, restores, or migrations. Symptoms are duplicate entries in the Open With menu and an app that appears in the list but never actually opens anything.

That last case is the only one with a maintenance answer rather than a configuration answer.

Forcing a re-registration

The tool that manages the database ships with macOS, at a path long enough to be worth saving. It sits inside the CoreServices framework, under LaunchServices.framework/Versions/A/Support/lsregister, and two of its options are useful.

-dump prints the database contents, which is how duplicate Open With entries get confirmed rather than guessed at. Forcing a rescan re-reads every bundle's declarations: pass the applications folder as the path, with -f to force the update even when a bundle's modification date has not changed, and -r to walk the directory. The tool also has a -delete option, and its own help notes that deleting the database requires a restart afterwards, which places it firmly in the category of last resort rather than routine cleanup.

Before reaching for any of this, the cheaper fix is to make sure each application exists in exactly one place on disk. A single copy in the Applications folder removes an entire class of ambiguity, and duplicated app bundles in Downloads or on an external volume are the most common reason the database has something to be confused about.

Auditing what is already set

Before changing anything, it helps to see the current state, because the list is longer than anyone expects. On a Mac in everyday use the handler list commonly runs past a hundred entries, accumulated over years of installing and removing software. Very few of them were set deliberately.

Printing the property list with plutil -p gives the whole set, and three things in it are worth scanning for.

Entries pointing at apps that are gone. The bundle identifier in LSHandlerRoleAll is just a string, and nothing removes it when the app is deleted. These are the entries behind "this file type opens in nothing sensible" complaints, and they are obvious once the list is in front of you: an identifier for software that is no longer installed.

Entries for URL schemes rather than file types. Anything with LSHandlerURLScheme is a link handler, not a file handler. Apps that install a custom scheme to be launched from a browser appear here, and an unfamiliar scheme name is a reasonable prompt to check what put it there.

The modification timestamp. Each entry carries LSHandlerModificationDate. Entries written by a deliberate Change All have a recent value, while entries inherited from an installer often carry zero. That difference is a quick way to separate choices that were made from claims that were simply accepted.

The roles are the other thing to notice. An entry can name a handler for all uses, or separately for viewing and for editing, which is why a PDF can preview in one app and open for editing in another. Get Info sets the all uses role, so a reader who wants that split has to configure it through a command line tool rather than through Finder.

Doing this audit once produces something more useful than a fixed setting: an accurate picture of which types matter, which is exactly the list needed in the next section.

Setting many types at once

There is no Apple provided way to set handlers for a list of content types in one operation. Get Info and Change All work one type at a time, which is tolerable for three types and tedious for twenty.

Two approaches cover the gap. The first is to accept the tedium once and write down the list, so that the next clean install is a checklist rather than a rediscovery. Recording the content type identifier next to each app, gathered with mdls as above, makes that list precise instead of approximate.

The second is a third party command line tool. duti is the established one, it is not included with macOS, and it takes a bundle identifier, a content type, and a role, which means a text file can hold the whole configuration and be replayed on a new machine. Anyone maintaining several Macs, or reinstalling often, gets more from an hour spent on that file than from any amount of clicking.

Both approaches lean on the same underlying idea: the handler configuration is data, and data that only exists as a series of past clicks cannot be restored. Keeping a record of which type maps to which app is a small file, and it is the difference between fixing this once and fixing it after every migration. Where that file lives alongside the folders it describes is part of the same question as how the rest of the toolset is arranged.

What to change first

Run mdls -name kMDItemContentType on one of the files that is opening in the wrong app, and set the default from Get Info against that specific type rather than assuming the extension is what matters. If the list of types worth configuring runs past three or four, write them into a file now, while the answers are in front of you. Keeping that file next to the folders it governs, in the same place the terminal is already open, is the argument behind Atriens.

Frequently asked questions

Why did changing the default app for `.txt` not affect my `.md` files?

Because macOS stores the preference against a content type identifier rather than an extension. A .txt file is public.plain-text and a .md file is net.daringfireball.markdown, so they are two separate settings. Run mdls -name kMDItemContentType on a file to see which identifier to configure.

What is the difference between choosing an app in Get Info and clicking Change All?

Choosing the app without clicking Change All sets an override for that one file only. Clicking Change All writes the system default for every file of that content type. A folder where only some files open correctly usually contains a mix of the two.

An app keeps taking a file type back after every update. Can that be prevented?

Not directly. Applications declare which document types they handle in their own bundle, and macOS re-reads those declarations when the app is updated. The setting has to be reapplied, or the app has to be replaced with one that does not claim the type.

How can duplicate entries in the Open With menu be cleared?

They usually mean the LaunchServices database has stale records, often from having more than one copy of the same app on disk. Keep a single copy in the Applications folder, then force a rescan with the lsregister tool inside the CoreServices framework, passing the applications folder with the force and recursive options.

Back to all posts