feat(cli): UTExportedTypeDeclarations support for file associations (#14128)

* feat(cli): UTExportedTypeDeclarations support for file associations

closes #13314

* update example

* update readme
This commit is contained in:
Lucas Fernandes Nogueira
2025-10-07 13:12:39 -03:00
committed by GitHub
parent cc8c0b5317
commit 3d6868d09c
10 changed files with 230 additions and 16 deletions

View File

@@ -0,0 +1,6 @@
---
"tauri-cli": minor:feat
"@tauri-apps/cli": minor:feat
---
Added support to defining the content type of the declared file association on macOS (maps to LSItemContentTypes property).

View File

@@ -0,0 +1,6 @@
---
"tauri-cli": minor:feat
"@tauri-apps/cli": minor:feat
---
Added support to defining the metadata for custom types declared in `tauri.conf.json > bundle > fileAssociations > exportedType` via the `UTExportedTypeDeclarations` Info.plist property.

View File

@@ -0,0 +1,5 @@
---
"tauri-utils": minor:feat
---
Added `FileAssociation::exported_type` and `FileAssociation::content_types` for better support to defining custom types on macOS.

View File

@@ -268,6 +268,55 @@ fn create_info_plist(
}
if let Some(associations) = settings.file_associations() {
let exported_associations = associations
.iter()
.filter_map(|association| {
association.exported_type.as_ref().map(|exported_type| {
let mut dict = plist::Dictionary::new();
dict.insert(
"UTTypeIdentifier".into(),
exported_type.identifier.clone().into(),
);
if let Some(description) = &association.description {
dict.insert("UTTypeDescription".into(), description.clone().into());
}
if let Some(conforms_to) = &exported_type.conforms_to {
dict.insert(
"UTTypeConformsTo".into(),
plist::Value::Array(conforms_to.iter().map(|s| s.clone().into()).collect()),
);
}
let mut specification = plist::Dictionary::new();
specification.insert(
"public.filename-extension".into(),
plist::Value::Array(
association
.ext
.iter()
.map(|s| s.to_string().into())
.collect(),
),
);
if let Some(mime_type) = &association.mime_type {
specification.insert("public.mime-type".into(), mime_type.clone().into());
}
dict.insert("UTTypeTagSpecification".into(), specification.into());
plist::Value::Dictionary(dict)
})
})
.collect::<Vec<_>>();
if !exported_associations.is_empty() {
plist.insert(
"UTExportedTypeDeclarations".into(),
plist::Value::Array(exported_associations),
);
}
plist.insert(
"CFBundleDocumentTypes".into(),
plist::Value::Array(
@@ -275,16 +324,27 @@ fn create_info_plist(
.iter()
.map(|association| {
let mut dict = plist::Dictionary::new();
dict.insert(
"CFBundleTypeExtensions".into(),
plist::Value::Array(
association
.ext
.iter()
.map(|ext| ext.to_string().into())
.collect(),
),
);
if !association.ext.is_empty() {
dict.insert(
"CFBundleTypeExtensions".into(),
plist::Value::Array(
association
.ext
.iter()
.map(|ext| ext.to_string().into())
.collect(),
),
);
}
if let Some(content_types) = &association.content_types {
dict.insert(
"LSItemContentTypes".into(),
plist::Value::Array(content_types.iter().map(|s| s.to_string().into()).collect()),
);
}
dict.insert(
"CFBundleTypeName".into(),
association

View File

@@ -2159,7 +2159,7 @@
]
},
"fileAssociations": {
"description": "File associations to application.",
"description": "File types to associate with the application.",
"type": [
"array",
"null"
@@ -2433,6 +2433,16 @@
"$ref": "#/definitions/AssociationExt"
}
},
"contentTypes": {
"description": "Declare support to a file with the given content type. Maps to `LSItemContentTypes` on macOS.\n\n This allows supporting any file format declared by another application that conforms to this type.\n Declaration of new types can be done with [`Self::exported_type`] and linking to certain content types are done via [`ExportedFileAssociation::conforms_to`].",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"name": {
"description": "The name. Maps to `CFBundleTypeName` on macOS. Default to `ext[0]`",
"type": [
@@ -2471,6 +2481,17 @@
"$ref": "#/definitions/HandlerRank"
}
]
},
"exportedType": {
"description": "The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.\n\n You should define this if the associated file is a custom file type defined by your application.",
"anyOf": [
{
"$ref": "#/definitions/ExportedFileAssociation"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
@@ -2552,6 +2573,30 @@
}
]
},
"ExportedFileAssociation": {
"description": "The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.",
"type": "object",
"required": [
"identifier"
],
"properties": {
"identifier": {
"description": "The unique identifier for the exported type. Maps to `UTTypeIdentifier`.",
"type": "string"
},
"conformsTo": {
"description": "The types that this type conforms to. Maps to `UTTypeConformsTo`.\n\n Examples are `public.data`, `public.image`, `public.json` and `public.database`.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
}
},
"additionalProperties": false
},
"WindowsConfig": {
"description": "Windows bundler configuration.\n\n See more: <https://v2.tauri.app/reference/config/#windowsconfig>",
"type": "object",

View File

@@ -2159,7 +2159,7 @@
]
},
"fileAssociations": {
"description": "File associations to application.",
"description": "File types to associate with the application.",
"type": [
"array",
"null"
@@ -2433,6 +2433,16 @@
"$ref": "#/definitions/AssociationExt"
}
},
"contentTypes": {
"description": "Declare support to a file with the given content type. Maps to `LSItemContentTypes` on macOS.\n\n This allows supporting any file format declared by another application that conforms to this type.\n Declaration of new types can be done with [`Self::exported_type`] and linking to certain content types are done via [`ExportedFileAssociation::conforms_to`].",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"name": {
"description": "The name. Maps to `CFBundleTypeName` on macOS. Default to `ext[0]`",
"type": [
@@ -2471,6 +2481,17 @@
"$ref": "#/definitions/HandlerRank"
}
]
},
"exportedType": {
"description": "The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.\n\n You should define this if the associated file is a custom file type defined by your application.",
"anyOf": [
{
"$ref": "#/definitions/ExportedFileAssociation"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
@@ -2552,6 +2573,30 @@
}
]
},
"ExportedFileAssociation": {
"description": "The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.",
"type": "object",
"required": [
"identifier"
],
"properties": {
"identifier": {
"description": "The unique identifier for the exported type. Maps to `UTTypeIdentifier`.",
"type": "string"
},
"conformsTo": {
"description": "The types that this type conforms to. Maps to `UTTypeConformsTo`.\n\n Examples are `public.data`, `public.image`, `public.json` and `public.database`.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
}
},
"additionalProperties": false
},
"WindowsConfig": {
"description": "Windows bundler configuration.\n\n See more: <https://v2.tauri.app/reference/config/#windowsconfig>",
"type": "object",

View File

@@ -1177,6 +1177,12 @@ impl<'d> serde::Deserialize<'d> for AssociationExt {
pub struct FileAssociation {
/// File extensions to associate with this app. e.g. 'png'
pub ext: Vec<AssociationExt>,
/// Declare support to a file with the given content type. Maps to `LSItemContentTypes` on macOS.
///
/// This allows supporting any file format declared by another application that conforms to this type.
/// Declaration of new types can be done with [`Self::exported_type`] and linking to certain content types are done via [`ExportedFileAssociation::conforms_to`].
#[serde(alias = "content-types")]
pub content_types: Option<Vec<String>>,
/// The name. Maps to `CFBundleTypeName` on macOS. Default to `ext[0]`
pub name: Option<String>,
/// The association description. Windows-only. It is displayed on the `Type` column on Windows Explorer.
@@ -1190,6 +1196,24 @@ pub struct FileAssociation {
/// The ranking of this app among apps that declare themselves as editors or viewers of the given file type. Maps to `LSHandlerRank` on macOS.
#[serde(default)]
pub rank: HandlerRank,
/// The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.
///
/// You should define this if the associated file is a custom file type defined by your application.
pub exported_type: Option<ExportedFileAssociation>,
}
/// The exported type definition. Maps to a `UTExportedTypeDeclarations` entry on macOS.
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ExportedFileAssociation {
/// The unique identifier for the exported type. Maps to `UTTypeIdentifier`.
pub identifier: String,
/// The types that this type conforms to. Maps to `UTTypeConformsTo`.
///
/// Examples are `public.data`, `public.image`, `public.json` and `public.database`.
#[serde(alias = "conforms-to")]
pub conforms_to: Option<Vec<String>>,
}
/// Deep link protocol configuration.
@@ -1356,7 +1380,7 @@ pub struct BundleConfig {
/// Should be one of the following:
/// Business, DeveloperTool, Education, Entertainment, Finance, Game, ActionGame, AdventureGame, ArcadeGame, BoardGame, CardGame, CasinoGame, DiceGame, EducationalGame, FamilyGame, KidsGame, MusicGame, PuzzleGame, RacingGame, RolePlayingGame, SimulationGame, SportsGame, StrategyGame, TriviaGame, WordGame, GraphicsAndDesign, HealthcareAndFitness, Lifestyle, Medical, Music, News, Photography, Productivity, Reference, SocialNetworking, Sports, Travel, Utility, Video, Weather.
pub category: Option<String>,
/// File associations to application.
/// File types to associate with the application.
pub file_associations: Option<Vec<FileAssociation>>,
/// A short description of your application.
#[serde(alias = "short-description")]

View File

@@ -11,3 +11,9 @@ This feature is commonly used for functionality such as previewing or editing fi
```
cargo build --features tauri/protocol-asset
```
## Associations
This example creates associations with PNG, JPG, JPEG and GIF files.
Additionally, it defines two new extensions - `taurid` (derives from a raw data file) and `taurijson` (derives from JSON). They have special treatment on macOS (see `exportedType` in `src-tauri/tauri.conf.json`).

View File

@@ -11,5 +11,5 @@ tauri-build = { path = "../../../crates/tauri-build", features = ["codegen"] }
[dependencies]
serde_json = "1"
serde = { version = "1", features = ["derive"] }
tauri = { path = "../../../crates/tauri", features = [] }
tauri = { path = "../../../crates/tauri", features = ["protocol-asset"] }
url = "2"

View File

@@ -1,12 +1,15 @@
{
"$schema": "../../../crates/tauri-cli/schema.json",
"$schema": "../../../crates/tauri-cli/config.schema.json",
"identifier": "com.tauri.dev-file-associations-demo",
"build": {
"frontendDist": ["../index.html"]
},
"app": {
"security": {
"csp": "default-src 'self'"
"csp": "default-src 'self'",
"assetProtocol": {
"enable": true
}
}
},
"bundle": {
@@ -34,6 +37,20 @@
"ext": ["gif"],
"mimeType": "image/gif",
"rank": "Owner"
},
{
"ext": ["taurijson"],
"exportedType": {
"identifier": "com.tauri.dev-file-associations-demo.taurijson",
"conformsTo": ["public.json"]
}
},
{
"ext": ["taurid"],
"exportedType": {
"identifier": "com.tauri.dev-file-associations-demo.tauridata",
"conformsTo": ["public.data"]
}
}
]
}