Filters

A filter is what stops your tweak being loaded into every process on the machine. For a plain dynamic library it is a .plist of the same name sitting beside it. For a bundle there are several places it can live, and the loader checks them in a fixed order.

Where the filter lives in a bundle

The first of these that exists is the one used, so a file earlier in the list wins over anything later:

#Path inside the bundleNotes
1Contents/Info.plist Only if it has a Filter dictionary. Checked before everything else.
2Contents/Resources/Filter.plistThe usual place.
3Contents/Filter.plist
4Contents/Info.plist Again, this time accepted even without a Filter key.

Putting the filter in Info.plist keeps a bundle down to one plist. Keeping it in Contents/Resources/Filter.plist keeps it separate from the bundle's own metadata. Either is fine; just do not ship both, because Info.plist will win and the other will look like it is being ignored for no reason.

Keys and accepted values

KeyTypeAccepted values and matching
Bundlesarray Bundle identifiers. Exact match, case-insensitive. Matches the main bundle or any bundle already loaded in the process, so a framework identifier matches too.
Executablesarray Executable file names. Exact match on the last path component, case-insensitive. Not a substring match.
Classesarray Objective-C class names. Matches when the class exists in the host. Exact and case-sensitive.
ExcludeBundlesarray Bundle identifiers that must never match. Evaluated before everything else; same comparison as Bundles.
Typestring Only App and Binary are evaluated. App requires the host to be an application, Binary requires that it is not. Any other value, including Any, imposes no restriction.
Privilegestring Only Root and User are evaluated, case-insensitively, against the effective user ID. Root requires euid 0, User requires non-zero.
CoreFoundationVersionarray One or two numbers, [minimum, maximum). The minimum is inclusive, the maximum exclusive. Elements past the second are ignored.

How the keys combine

Bundles, Executables and Classes are alternatives: any one matching is sufficient. Type, Privilege, CoreFoundationVersion and ExcludeBundles are restrictions applied on top of them.

An empty filter matches nothing. A filter with only Type or only Privilege matches every process that passes it, which is allowed and occasionally what you want. Be deliberate about it: loading into everything is a lot of processes.

Example

Filter.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Bundles</key>
    <array>
        <string>com.apple.dock</string>
    </array>
    <key>Privilege</key>
    <string>User</string>
</dict>
</plist>