Skip to content
macOSDeep Dive Published Updated 7 min readViews unavailable

Uniform Type Identifiers on macOS: Document Types, Conformance, and Routing

Model macOS document formats with UTType declarations, conformance, extension mappings, safe import checks, and dependable Finder routing.

Uniform Type Identifiers provide a shared vocabulary for describing files and other data passed between apps. A UTType is more than a filename extension: it carries an identifier and relationships to other types, such as a custom project document conforming to public.data and public.content. The system uses these declarations when it presents document choices, routes open requests, transfers pasteboard data, and determines which apps can handle a file.

Treat the type declaration as part of the file-format contract. A typo in an identifier, an incorrect conformance, or a missing document registration can make a valid file invisible in an Open panel or launch the wrong handler from Finder. Conversely, a correct type declaration does not validate the bytes inside a file. Routing and content validation solve different problems.

Choose between exported and imported types

Declare an exported type when your app is the canonical owner of a proprietary format. Declare an imported type when your app consumes a format owned by another organization or not already declared by the system. If another app already owns a public identifier, reuse that identifier rather than inventing a second identifier for the same data.

Type identifiers should be unique and stable. Apple recommends reverse-DNS identifiers; do not use reserved prefixes such as public, dyn, or com.apple for your own type. Once documents exist in users’ folders, changing the identifier is a compatibility and routing migration, not a cosmetic rename. Keep old identifiers readable during a transition or provide an explicit conversion path.

Define conformance based on what the content actually is. A document format should conform to public.content directly or through an appropriate parent, and Apple advises that a document type conform to public.data or com.apple.package so the system can represent it. Add a functional conformance such as public.json only if the format is actually JSON-based. A type hierarchy is a statement about substitutability, so overclaiming conformance can expose files to handlers that cannot parse them.

import UniformTypeIdentifiers

extension UTType {
    static let exampleProject = UTType(
        exportedAs: "com.example.editor-project",
        conformingTo: .json
    )
}

func canImportAsProject(_ url: URL) -> Bool {
    guard let detected = UTType(filenameExtension: url.pathExtension) else {
        return false
    }
    return detected.conforms(to: .exampleProject)
}

The type declaration in code is useful for typed checks and document APIs, but it does not replace registering the type and the app’s document role in the built app bundle. Verify the generated Info.plist, not only the source property list or an Xcode editor pane. The filename-extension initializer resolves a system type based on a tag; it is not a parser for the file bytes and can return no result or a broader type than the format expects.

Register document formats in the bundle

For an exported type, the app bundle uses UTExportedTypeDeclarations; imported types use UTImportedTypeDeclarations. Each declaration can provide the identifier, parent types, a localized description, and tag specifications such as filename extensions or MIME types. Keep the declaration consistent with the parser and writer shipped by the app.

The app’s document type declaration is a separate registration. It associates the app with the UTType it can open or export and defines the role the app plays. An exported file type declaration says who owns the type; the document declaration says which app handles documents of that type. Test both after archiving and signing the actual app, because development and distribution bundles can differ.

Do not claim an extension that your parser does not support. If older versions wrote a different schema under the same extension, keep the type stable and version the file format internally. A MIME type is a tag mapping, not proof that every HTTP server or external app will interpret the file identically. Document the canonical extension and MIME value in the format specification and test interoperability.

Use conformance for capability, not trust

conforms(to:) checks whether the type is equal to or directly or indirectly conforms to another type. This is useful for accepting categories of data, such as image or text content, without maintaining a brittle list of every subtype. Prefer the narrowest appropriate type when a workflow needs format-specific behavior.

Conformance is not a safety check. A filename can be renamed, a package can be malformed, and untrusted data can claim a type it does not actually contain. After the user selects a file, validate size limits, parse structure, handle truncated input, and reject unsupported versions. Never use a file extension alone to decide whether data is safe to decode or execute.

Dynamic UTTypes can represent unknown tags when the system cannot resolve a declared type. Treat a dynamic result as “the system has no stronger declaration,” not as a stable public type or a promise of interoperability. If your app owns a format, declare it consistently so it does not depend on a per-file dynamic identifier.

Keep file routing and file access separate

UTType answers what kind of data a URL claims to represent. It does not grant access to the URL, coordinate concurrent edits, or guarantee that the file remains unchanged between inspection and opening. Use the document and file coordination mechanisms appropriate to the app’s sandbox and document workflow. Re-check the file at the point where it is actually read.

When configuring an Open panel, provide accepted content types that match the parser. If the interface lets the user select directories or packages, decide that explicitly rather than relying on extension filters. When a file arrives from Finder or another app, validate the incoming type and then validate its contents. Keep the open handler idempotent so repeated open events for the same URL do not create conflicting document instances.

For drag and pasteboard flows, advertise the types your app can truly provide and consume. A representation may be a file URL, a data payload, or a promised file created later; these are different transfer contracts. Select the representation that preserves the intended content and lifetime, and do not infer that every receiver supports every conformance ancestor.

Versioning, localization, and testing

Keep identifiers machine-stable and localize only descriptions intended for people. Do not derive identifiers from localized product names. If the format evolves incompatibly, version the document schema or define a new type only when the new format is semantically a distinct type. Preserve backward compatibility in the reader or give users an explicit migration tool.

When importing a batch of files, do not stop at the first unsupported item without telling the user which file failed. Validate each file independently, preserve successful imports, and report a summary that distinguishes an unknown type from a known type with malformed contents. For a bulk export, choose the destination type once and ensure every output actually conforms to the same declared format. A mixed extension list in one exporter is an invitation to route output inconsistently.

Treat package formats deliberately. A package is a directory presented as one document-like item; it is not equivalent to a flat data file with a familiar extension. If a type conforms to com.apple.package, ensure the app’s document reader and migration logic handle the package structure rather than attempting to parse the directory as a byte stream. Test package presentation in Finder and the app’s open workflow.

Document import/export tests should run against the archived app in a clean user account. Check the compiled bundle’s exported declarations, imported declarations, document type role, extension tags, and URL handler behavior. A source Info.plist can look correct while build settings merge or replace keys in the final bundle. Validate the artifact users install and keep a fixture file for every supported schema revision.

Test the archived app’s Info.plist entries, exported and imported type behavior, extension-to-type mapping, Open and Save panels, Finder open routing, Quick Look or sharing behavior where applicable, and drag-and-drop with a second app. Test a missing extension, unknown extension, mismatched content, corrupt file, old schema, and two installed apps that can handle the same type. Verify what Finder displays after a Launch Services registration update and a clean install.

Record the detected identifier, parser version, and validation result in diagnostics, but avoid logging document contents or sensitive paths. A clear “unsupported format” error should distinguish unknown type, invalid bytes, incompatible schema, and access failure. This helps support teams solve the right layer instead of changing file associations to mask a parser bug.

Uniform Type Identifiers make format relationships and app routing explicit. Keep the identifier stable, declare accurate conformance, register the document role, and validate bytes independently. That combination makes file handoff predictable without mistaking metadata for proof of content or permission.

Related:

Sources:

Comments