diff --git a/crates/engine/src/cmd/assets.rs b/crates/engine/src/cmd/assets.rs
--- a/crates/engine/src/cmd/assets.rs
+++ b/crates/engine/src/cmd/assets.rs
@@ -7,7 +7,7 @@ use serde_json::{Map, Value, json};
 use vectorcraft_doc::{ExportAsset, NodeId};
 
 use super::edit::{roots_of, selected_roots};
-use super::fileio::{SCREEN_SETTINGS_KEYS, check_screen_settings, export_crop, export_screens, store_screen_settings};
+use super::fileio::{SCREEN_SETTINGS_KEYS, check_screen_settings, export_crop, export_screens, store_screen_settings, with_screen_export_preset};
 use super::*;
 
 /// The longest asset name kept (characters).
@@ -48,7 +48,7 @@ pub fn specs() -> Vec<CommandSpec> {
             "Export Assets",
             [],
             None,
-            "{assets?: [asset id…] (default all), folder?, zip?, openLocation?, formats?, preset?, settings?, prefix?, subfolders?, antiAlias?} each asset's art alone, cropped to it, in every format row: document.exportForScreens {assets} at the document's export settings (document.exportSettings) under the ones given → what document.exportForScreens returns (no folder: the files, or one zip, as dataBase64). Leaves the remembered settings as they are (assets.settings.set changes them)",
+            "{assets?: [asset id…] (default all), folder?, zip?, openLocation?, formats?, preset?, settings?, prefix?, subfolders?, antiAlias?} each asset's art alone, cropped to it, in every format row: document.exportForScreens {assets} at the document's export settings (document.exportSettings) under the ones given; with subfolders false every file goes into the folder itself, mobile and density rows too → what document.exportForScreens returns (no folder: the files, or one zip, as dataBase64). Leaves the remembered settings as they are (assets.settings.set changes them)",
             has_doc,
             export
         ),
@@ -57,7 +57,7 @@ pub fn specs() -> Vec<CommandSpec> {
             "Asset Export Settings",
             [],
             None,
-            "{formats?, preset?: mobile|density|\"\" (none), settings?, prefix?, subfolders?, antiAlias?} change the export settings the Asset Export panel and Export for Screens share (document.exportSettings; the values of document.exportForScreens; null removes one): formats replace a preset, a preset replaces the formats → {settings}. Not an undo step",
+            "{exportPreset?: (a preset of document.exportPresets.list: its formats or preset, its format settings over the saved ones, its prefix (none when it has none), its folders (or subfolders: rows or none) with subfolders on for rows, and antiAlias; the other keys go over it), formats?, preset?: mobile|density|\"\" (none), settings?, prefix?, subfolders?, antiAlias?} change the export settings the Asset Export panel and Export for Screens share (document.exportSettings; the values of document.exportForScreens; null removes one): formats replace a preset, a preset replaces the formats → {settings}. Not an undo step",
             has_doc,
             settings_set
         ),
@@ -223,6 +223,10 @@ fn export(s: &mut Session, p: &Value) -> Result<Value> {
     if let Some(o) = p.as_object() {
         overlay(&mut q, o);
     }
+    // Create Sub-folders off puts every file in the folder itself, mobile and density rows too.
+    if q.get("subfolders").and_then(Value::as_bool) == Some(false) && p.get("folders").is_none() {
+        q.insert("folders".into(), json!("none"));
+    }
     if !q.contains_key("assets") {
         let all: Vec<u64> = s.doc()?.doc.assets.iter().map(|a| a.id).collect();
         if all.is_empty() {
@@ -236,11 +240,22 @@ fn export(s: &mut Session, p: &Value) -> Result<Value> {
 fn settings_set(s: &mut Session, p: &Value) -> Result<Value> {
     const C: &str = "assets.settings.set";
     let o = p.as_object().ok_or_else(|| bad(C, "give the settings to change, e.g. {\"formats\": [{\"format\": \"png\", \"scale\": \"2x\"}]}"))?;
-    if let Some(k) = o.keys().find(|k| !SCREEN_SETTINGS_KEYS.contains(&k.as_str())) {
+    if let Some(k) = o.keys().find(|k| !SCREEN_SETTINGS_KEYS.contains(&k.as_str()) && k.as_str() != "exportPreset") {
         return Err(bad(C, format!("`{k}` isn't an export setting: {}", SCREEN_SETTINGS_KEYS.join(", "))));
     }
     let mut settings = s.doc()?.doc.export_settings.clone();
-    overlay(&mut settings, o);
+    let mut given = o.clone();
+    if let Some(name) = given.remove("exportPreset") {
+        let name = name.as_str().ok_or_else(|| bad(C, "`exportPreset` is the name of a preset of document.exportPresets.list"))?;
+        with_screen_export_preset(s, C, &mut settings, name)?;
+    }
+    // Create Sub-folders is Export for Screens' folder per row: the dialog opens on the choice.
+    match given.get("subfolders").and_then(Value::as_bool) {
+        Some(true) => _ = settings.insert("folders".into(), json!("rows")),
+        Some(false) if settings.get("folders").and_then(Value::as_str) == Some("rows") => _ = settings.insert("folders".into(), json!("none")),
+        _ => {}
+    }
+    overlay(&mut settings, &given);
     check_screen_settings(s, &Value::Object(settings.clone())).map_err(|e| of_cmd(C, e))?;
     store_screen_settings(s, settings)?;
     Ok(json!({ "settings": shared_settings(s)? }))
diff --git a/crates/engine/src/cmd/fileio/mod.rs b/crates/engine/src/cmd/fileio/mod.rs
--- a/crates/engine/src/cmd/fileio/mod.rs
+++ b/crates/engine/src/cmd/fileio/mod.rs
@@ -79,12 +79,14 @@ pub use save::{
     templates_folder,
 };
 pub use screens::{
-    PRESET_KEYS as SCREEN_EXPORT_PRESET_KEYS, PRESETS as SCREEN_PRESETS, ScreenExport, ScreenExportPreset, ScreenSize,
-    builtin_presets as screen_export_builtin_presets, first_file as screen_first_file, preset_rows as screen_preset_rows,
+    PRESET_FILE_EXTS as SCREEN_EXPORT_PRESET_EXTS, PRESET_FILE_FORMAT as SCREEN_EXPORT_PRESET_FORMAT, PRESET_KEYS as SCREEN_EXPORT_PRESET_KEYS,
+    PRESETS as SCREEN_PRESETS, SHARED_KEYS as SCREEN_SETTINGS_KEYS, ScreenExport, ScreenExportPreset, ScreenSize,
+    builtin_presets as screen_export_builtin_presets, first_file as screen_first_file, preset_list as screen_export_preset_list,
+    preset_rows as screen_preset_rows,
 };
 pub(crate) use screens::{
-    SHARED_KEYS as SCREEN_SETTINGS_KEYS, check_settings as check_screen_settings, export as export_screens, safe_file_name,
-    store_settings as store_screen_settings,
+    check_settings as check_screen_settings, export as export_screens, safe_file_name, store_settings as store_screen_settings,
+    with_export_preset as with_screen_export_preset,
 };
 pub use svg::options_map as svg_options;
 /// Atomic file writes (a temporary file renamed over the target: a failed write never damages the
@@ -178,6 +180,24 @@ pub fn specs() -> Vec<CommandSpec> {
             always,
             screens::presets_delete
         ),
+        cmd!(
+            query "document.exportPresets.import",
+            "Import Export Presets",
+            [],
+            None,
+            "{path? | data?: file text | dataBase64?, replace?: false (replace saved presets of the same names; else the imported ones get a number)} add the presets of a .vcexportpresets file (as document.exportPresets.export writes) to the saved ones; a built-in name gets a number; a taken name that ends in a number gets the next free one (Web 2 comes in as Web 3); settings that document.exportPresets.save refuses, or more than 1000 presets, fail the whole file → {imported: [names]}",
+            always,
+            screens::presets_import
+        ),
+        cmd!(
+            query "document.exportPresets.export",
+            "Export Export Presets",
+            [],
+            None,
+            "{names?: [preset names, built-in ones too] (default: every saved preset), path?} write the presets (at most 1000, as an import reads) as a .vcexportpresets file (JSON) → {path, count}; without path → {data: the file's text, count}",
+            always,
+            screens::presets_export
+        ),
         cmd!(
             query "command.batch",
             "Batch",
@@ -779,8 +799,8 @@ fn format_filters() -> impl Iterator<Item = (&'static str, &'static [&'static st
 }
 
 /// Open-dialog filters: "All readable files" first, then one per readable format, then swatch
-/// libraries (which open in the library panel), Libraries panel libraries, flattener, PDF and print
-/// presets (imported), scripts (checked without running them) and plug-ins (installed).
+/// libraries (which open in the library panel), Libraries panel libraries, flattener, PDF, print
+/// and export presets (imported), scripts (checked without running them) and plug-ins (installed).
 pub fn open_filters() -> impl Iterator<Item = (&'static str, &'static [&'static str])> {
     std::iter::once(("All readable files", OPEN_EXTS))
         .chain(format_filters())
@@ -789,6 +809,7 @@ pub fn open_filters() -> impl Iterator<Item = (&'static str, &'static [&'static
         .chain(std::iter::once(("Flattener presets", super::flatten::PRESET_EXTS)))
         .chain(std::iter::once(("PDF presets", super::pdfcmds::PRESET_EXTS)))
         .chain(std::iter::once(("Print presets", super::printpresets::PRESET_EXTS)))
+        .chain(std::iter::once(("Export presets", screens::PRESET_FILE_EXTS)))
         .chain(std::iter::once(("Shortcut sets", super::shortcutset::EXTS)))
         .chain(std::iter::once(("Scripts", super::scripts::EXTS)))
         .chain(std::iter::once(("Plug-ins", super::plugin::EXTS)))
@@ -868,6 +889,25 @@ pub fn file_stem(name: &str) -> String {
     std::path::Path::new(name).file_stem().map(|s| s.to_string_lossy().to_string()).unwrap_or_else(|| name.to_string())
 }
 
+/// The name an imported preset `base` takes: `base` if `taken` doesn't claim it, else for a name
+/// that ends in a space and a number ("Logo Package 2") the first free name with a higher number
+/// ("Logo Package 3"), and for any other name the first free "base 2", "base 3"… ([`unique_name`]).
+pub(crate) fn import_name(base: &str, taken: impl Fn(&str) -> bool) -> String {
+    if !taken(base) {
+        return base.to_string();
+    }
+    let numbered = base
+        .rsplit_once(' ')
+        .filter(|(stem, n)| !stem.trim().is_empty() && !n.is_empty() && n.bytes().all(|b| b.is_ascii_digit()))
+        .and_then(|(stem, n)| Some((stem, n.parse::<u64>().ok()?)));
+    match numbered {
+        Some((stem, n)) => {
+            (n.saturating_add(1)..=u64::MAX).map(|i| format!("{stem} {i}")).find(|c| !taken(c)).unwrap_or_else(|| unique_name(base, &taken))
+        }
+        None => unique_name(base, taken),
+    }
+}
+
 /// What a `format` export of `doc`, whose file is `path`, writes when it isn't `doc` itself: a
 /// document that only has the Untitled-N name New gave it is titled after its file, as its tab is
 /// (the title PDF, PNG and SVG metadata carry). A title of its own stays, and native files keep
diff --git a/crates/engine/src/cmd/fileio/screens.rs b/crates/engine/src/cmd/fileio/screens.rs
--- a/crates/engine/src/cmd/fileio/screens.rs
+++ b/crates/engine/src/cmd/fileio/screens.rs
@@ -277,7 +277,7 @@ impl Row {
 
 /// The settings the Asset Export panel shares with the dialog: what is exported, not which art
 /// or where to.
-pub(crate) const SHARED_KEYS: [&str; 6] = ["formats", "preset", "settings", "prefix", "subfolders", "antiAlias"];
+pub const SHARED_KEYS: [&str; 6] = ["formats", "preset", "settings", "prefix", "subfolders", "antiAlias"];
 
 /// What `document.exportForScreens` reads besides the formats.
 #[derive(Default, Deserialize)]
@@ -624,24 +624,45 @@ fn with_preset(s: &Session, p: &Value) -> Result<Value> {
     Ok(Value::Object(merged))
 }
 
-pub(super) fn presets_list(s: &mut Session, _: &Value) -> Result<Value> {
+/// The rows of `document.exportPresets.list`: the built-in presets, then `saved`, each
+/// `{name, builtIn, settings}`.
+pub fn preset_list(saved: &[ScreenExportPreset]) -> Value {
     let row = |p: &ScreenExportPreset, built_in: bool| json!({"name": p.name, "builtIn": built_in, "settings": p.settings});
-    let rows: Vec<Value> =
-        builtin_presets().iter().map(|p| row(p, true)).chain(s.prefs.screen_export_presets.iter().map(|p| row(p, false))).collect();
-    Ok(json!({ "presets": rows }))
+    Value::Array(builtin_presets().iter().map(|p| row(p, true)).chain(saved.iter().map(|p| row(p, false))).collect())
+}
+
+pub(super) fn presets_list(s: &mut Session, _: &Value) -> Result<Value> {
+    Ok(json!({ "presets": preset_list(&s.prefs.screen_export_presets) }))
+}
+
+/// The [`PRESET_KEYS`] of `given`, refused as an error of command `cmd` when an export would
+/// refuse them.
+fn preset_settings(s: &Session, cmd: &str, given: &Map<String, Value>) -> Result<Map<String, Value>> {
+    let settings: Map<String, Value> = given.iter().filter(|(k, _)| PRESET_KEYS.contains(&k.as_str())).map(|(k, v)| (k.clone(), v.clone())).collect();
+    // Refuse what an export would refuse, now rather than when the preset is used.
+    check_settings(s, &Value::Object(settings.clone())).map_err(|e| bad(cmd, e.to_string()))?;
+    Folders::of(&ScreenParams::of(&Value::Object(settings.clone()))?).map_err(|e| bad(cmd, e.to_string()))?;
+    Ok(settings)
+}
+
+/// The longest export preset name kept (characters), as for asset names (`assets::MAX_NAME`).
+const MAX_PRESET_NAME: usize = 255;
+
+/// `name` trimmed and at most [`MAX_PRESET_NAME`] characters long (`None` when empty), as
+/// `assets::clean_name` keeps an asset name.
+fn preset_name(name: &str) -> Option<String> {
+    let n: String = name.trim().chars().take(MAX_PRESET_NAME).collect();
+    (!n.is_empty()).then_some(n)
 }
 
 pub(super) fn presets_save(s: &mut Session, p: &Value) -> Result<Value> {
     const SAVE: &str = "document.exportPresets.save";
-    let name = str_param(p, "name").map(str::trim).filter(|n| !n.is_empty()).ok_or_else(|| bad(SAVE, "give the preset a name"))?.to_string();
+    let name = str_param(p, "name").and_then(preset_name).ok_or_else(|| bad(SAVE, "give the preset a name"))?;
     if builtin_presets().iter().any(|b| b.name == name) {
         return Err(bad(SAVE, format!("`{name}` is a built-in preset: save under another name")));
     }
     let given = p.get("settings").and_then(Value::as_object).ok_or_else(|| bad(SAVE, "settings is an object of document.exportForScreens params"))?;
-    let settings: Map<String, Value> = given.iter().filter(|(k, _)| PRESET_KEYS.contains(&k.as_str())).map(|(k, v)| (k.clone(), v.clone())).collect();
-    // Refuse what an export would refuse, now rather than when the preset is used.
-    check_settings(s, &Value::Object(settings.clone())).map_err(|e| bad(SAVE, e.to_string()))?;
-    Folders::of(&ScreenParams::of(&Value::Object(settings.clone()))?).map_err(|e| bad(SAVE, e.to_string()))?;
+    let settings = preset_settings(s, SAVE, given)?;
     let preset = ScreenExportPreset { name: name.clone(), settings };
     let created = match s.prefs.screen_export_presets.iter_mut().find(|q| q.name == name) {
         Some(slot) => {
@@ -670,6 +691,157 @@ pub(super) fn presets_delete(s: &mut Session, p: &Value) -> Result<Value> {
     Ok(json!({ "deleted": name }))
 }
 
+/// What `document.exportPresets.export` writes and `document.exportPresets.import` reads.
+#[derive(Serialize, Deserialize)]
+struct PresetFile {
+    format: String,
+    #[serde(default)]
+    presets: Vec<ScreenExportPreset>,
+}
+
+/// The `format` of an export presets file, also its extension.
+pub const PRESET_FILE_FORMAT: &str = "vcexportpresets";
+
+/// The extensions of export presets files (opening one imports it).
+pub const PRESET_FILE_EXTS: &[&str] = &[PRESET_FILE_FORMAT];
+
+/// The most presets one file imports.
+const MAX_IMPORT: usize = 1000;
+
+pub(super) fn presets_export(s: &mut Session, p: &Value) -> Result<Value> {
+    const EXPORT: &str = "document.exportPresets.export";
+    // A file holds no more presets than an import reads.
+    let too_many = || bad(EXPORT, format!("more than {MAX_IMPORT} presets"));
+    let presets = match p.get("names") {
+        Some(Value::Array(names)) if names.len() > MAX_IMPORT => return Err(too_many()),
+        Some(Value::Array(names)) => {
+            let find = |n: &Value| {
+                let n = n.as_str().ok_or_else(|| bad(EXPORT, "`names` lists preset names"))?;
+                find_preset(s, n).ok_or_else(|| bad(EXPORT, format!("no export preset `{n}` (see document.exportPresets.list)")))
+            };
+            names.iter().map(find).collect::<Result<Vec<_>>>()?
+        }
+        None | Some(Value::Null) => s.prefs.screen_export_presets.clone(),
+        Some(_) => return Err(bad(EXPORT, "`names` is a list of preset names")),
+    };
+    if presets.is_empty() {
+        return Err(bad(EXPORT, "no saved export presets to export (name built-in ones in `names`)"));
+    }
+    if presets.len() > MAX_IMPORT {
+        return Err(too_many());
+    }
+    let count = presets.len();
+    let text = serde_json::to_string_pretty(&PresetFile { format: PRESET_FILE_FORMAT.into(), presets }).map_err(|e| bad(EXPORT, e.to_string()))?;
+    match str_param(p, "path") {
+        Some(path) => {
+            write_file(path, text.as_bytes())?;
+            Ok(json!({"path": path, "count": count}))
+        }
+        None => Ok(json!({"data": text, "count": count})),
+    }
+}
+
+pub(super) fn presets_import(s: &mut Session, p: &Value) -> Result<Value> {
+    const IMPORT: &str = "document.exportPresets.import";
+    let bytes = match (str_param(p, "path"), str_param(p, "data"), str_param(p, "dataBase64")) {
+        (Some(path), ..) => super::read_file(path)?,
+        (None, Some(text), _) => text.as_bytes().to_vec(),
+        (None, None, Some(b64)) => vectorcraft_format::base64_decode(b64).ok_or_else(|| bad(IMPORT, "bad dataBase64"))?,
+        _ => return Err(bad(IMPORT, "give `path`, `data` or `dataBase64`")),
+    };
+    let file: PresetFile = serde_json::from_slice(&bytes).map_err(|e| bad(IMPORT, format!("not an export presets file: {e}")))?;
+    if file.format != PRESET_FILE_FORMAT {
+        return Err(bad(IMPORT, format!("not an export presets file (format `{}`)", file.format)));
+    }
+    if file.presets.len() > MAX_IMPORT {
+        return Err(bad(IMPORT, format!("more than {MAX_IMPORT} presets")));
+    }
+    // Settings a hand-edited file may carry are refused as document.exportPresets.save refuses
+    // them, before any preset is added.
+    let mut presets = vec![];
+    for q in file.presets {
+        let settings = preset_settings(s, IMPORT, &q.settings).map_err(|e| match e {
+            EngineError::BadParams { msg, .. } => bad(IMPORT, format!("`{}`: {msg}", q.name)),
+            e => e,
+        })?;
+        let name = preset_name(&q.name).unwrap_or_else(|| "Export Preset".to_string());
+        presets.push((name, settings));
+    }
+    let replace = bool_or(p, "replace", false);
+    let saved = &mut s.prefs.screen_export_presets;
+    // A built-in name, or a saved one that stays, gets a number.
+    let mut taken: std::collections::HashSet<String> =
+        builtin_presets().into_iter().map(|b| b.name).chain(saved.iter().map(|q| q.name.clone())).collect();
+    let mut imported = vec![];
+    for (base, settings) in presets {
+        if replace && let Some(slot) = saved.iter_mut().find(|q| q.name == base) {
+            slot.settings = settings;
+            imported.push(base);
+            continue;
+        }
+        let mut name = super::import_name(&base, |n| taken.contains(n));
+        if name.chars().count() > MAX_PRESET_NAME {
+            // A number after a long name stays within the limit: the name is cut to leave room for
+            // a space and the longest number.
+            let short: String = base.chars().take(MAX_PRESET_NAME - 21).collect();
+            let short = short.trim_end();
+            name = super::import_name(short, |n| n == short || taken.contains(n));
+        }
+        taken.insert(name.clone());
+        saved.push(ScreenExportPreset { name: name.clone(), settings });
+        imported.push(name);
+    }
+    Ok(json!({ "imported": imported }))
+}
+
+/// `settings` (a document's export settings) with export preset `name` applied as the Asset
+/// Export panel applies one: its formats (or mobile/density `preset`), its format settings over
+/// the ones there, its prefix (none when it has none), its folders (`folders`, or `subfolders`)
+/// with Create Sub-folders on for a folder per row, and its anti-aliasing.
+pub(crate) fn with_export_preset(s: &Session, cmd: &str, settings: &mut Map<String, Value>, name: &str) -> Result<()> {
+    let preset = find_preset(s, name).ok_or_else(|| bad(cmd, format!("no export preset `{name}` (see document.exportPresets.list)")))?.settings;
+    if let Some(formats) = preset.get("formats") {
+        settings.insert("formats".into(), formats.clone());
+        settings.remove("preset");
+    } else if let Some(p) = preset.get("preset") {
+        settings.insert("preset".into(), p.clone());
+        settings.remove("formats");
+    }
+    if let Some(given) = preset.get("settings").and_then(Value::as_object) {
+        let all = settings.entry("settings").or_insert_with(|| json!({}));
+        if !all.is_object() {
+            *all = json!({});
+        }
+        if let Some(all) = all.as_object_mut() {
+            for (id, o) in given {
+                match (all.get_mut(id).and_then(Value::as_object_mut), o.as_object()) {
+                    (Some(mine), Some(o)) => mine.extend(o.iter().map(|(k, v)| (k.clone(), v.clone()))),
+                    _ => _ = all.insert(id.clone(), o.clone()),
+                }
+            }
+        }
+    }
+    match preset.get("prefix") {
+        Some(prefix) => _ = settings.insert("prefix".into(), prefix.clone()),
+        None => _ = settings.remove("prefix"),
+    }
+    // Its `folders` (Export for Screens shows it), else its `subfolders` (a folder per row or
+    // none). Create Sub-folders is on for a folder per row, as the dialog sets it.
+    let folders = preset
+        .get("folders")
+        .and_then(Value::as_str)
+        .filter(|f| !f.is_empty())
+        .or_else(|| preset.get("subfolders").and_then(Value::as_bool).map(|b| if b { "rows" } else { "none" }));
+    if let Some(f) = folders {
+        settings.insert("folders".into(), json!(f));
+        settings.insert("subfolders".into(), json!(f == "rows"));
+    }
+    if let Some(aa) = preset.get("antiAlias") {
+        settings.insert("antiAlias".into(), aa.clone());
+    }
+    Ok(())
+}
+
 /// Every chosen artboard (asset, layer, selected object) in every format row, one file each (a PDF
 /// holds its artboard alone), or with `fullDocument` one file per row (a PDF of every artboard,
 /// else the bounds of all art). Items that share a name get `-2`, `-3`… instead of overwriting, and
diff --git a/crates/engine/src/cmd/fileio/tests.rs b/crates/engine/src/cmd/fileio/tests.rs
--- a/crates/engine/src/cmd/fileio/tests.rs
+++ b/crates/engine/src/cmd/fileio/tests.rs
@@ -377,9 +377,10 @@ fn open_exts_cover_every_readable_format() {
     assert_eq!(filters[0], ("All readable files", OPEN_EXTS));
     assert_eq!(
         filters.len(),
-        9 + FORMATS.iter().filter(|f| f.read).count(),
-        "and swatch libraries, libraries, flattener, PDF and print presets, shortcut sets, scripts, plug-ins"
+        10 + FORMATS.iter().filter(|f| f.read).count(),
+        "and swatch libraries, libraries, flattener, PDF, print and export presets, shortcut sets, scripts, plug-ins"
     );
+    assert!(filters.contains(&("Export presets", SCREEN_EXPORT_PRESET_EXTS)), "File › Open imports export presets");
     assert!(filters.contains(&("Scripts", crate::cmd::scripts::EXTS)), "File › Open checks scripts");
     assert_eq!(filters.last(), Some(&("Plug-ins", crate::cmd::plugin::EXTS)), "File › Open installs plug-ins");
 }
diff --git a/crates/engine/src/cmd/fileio/tests_batch.rs b/crates/engine/src/cmd/fileio/tests_batch.rs
--- a/crates/engine/src/cmd/fileio/tests_batch.rs
+++ b/crates/engine/src/cmd/fileio/tests_batch.rs
@@ -222,3 +222,129 @@ fn export_presets_save_use_and_delete() {
         assert!(s.execute(bad.0, &bad.1).is_err(), "{bad:?}");
     }
 }
+
+/// Export presets go to a file and come back from one: a name in use gets a number unless
+/// `replace`, a built-in name is never replaced, and a file in another format or with a setting an
+/// export refuses adds nothing.
+#[test]
+fn export_presets_export_and_import() {
+    let mut s = session(2);
+    assert!(s.execute("document.exportPresets.export", &json!({})).is_err(), "nothing saved to export");
+    let web = json!({"name": "ACME-<name>", "folders": "item", "formats": [{"format": "jpg", "quality": 80}]});
+    s.execute("document.exportPresets.save", &json!({"name": "Web JPG", "settings": web})).unwrap();
+    let out = s.execute("document.exportPresets.export", &json!({})).unwrap();
+    assert_eq!(out["count"], 1);
+    let data = out["data"].as_str().unwrap().to_string();
+    assert_eq!(serde_json::from_str::<Value>(&data).unwrap()["format"], "vcexportpresets");
+    // Into a session with no saved presets: the preset as it was saved, and it exports.
+    let mut other = session(2);
+    let r = other.execute("document.exportPresets.import", &json!({ "data": data })).unwrap();
+    assert_eq!(r["imported"], json!(["Web JPG"]));
+    assert_eq!(other.prefs.screen_export_presets, s.prefs.screen_export_presets);
+    assert_eq!(names(&export(&mut other, json!({"exportPreset": "Web JPG", "range": "2"}))), ["Artboard-2/ACME-Artboard 2.jpg"]);
+    // Again: a second copy with a number; with replace, the saved one changes.
+    let r = other.execute("document.exportPresets.import", &json!({ "data": data })).unwrap();
+    assert_eq!(r["imported"], json!(["Web JPG 2"]));
+    s.execute("document.exportPresets.save", &json!({"name": "Web JPG", "settings": {"formats": [{"format": "png"}]}})).unwrap();
+    let changed = s.execute("document.exportPresets.export", &json!({"names": ["Web JPG"]})).unwrap()["data"].clone();
+    let r = other.execute("document.exportPresets.import", &json!({"data": changed, "replace": true})).unwrap();
+    assert_eq!(r["imported"], json!(["Web JPG"]));
+    let saved: Vec<(&str, &Value)> =
+        other.prefs.screen_export_presets.iter().map(|p| (p.name.as_str(), &p.settings["formats"][0]["format"])).collect();
+    assert_eq!(saved, [("Web JPG", &json!("png")), ("Web JPG 2", &json!("jpg"))]);
+    // A built-in preset exports by name and comes back under a new name; the built-in one stays.
+    let path = std::env::temp_dir().join(format!("vc-export-presets-{}.vcexportpresets", std::process::id()));
+    let path = path.to_string_lossy().to_string();
+    let out = s.execute("document.exportPresets.export", &json!({"names": ["Logo Package"], "path": path})).unwrap();
+    assert_eq!((out["path"].as_str(), out["count"].as_u64()), (Some(path.as_str()), Some(1)));
+    let r = other.execute("document.exportPresets.import", &json!({"path": path, "replace": true})).unwrap();
+    assert_eq!(r["imported"], json!(["Logo Package 2"]));
+    let builtin = screens::builtin_presets();
+    assert_eq!(other.prefs.screen_export_presets.last().map(|p| &p.settings), builtin.first().map(|p| &p.settings));
+    let _ = std::fs::remove_file(&path);
+    // Refused files add nothing.
+    let before = other.prefs.screen_export_presets.clone();
+    let preset = |settings: Value| {
+        json!({"format": "vcexportpresets", "presets": [{"name": "Fine", "settings": {"formats": [{"format": "png"}]}}, {"name": "Bad", "settings": settings}]}).to_string()
+    };
+    for bad in [
+        json!({"data": "{\"format\": \"vcflattener\", \"presets\": []}"}),
+        json!({"data": "not json"}),
+        json!({"data": preset(json!({"formats": [{"format": "txt"}]}))}),
+        json!({"data": preset(json!({"folders": "sideways"}))}),
+        json!({"dataBase64": "%%"}),
+        json!({}),
+    ] {
+        assert!(other.execute("document.exportPresets.import", &bad).is_err(), "{bad}");
+    }
+    assert_eq!(other.prefs.screen_export_presets, before);
+    assert!(s.execute("document.exportPresets.export", &json!({"names": ["Never saved"]})).is_err());
+    // `names` that is not a list of names is an error, not every saved preset.
+    for bad in [json!({"names": "Web JPG"}), json!({"names": [3]}), json!({"names": {"Web JPG": true}})] {
+        assert!(s.execute("document.exportPresets.export", &bad).is_err(), "{bad}");
+    }
+    // One file imports at most 1,000 presets; names repeated in a file are numbered in turn.
+    let many = |n: usize| {
+        let one = json!({"name": "Same", "settings": {"formats": [{"format": "png"}]}});
+        json!({"format": "vcexportpresets", "presets": vec![one; n]}).to_string()
+    };
+    assert!(other.execute("document.exportPresets.import", &json!({"data": many(1001)})).is_err(), "more than 1,000");
+    assert_eq!(other.prefs.screen_export_presets, before);
+    let r = other.execute("document.exportPresets.import", &json!({"data": many(1000)})).unwrap();
+    let imported = r["imported"].as_array().unwrap();
+    assert_eq!((imported.len(), imported.first(), imported.last()), (1000, Some(&json!("Same")), Some(&json!("Same 1000"))));
+    // An export writes no file that an import refuses: at most 1,000 presets.
+    assert!(other.prefs.screen_export_presets.len() > 1000);
+    assert!(other.execute("document.exportPresets.export", &json!({})).is_err(), "every saved preset");
+    let names: Vec<String> = other.prefs.screen_export_presets.iter().take(1001).map(|p| p.name.clone()).collect();
+    assert!(other.execute("document.exportPresets.export", &json!({ "names": names })).is_err(), "1,001 names");
+    let r = other.execute("document.exportPresets.export", &json!({ "names": names[..1000] })).unwrap();
+    assert_eq!(r["count"], 1000);
+}
+
+/// An imported preset whose name is taken and ends in a number gets the next free number, not a
+/// second one; any other taken name gets the first free number.
+#[test]
+fn an_imported_numbered_name_counts_on() {
+    let taken = ["Logo Package", "Logo Package 2", "Logo Package 4", "Web", "Web 2", "2", "Max 18446744073709551615"];
+    let name = |base: &str| import_name(base, |n| taken.contains(&n));
+    assert_eq!(name("Print"), "Print");
+    assert_eq!(name("Logo Package 3"), "Logo Package 3");
+    assert_eq!(name("Logo Package"), "Logo Package 3");
+    assert_eq!(name("Logo Package 2"), "Logo Package 3");
+    assert_eq!(name("Logo Package 4"), "Logo Package 5");
+    assert_eq!(name("Web"), "Web 3");
+    // A number alone, or one past the largest, is not counted on.
+    assert_eq!(name("2"), "2 2");
+    assert_eq!(name("Max 18446744073709551615"), "Max 18446744073709551615 2");
+    // Through the command: a built-in preset imported twice is Logo Package 2, then Logo Package 3.
+    let mut s = session(1);
+    let data = s.execute("document.exportPresets.export", &json!({"names": ["Logo Package"]})).unwrap()["data"].clone();
+    assert_eq!(s.execute("document.exportPresets.import", &json!({ "data": data })).unwrap()["imported"], json!(["Logo Package 2"]));
+    let data = s.execute("document.exportPresets.export", &json!({"names": ["Logo Package 2"]})).unwrap()["data"].clone();
+    assert_eq!(s.execute("document.exportPresets.import", &json!({ "data": data })).unwrap()["imported"], json!(["Logo Package 3"]));
+}
+
+/// A preset name, imported or saved, keeps at most 255 characters, as an asset name does.
+#[test]
+fn export_preset_names_are_capped() {
+    let mut s = session(1);
+    let long = "é".repeat(300);
+    let capped = "é".repeat(255);
+    let file = json!({"format": "vcexportpresets", "presets": [{"name": long, "settings": {"formats": [{"format": "png"}]}}]}).to_string();
+    let r = s.execute("document.exportPresets.import", &json!({ "data": file })).unwrap();
+    assert_eq!(r["imported"], json!([capped]));
+    let r = s.execute("document.exportPresets.save", &json!({"name": format!("  {}", "w".repeat(400)), "settings": {}})).unwrap();
+    assert_eq!(r["name"], "w".repeat(255));
+    let names: Vec<usize> = s.prefs.screen_export_presets.iter().map(|p| p.name.chars().count()).collect();
+    assert_eq!(names, [255, 255]);
+    // The same long name imported again gets a number within the limit, and saving under the name
+    // it got changes that preset alone.
+    let r = s.execute("document.exportPresets.import", &json!({ "data": file })).unwrap();
+    let again = r["imported"][0].as_str().unwrap().to_string();
+    assert!(again.chars().count() <= 255 && again != capped && again.ends_with(" 2"), "{again}");
+    s.execute("document.exportPresets.save", &json!({"name": again, "settings": {"formats": [{"format": "svg"}]}})).unwrap();
+    let formats: Vec<Option<&str>> =
+        s.prefs.screen_export_presets.iter().map(|p| p.settings.get("formats").and_then(|f| f[0]["format"].as_str())).collect();
+    assert_eq!(formats, [Some("png"), None, Some("svg")]);
+}
diff --git a/crates/engine/src/tests_assets.rs b/crates/engine/src/tests_assets.rs
--- a/crates/engine/src/tests_assets.rs
+++ b/crates/engine/src/tests_assets.rs
@@ -268,6 +268,90 @@ fn the_shared_settings_drive_the_export_and_arent_an_undo_step() {
     assert_eq!(run(&mut s, "document.exportSettings", json!({}))["settings"]["formats"][0]["format"], "jpg");
 }
 
+/// An export preset sets the shared settings as the Asset Export panel applies one: its rows (or
+/// mobile/density), its format settings over the saved ones, its prefix and its sub-folders.
+#[test]
+fn an_export_preset_sets_the_shared_settings() {
+    let mut s = session();
+    let a = rect(&mut s, 10.0, 10.0, 20.0, 20.0);
+    run(&mut s, "assets.add", json!({"ids": [a]}));
+    run(
+        &mut s,
+        "assets.settings.set",
+        json!({"preset": "mobile", "prefix": "old_", "settings": {"png": {"background": "white"}, "jpg": {"quality": 30}}}),
+    );
+    let pack =
+        json!({"formats": [{"format": "png"}, {"format": "svg"}], "folders": "rows", "settings": {"png": {"antiAlias": "type"}}, "name": "x-<name>"});
+    run(&mut s, "document.exportPresets.save", json!({"name": "Pack", "settings": pack}));
+    run(&mut s, "assets.settings.set", json!({"exportPreset": "Pack"}));
+    let settings = run(&mut s, "document.exportSettings", json!({}))["settings"].clone();
+    assert!(settings.get("preset").is_none() && settings.get("prefix").is_none(), "{settings}");
+    assert_eq!(settings["formats"], pack["formats"]);
+    assert_eq!(settings["subfolders"], true);
+    assert_eq!(settings["settings"], json!({"png": {"background": "white", "antiAlias": "type"}, "jpg": {"quality": 30}}));
+    let out = files(&run(&mut s, "assets.export", json!({})));
+    assert_eq!(out.iter().map(|f| f.0.as_str()).collect::<Vec<_>>(), ["1x/Asset-1.png", "SVG/Asset-1.svg"]);
+    // A built-in preset; the call's own keys go over it.
+    run(&mut s, "assets.settings.set", json!({"exportPreset": "Print-Ready PDF", "prefix": "p_"}));
+    let settings = run(&mut s, "document.exportSettings", json!({}))["settings"].clone();
+    assert_eq!((settings["formats"][0]["format"].as_str(), settings["prefix"].as_str()), (Some("pdf"), Some("p_")));
+    for bad in [json!({"exportPreset": "Never saved"}), json!({"exportPreset": 3})] {
+        assert!(s.execute("assets.settings.set", &bad).is_err(), "{bad}");
+    }
+    assert_eq!(run(&mut s, "document.exportSettings", json!({}))["settings"]["prefix"], "p_");
+}
+
+/// An export preset applied to the shared settings keeps its folders, with Create Sub-folders on
+/// for a folder per row; with Create Sub-folders off every file goes into the folder itself,
+/// mobile and density rows too.
+#[test]
+fn create_sub_folders_off_puts_every_asset_file_in_the_folder() {
+    let mut s = session();
+    let a = rect(&mut s, 10.0, 10.0, 20.0, 20.0);
+    run(&mut s, "assets.add", json!({"ids": [a]}));
+    let saved = [("None", json!({"folders": "none"})), ("Items", json!({"folders": "itemFormat"})), ("Panel", json!({"subfolders": true}))];
+    for (name, mut settings) in saved {
+        settings["formats"] = json!([{"format": "svg"}]);
+        run(&mut s, "document.exportPresets.save", json!({"name": name, "settings": settings}));
+    }
+    // (preset, the folders and Create Sub-folders it leaves).
+    let want = [("Logo Package", "format", false), ("None", "none", false), ("Items", "itemFormat", false), ("Panel", "rows", true)];
+    for (preset, folders, sub) in want {
+        run(&mut s, "assets.settings.set", json!({"exportPreset": preset}));
+        let settings = run(&mut s, "document.exportSettings", json!({}))["settings"].clone();
+        assert_eq!((settings["folders"].as_str(), settings["subfolders"].as_bool()), (Some(folders), Some(sub)), "{preset}: {settings}");
+    }
+    let paths = |s: &mut Session, p: Value| -> Vec<String> { files(&run(s, "assets.export", p)).into_iter().map(|f| f.0).collect() };
+    assert_eq!(paths(&mut s, json!({})), ["SVG/Asset-1.svg"]);
+    assert_eq!(paths(&mut s, json!({"subfolders": false})), ["Asset-1.svg"]);
+    run(&mut s, "assets.settings.set", json!({"exportPreset": "Logo Package"}));
+    let out = paths(&mut s, json!({}));
+    assert_eq!(out.len(), 5, "{out:?}");
+    assert!(out.iter().all(|f| !f.contains('/')), "{out:?}");
+    // Create Sub-folders set in the panel is the dialog's folder per row, on and off.
+    let folders = |s: &mut Session| run(s, "document.exportSettings", json!({}))["settings"]["folders"].clone();
+    run(&mut s, "assets.settings.set", json!({"subfolders": true}));
+    assert_eq!(folders(&mut s), "rows");
+    run(&mut s, "assets.settings.set", json!({"subfolders": false}));
+    assert_eq!(folders(&mut s), "none");
+    run(&mut s, "assets.settings.set", json!({"exportPreset": "Items", "subfolders": false}));
+    assert_eq!(folders(&mut s), "itemFormat");
+    // A mobile preset saved with no sub-folders exports its rows into the folder itself.
+    run(&mut s, "document.exportPresets.save", json!({"name": "Flat Mobile", "settings": {"preset": "mobile", "folders": "none"}}));
+    run(&mut s, "assets.settings.set", json!({"exportPreset": "Flat Mobile"}));
+    let out = paths(&mut s, json!({}));
+    assert_eq!(out.len(), 3, "{out:?}");
+    assert!(out.iter().all(|f| !f.contains('/')), "{out:?}");
+    // Create Sub-folders turned off keeps mobile rows in the folder, set or given to the export.
+    run(&mut s, "assets.settings.set", json!({"exportPreset": "Flat Mobile", "subfolders": false}));
+    assert!(paths(&mut s, json!({})).iter().all(|f| !f.contains('/')));
+    run(&mut s, "assets.settings.set", json!({"subfolders": true}));
+    assert_eq!(paths(&mut s, json!({})).iter().filter(|f| f.starts_with("3x/")).count(), 1);
+    assert!(paths(&mut s, json!({"subfolders": false})).iter().all(|f| !f.contains('/')));
+    run(&mut s, "assets.settings.set", json!({"subfolders": false}));
+    assert!(paths(&mut s, json!({})).iter().all(|f| !f.contains('/')));
+}
+
 #[test]
 fn export_for_screens_writes_chosen_assets_and_keeps_its_rules() {
     let mut s = session();
diff --git a/crates/engine/tests/import_fuzz.rs b/crates/engine/tests/import_fuzz.rs
--- a/crates/engine/tests/import_fuzz.rs
+++ b/crates/engine/tests/import_fuzz.rs
@@ -1,6 +1,6 @@
 //! Untrusted files never crash the app: garbage, truncated, mutated and hostile SVG, PDF and DXF input,
-//! mutated raster images (Photoshop documents among them) placed with File → Place, and swatch (`.vcswatches`, `.gpl`, `.ase`), graphic style (`.vcstyles`) and flattener preset
-//! (`.vcflattener`) libraries, shortcut sets other apps save as text (`.kys`), and native files (compressed, damaged, saved for older versions),
+//! mutated raster images (Photoshop documents among them) placed with File → Place, and swatch (`.vcswatches`, `.gpl`, `.ase`), graphic style (`.vcstyles`), flattener preset
+//! (`.vcflattener`) and export preset (`.vcexportpresets`) libraries, shortcut sets other apps save as text (`.kys`), and native files (compressed, damaged, saved for older versions),
 //! must load as an error or as a document that then renders and exports, without a panic; nor may
 //! bitmaps, PDF and text pasted from other apps, nor EMF and WMF pictures (damaged files, records
 //! of every kind with random contents) opened, placed or pasted, nor EPS and PostScript files
@@ -1206,6 +1206,16 @@ fn flattener_presets(what: &str, data: &str) -> Result<(), TestCaseError> {
     })
 }
 
+fn export_presets(what: &str, data: &str) -> Result<(), TestCaseError> {
+    survive_library(what, "document.exportPresets.import", data, |s, r| {
+        if let Some(name) = r["imported"].get(0) {
+            let _ = no_panic(s.execute("assets.settings.set", &json!({"exportPreset": name})));
+            let _ = no_panic(s.execute("document.exportPresets.export", &json!({"names": [name]})));
+            let _ = no_panic(s.execute("document.exportForScreens", &json!({"exportPreset": name, "range": "1"})));
+        }
+    })
+}
+
 /// Swatch exchange (`.ase`) bytes loaded as a swatch library, then added to the rich document.
 fn swatch_exchange(what: &str, bytes: &[u8]) -> Result<(), TestCaseError> {
     survive(what, || {
@@ -1257,11 +1267,12 @@ proptest! {
     #![proptest_config(config())]
 
     #[test]
-    fn library_garbage_never_panics(s in ".{0,300}", head in prop::sample::select(vec!["", "GIMP Palette\n","{\"format\": \"vcswatches\", ", "{\"format\": \"vcstyles\", ", "{\"format\": \"vcflattener\", ", "{\"name\": \"L\", "])) {
+    fn library_garbage_never_panics(s in ".{0,300}", head in prop::sample::select(vec!["", "GIMP Palette\n","{\"format\": \"vcswatches\", ", "{\"format\": \"vcstyles\", ", "{\"format\": \"vcflattener\", ", "{\"format\": \"vcexportpresets\", ", "{\"name\": \"L\", "])) {
         let data = format!("{head}{s}");
         swatches("swatch library garbage", &data)?;
         styles("style library garbage", &data)?;
         flattener_presets("flattener preset garbage", &data)?;
+        export_presets("export preset garbage", &data)?;
         libraries("library garbage", &data)?;
     }
 
@@ -1283,6 +1294,12 @@ proptest! {
         flattener_presets("mutated flattener presets", &mutate_text(&text, cut, &edits))?;
     }
 
+    #[test]
+    fn mutated_export_presets_never_panic(cut in 0usize..5_000, edits in prop::collection::vec(arb_edit(), 0..10)) {
+        let text = saved("document.exportPresets.export", json!({"names": ["Logo Package", "Print-Ready PDF"]}));
+        export_presets("mutated export presets", &mutate_text(&text, cut, &edits))?;
+    }
+
     #[test]
     fn mutated_libraries_never_panic(keep in 0.0f64..1.2, edits in prop::collection::vec(arb_edit(), 0..10)) {
         // Cut anywhere in the file (whole most of the time), the edits anywhere in it.
diff --git a/crates/ui-egui/src/control.rs b/crates/ui-egui/src/control.rs
--- a/crates/ui-egui/src/control.rs
+++ b/crates/ui-egui/src/control.rs
@@ -445,10 +445,19 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context, req: &ControlReques
                     let names = crate::dialogs::field_names(d).join(", ");
                     return err(format!("the `{}` dialog has no field `{f}`; its fields: {names}", d.kind));
                 }
+                // In order: a field that sets others (an export preset) goes before the fields after it.
+                // A field that fails leaves the dialog as it was before the request.
+                let before = app.ui.dialog.clone();
                 for (f, value) in fields {
-                    d.fields.insert(f, value);
+                    if let Some(d) = app.ui.dialog.as_mut() {
+                        d.fields.insert(f.clone(), value);
+                    }
+                    if let Err(e) = crate::dialogs::field_set(app, &f) {
+                        app.ui.dialog = before;
+                        return err(e);
+                    }
                 }
-                ok(serde_json::to_value(&*d).unwrap_or_default())
+                ok(app.ui.dialog.as_ref().and_then(|d| serde_json::to_value(d).ok()).unwrap_or_default())
             }
             None => err("no dialog open"),
         },
diff --git a/crates/ui-egui/src/dialogs/export_for_screens.rs b/crates/ui-egui/src/dialogs/export_for_screens.rs
--- a/crates/ui-egui/src/dialogs/export_for_screens.rs
+++ b/crates/ui-egui/src/dialogs/export_for_screens.rs
@@ -374,6 +374,14 @@ fn settings_params(app: &VectorcraftApp, d: &Dialog) -> Result<(Map<String, Valu
         }
     };
     let files = pieces * rows.len();
+    p.insert("formats".into(), Value::Array(rows));
+    naming_params(d, &mut p);
+    Ok((p, files))
+}
+
+/// The params of [`settings_params`] that do not depend on the document: the bleed, the folders,
+/// the naming and the format settings (not the formats).
+fn naming_params(d: &Dialog, p: &mut Map<String, Value>) {
     p.insert("includeBleed".into(), json!(d.bool("includeBleed")));
     let folders = d.str("folders");
     let folders = match (FOLDERS.iter().any(|(id, _)| *id == folders), d.bool("subfolders")) {
@@ -392,9 +400,24 @@ fn settings_params(app: &VectorcraftApp, d: &Dialog) -> Result<(Map<String, Valu
         p.insert("name".into(), json!(name.trim()));
     }
     p.insert("counterStart".into(), json!(counter_start(d)));
-    p.insert("formats".into(), Value::Array(rows));
     p.insert("settings".into(), d.fields.get("settings").cloned().unwrap_or(json!({})));
-    Ok((p, files))
+}
+
+/// What Save Preset… saves of dialog `d`: the formats, their settings, the naming, the folders,
+/// Full Document and the folder when one is chosen (not on the web), whatever art is checked or
+/// selected.
+fn preset_settings(app: &VectorcraftApp, d: &Dialog) -> Value {
+    let mut p = Map::new();
+    if !["assets", "layers", "selection"].contains(&d.str("tab").as_str()) {
+        p.insert("fullDocument".into(), json!(d.str("select") == "full"));
+    }
+    p.insert("formats".into(), d.fields.get("formats").cloned().unwrap_or(json!([])));
+    naming_params(d, &mut p);
+    let folder = d.str("folder");
+    if !io::is_web(app) && !folder.trim().is_empty() {
+        p.insert("folder".into(), json!(folder.trim()));
+    }
+    Value::Object(p)
 }
 
 /// Export; the dialog stays open when the export fails.
@@ -604,71 +627,237 @@ fn destination(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
         let current = d.str("folders");
         let at = FOLDERS.iter().position(|(id, _)| *id == current).unwrap_or(0);
         if let Some((id, _)) = widgets::dropdown(ui, "efs-folders", FOLDER_LABELS[at], &FOLDER_LABELS, 190.0).and_then(|i| FOLDERS.get(i)) {
-            d.fields.insert("folders".into(), json!(id));
-            d.fields.insert("subfolders".into(), json!(*id == "rows"));
+            set_folders(d, id);
         }
     });
 }
 
-/// Export Preset: apply a built-in or saved preset (its formats, settings, naming and folders),
-/// save the dialog's settings as one (Save…, then a name), or delete a saved one.
-fn presets(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
-    let list: Vec<Value> = d.fields.get("__exportPresets").and_then(Value::as_array).cloned().unwrap_or_default();
+/// Set Folders to `id`, with Create Sub-folders on for a folder per row.
+fn set_folders(d: &mut Dialog, id: &str) {
+    d.fields.insert("folders".into(), json!(id));
+    d.fields.insert("subfolders".into(), json!(id == "rows"));
+}
+
+/// `ui.dialog.set` of `folders` in the open dialog: Create Sub-folders follows it as a pick in the
+/// Folders list sets it. A value that is not a Folders choice leaves Create Sub-folders as it is.
+pub(super) fn folders_set(app: &mut VectorcraftApp) {
+    if let Some(d) = app.ui.dialog.as_mut() {
+        let folders = d.str("folders");
+        if let Some((id, _)) = FOLDERS.iter().find(|(id, _)| *id == folders) {
+            set_folders(d, id);
+        }
+    }
+}
+
+/// What the Export Preset row asked for.
+#[derive(Debug, PartialEq)]
+pub(crate) enum PresetPick {
+    /// Use this preset (`{name, builtIn, settings}`).
+    Apply(Value),
+    /// Save the settings as the preset of this name (Save, after a name was typed).
+    Save(String),
+    /// Delete this saved preset.
+    Delete(String),
+    /// Import Preset…: add the presets of a file.
+    Import,
+    /// Export Preset…: write this preset to a file.
+    Export(String),
+}
+
+/// The Export Preset row, the dialog's and the Asset Export panel's: the built-in and saved presets
+/// (`list`, as [`preset_list`] keeps it; `exportPreset` names the current one), Save Preset… (then
+/// a name in `__savePreset`), Delete Preset (a saved one), and with `files` (the panel's) Import
+/// Preset… and Export Preset… (the current one).
+pub(crate) fn preset_row(ui: &mut egui::Ui, d: &mut Dialog, list: &Value, files: bool) -> Option<PresetPick> {
+    let list: &[Value] = list.as_array().map_or(&[], Vec::as_slice);
     let names: Vec<&str> = list.iter().filter_map(|p| p["name"].as_str()).collect();
     let current = d.str("exportPreset");
+    let known = names.contains(&current.as_str());
     let saved = list.iter().any(|p| p["name"] == current && p["builtIn"] == false);
-    ui.horizontal(|ui| {
+    let mut pick = None;
+    // In a narrow column (the panel) the label goes above the dropdown.
+    let narrow = ui.available_width() < 320.0;
+    if narrow {
         widgets::dim_label(ui, tl!("Export Preset:"));
+    }
+    ui.horizontal(|ui| {
+        if !narrow {
+            widgets::dim_label(ui, tl!("Export Preset:"));
+        }
         let labels: Vec<&str> = std::iter::once("Custom").chain(names.iter().copied()).collect();
         let at = names.iter().position(|n| *n == current).map_or(0, |i| i + 1);
-        if let Some(i) = widgets::dropdown(ui, "efs-export-preset", labels[at], &labels, 150.0)
+        // Two buttons follow it, or four with `files`.
+        let buttons = if files { 4.0 } else { 2.0 };
+        let width = (ui.available_width() - buttons * 24.0 - (buttons + 1.0) * ui.spacing().item_spacing.x).clamp(80.0, 150.0);
+        if let Some(i) = widgets::dropdown(ui, "efs-export-preset", labels[at], &labels, width)
             && let Some(preset) = i.checked_sub(1).and_then(|i| list.get(i))
         {
-            apply_preset(d, preset);
+            pick = Some(PresetPick::Apply(preset.clone()));
         }
         if widgets::icon_button(ui, "save", tl!("Save Preset…"), false, 24.0).clicked() {
             let name = if saved { current.clone() } else { String::new() };
             d.fields.insert("__savePreset".into(), json!(name));
         }
         ui.add_enabled_ui(saved, |ui| {
-            if widgets::icon_button(ui, "trash-2", tl!("Delete Preset"), false, 24.0).clicked()
-                && app.run("document.exportPresets.delete", json!({"name": current})).is_ok()
-            {
-                d.fields.insert("exportPreset".into(), json!(""));
-                d.fields.insert("__exportPresets".into(), export_presets(app));
+            if widgets::icon_button(ui, "trash-2", tl!("Delete Preset"), false, 24.0).clicked() {
+                pick = Some(PresetPick::Delete(current.clone()));
             }
         });
+        if files {
+            if widgets::icon_button(ui, "folder-open", tl!("Import Preset…"), false, 24.0).clicked() {
+                pick = Some(PresetPick::Import);
+            }
+            ui.add_enabled_ui(known, |ui| {
+                if widgets::icon_button(ui, "external-link", tl!("Export Preset…"), false, 24.0).clicked() {
+                    pick = Some(PresetPick::Export(current.clone()));
+                }
+            });
+        }
     });
     if d.fields.contains_key("__savePreset") {
         ui.horizontal(|ui| {
             widgets::dim_label(ui, tl!("Name:"));
-            form::text(ui, d, "__savePreset", 150.0);
+            // Save and Cancel follow it.
+            form::text(ui, d, "__savePreset", (ui.available_width() - 130.0).clamp(60.0, 150.0));
             let name = d.str("__savePreset").trim().to_string();
             if ui.add_enabled(!name.is_empty(), egui::Button::new(tl!("Save"))).clicked() {
-                let settings = params(app, d).unwrap_or_else(|_| json!({"formats": d.fields.get("formats").cloned().unwrap_or(json!([]))}));
-                match app.run("document.exportPresets.save", json!({"name": name, "settings": settings})) {
-                    Ok(_) => {
-                        d.fields.remove("__savePreset");
-                        d.fields.insert("exportPreset".into(), json!(name));
-                        d.fields.insert("__exportPresets".into(), export_presets(app));
-                    }
-                    Err(e) => app.ui.status = e,
-                }
+                pick = Some(PresetPick::Save(name));
             }
             if ui.small_button(tl!("Cancel")).clicked() {
                 d.fields.remove("__savePreset");
             }
         });
     }
+    pick
 }
 
-/// Set the dialog to preset `preset` (`{name, settings}`): its formats, settings, naming and
-/// folders; what it doesn't name keeps the dialog's value.
+/// Import Preset… (the Asset Export panel's): the presets of a picked file are added to the saved
+/// ones → the names added
+/// (none on the web, where the picked file arrives later and imports through `io::open_bytes`).
+pub(crate) fn import_presets(app: &mut VectorcraftApp) -> Result<Vec<String>, String> {
+    if let Some(f) = app.services.open_async.as_mut() {
+        f();
+        return Ok(vec![]);
+    }
+    let pick = crate::FilePick { filters: vec![crate::FilePick::filter("Export presets", fileio::SCREEN_EXPORT_PRESET_EXTS)], ..Default::default() };
+    let path = crate::picks::open(app, &pick).ok_or("cancelled")?;
+    let r = io::import_presets(app, "document.exportPresets.import", io::EXPORT_PRESETS, json!({ "path": path }))?;
+    Ok(r["imported"].as_array().into_iter().flatten().filter_map(Value::as_str).map(str::to_string).collect())
+}
+
+/// Export Preset… (the Asset Export panel's): preset `name` written to a picked file named after
+/// it (the web downloads it) → where it went.
+pub(crate) fn export_preset(app: &mut VectorcraftApp, name: &str) -> Result<String, String> {
+    io::save_command_output_named(app, "document.exportPresets.export", &preset_file_name(name), json!({ "names": [name] }))
+}
+
+/// The file name Export Preset… suggests for preset `name`: the name made safe as a file name, as
+/// Export Library… makes a library's (`library::file_stem_for`), with the `.vcexportpresets`
+/// extension.
+pub(crate) fn preset_file_name(name: &str) -> String {
+    format!("{}.{}", vectorcraft_engine::cmd::library::file_stem_for(name), fileio::SCREEN_EXPORT_PRESET_FORMAT)
+}
+
+/// `list` (the rows of `document.exportPresets.list`) made the rows for the saved presets `saved`:
+/// built again only when it does not list them as they are, so that a frame that changes nothing
+/// builds nothing → whether it was built again.
+pub(crate) fn preset_list(list: &mut Value, saved: &[fileio::ScreenExportPreset]) -> bool {
+    // The built-in rows never change; the saved ones are compared in place.
+    let current = list.as_array().is_some_and(|rows| {
+        let mut mine = rows.iter().filter(|p| p["builtIn"] == false);
+        rows.iter().any(|p| p["builtIn"] == true)
+            && saved.iter().all(|q| mine.next().is_some_and(|row| row["name"] == q.name.as_str() && row["settings"].as_object() == Some(&q.settings)))
+            && mine.next().is_none()
+    });
+    if !current {
+        *list = fileio::screen_export_preset_list(saved);
+    }
+    !current
+}
+
+/// Export Preset: apply a built-in or saved preset (its formats, settings, naming and folders),
+/// save the dialog's settings as one (Save…, then a name), or delete a saved one.
+fn presets(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
+    // Presets saved, deleted or imported elsewhere (a command, a file opened) show at once.
+    let mut list = d.fields.remove("__exportPresets").unwrap_or_default();
+    preset_list(&mut list, &app.session.prefs.screen_export_presets);
+    let pick = preset_row(ui, d, &list, false);
+    d.fields.insert("__exportPresets".into(), list);
+    if let Some(pick) = pick {
+        preset_pick(app, d, pick);
+    }
+}
+
+/// `ui.dialog.set` of `exportPreset` in the open dialog: the preset it names is applied as a pick
+/// in the dropdown applies it, and `""` is Custom. A name that is not a preset's is an error, and
+/// `ui.dialog.set` then leaves the dialog as it was.
+pub(super) fn preset_set(app: &mut VectorcraftApp) -> Result<(), String> {
+    let Some(mut d) = app.ui.dialog.take() else { return Ok(()) };
+    let list = fileio::screen_export_preset_list(&app.session.prefs.screen_export_presets);
+    let name = d.str("exportPreset");
+    let preset = list.as_array().and_then(|l| l.iter().find(|p| p["name"] == name.as_str())).cloned();
+    let r = match preset {
+        Some(preset) => {
+            preset_pick(app, &mut d, PresetPick::Apply(preset));
+            Ok(())
+        }
+        None => {
+            d.fields.insert("exportPreset".into(), json!(""));
+            d.fields.insert("__exportPresets".into(), list);
+            if name.is_empty() { Ok(()) } else { Err(format!("no export preset `{name}` (see document.exportPresets.list)")) }
+        }
+    };
+    app.ui.dialog = Some(d);
+    r
+}
+
+/// Act on what the Export Preset row of dialog `d` asked for.
+pub(super) fn preset_pick(app: &mut VectorcraftApp, d: &mut Dialog, pick: PresetPick) {
+    let done = match pick {
+        PresetPick::Apply(preset) => {
+            apply_preset(d, &preset);
+            Ok(())
+        }
+        // The name as saved (trimmed and capped).
+        PresetPick::Save(name) => app.run("document.exportPresets.save", json!({"name": name, "settings": preset_settings(app, d)})).map(|r| {
+            d.fields.remove("__savePreset");
+            d.fields.insert("exportPreset".into(), r["name"].clone());
+        }),
+        PresetPick::Delete(name) => app.run("document.exportPresets.delete", json!({ "name": name })).map(|_| {
+            d.fields.insert("exportPreset".into(), json!(""));
+        }),
+        // The dialog's row has no Import Preset… or Export Preset… (the Asset Export panel's has).
+        PresetPick::Import | PresetPick::Export(_) => Ok(()),
+    };
+    if let Err(e) = done
+        && e != "cancelled"
+    {
+        app.ui.status = e;
+    }
+    let list = d.fields.entry("__exportPresets".to_string()).or_insert(Value::Null);
+    preset_list(list, &app.session.prefs.screen_export_presets);
+}
+
+/// Set the dialog to preset `preset` (`{name, settings}`): its formats, settings, naming, folders
+/// and Full Document; what it doesn't name keeps the dialog's value. A preset saved without Full
+/// Document chooses All, or Range when the checked artboards are not all of them.
 pub(super) fn apply_preset(d: &mut Dialog, preset: &Value) {
     let st = &preset["settings"];
+    match st["fullDocument"].as_bool() {
+        Some(true) => set_select(d, "full"),
+        Some(false) if d.str("select") == "full" => {
+            let all = d.fields.get("boards").and_then(Value::as_array).is_none_or(|b| b.iter().all(|b| b.as_bool() != Some(false)));
+            set_select(d, if all { "all" } else { "range" });
+        }
+        _ => {}
+    }
     if let Some(rows) = st["formats"].as_array() {
         d.fields.insert("formats".into(), json!(rows.iter().filter(|r| r.is_object()).map(editable_row).collect::<Vec<_>>()));
         d.fields.insert("preset".into(), json!(""));
+    } else if let Some((p, rows)) = st["preset"].as_str().and_then(|p| Some((p, fileio::screen_preset_rows(p)?))) {
+        // A preset saved with the mobile or density rows (the Asset Export panel's).
+        d.fields.insert("formats".into(), json!(rows));
+        d.fields.insert("preset".into(), json!(p));
     }
     if let (Some(all), Some(given)) = (d.fields.get_mut("settings").and_then(Value::as_object_mut), st["settings"].as_object()) {
         for (id, o) in given {
@@ -678,10 +867,9 @@ pub(super) fn apply_preset(d: &mut Dialog, preset: &Value) {
             }
         }
     }
-    let folders = st["folders"].as_str().or_else(|| st["subfolders"].as_bool().map(|b| if b { "rows" } else { "none" }));
+    let folders = st["folders"].as_str().filter(|f| !f.is_empty()).or_else(|| st["subfolders"].as_bool().map(|b| if b { "rows" } else { "none" }));
     if let Some(f) = folders {
-        d.fields.insert("folders".into(), json!(f));
-        d.fields.insert("subfolders".into(), json!(f == "rows"));
+        set_folders(d, f);
     }
     for k in ["name", "prefix"] {
         d.fields.insert(k.into(), json!(st[k].as_str().unwrap_or_default()));
diff --git a/crates/ui-egui/src/dialogs/mod.rs b/crates/ui-egui/src/dialogs/mod.rs
--- a/crates/ui-egui/src/dialogs/mod.rs
+++ b/crates/ui-egui/src/dialogs/mod.rs
@@ -122,8 +122,9 @@ pub(crate) use effect::{FILM_GRAIN_FIELDS, PAINT_DAUB_BRUSH_TYPE_LABELS, SHADES,
 pub use export_as::open as open_export_as;
 pub use export_for_screens::open as open_export_for_screens;
 pub(crate) use export_for_screens::{
-    KIND as EXPORT_FOR_SCREENS, formats as screen_formats, open_assets as open_export_for_screens_assets, saved_options as screen_saved_options,
-    saved_rows as screen_saved_rows,
+    KIND as EXPORT_FOR_SCREENS, PresetPick as ScreenPresetPick, export_preset as screen_export_preset, formats as screen_formats,
+    import_presets as screen_import_presets, open_assets as open_export_for_screens_assets, preset_list as screen_preset_list,
+    preset_row as screen_preset_row, saved_options as screen_saved_options, saved_rows as screen_saved_rows,
 };
 pub(crate) use form::{humanize, number_list_text, read_number_list, unit_suffix};
 pub(crate) use new_document::preset_name;
@@ -433,6 +434,19 @@ pub(crate) fn parent(d: &Dialog) -> Option<Dialog> {
     d.fields.get(PARENT).and_then(|v| serde::Deserialize::deserialize(v).ok())
 }
 
+/// `ui.dialog.set` changed `field` of the open dialog: a choice that sets other fields sets them as
+/// choosing it in the dialog does (Export for Screens' `exportPreset` and `folders`).
+pub(crate) fn field_set(app: &mut VectorcraftApp, field: &str) -> Result<(), String> {
+    match app.ui.dialog.as_ref().map(|d| d.kind.as_str()) {
+        Some(export_for_screens::KIND) if field == "exportPreset" => export_for_screens::preset_set(app),
+        Some(export_for_screens::KIND) if field == "folders" => {
+            export_for_screens::folders_set(app);
+            Ok(())
+        }
+        _ => Ok(()),
+    }
+}
+
 /// Close the open dialog as Cancel does, rolling back a live preview and showing the dialog it was
 /// opened over (`ui.dialog.cancel`).
 pub fn cancel(app: &mut VectorcraftApp) {
diff --git a/crates/ui-egui/src/dialogs/tests_screens.rs b/crates/ui-egui/src/dialogs/tests_screens.rs
--- a/crates/ui-egui/src/dialogs/tests_screens.rs
+++ b/crates/ui-egui/src/dialogs/tests_screens.rs
@@ -893,7 +893,9 @@ fn batch_naming_folders_layers_selection_and_presets() {
     set(&mut app, "tab", json!("layers"));
     set(&mut app, "layers", json!([layer]));
     set(&mut app, "name", json!(""));
+    // As `ui.dialog.set` sets it: Create Sub-folders goes off with All in One Folder.
     set(&mut app, "folders", json!("none"));
+    crate::dialogs::field_set(&mut app, "folders").unwrap();
     set(&mut app, "formats", json!([{"format": "png", "scale": "1x", "suffix": ""}]));
     let r = confirm(&mut app).unwrap();
     assert_eq!(r["files"][0], json!(format!("{folder}/Layer-1.png")));
@@ -935,3 +937,334 @@ fn batch_naming_folders_layers_selection_and_presets() {
     assert!(list.as_array().unwrap().iter().any(|p| p["name"] == "Client Pack" && p["builtIn"] == false));
     let _ = std::fs::remove_dir_all(&dir);
 }
+
+/// Save Preset… keeps the naming and the folders while the dialog cannot export yet: no folder
+/// chosen, or no asset checked on the Assets tab.
+#[test]
+fn save_preset_keeps_the_naming_while_the_dialog_cannot_export() {
+    use export_for_screens::PresetPick;
+    let (mut app, _) = app(2, false);
+    let folder = temp_folder("save-preset").to_string_lossy().to_string();
+    open(&mut app);
+    let save = |app: &mut VectorcraftApp, name: &str| {
+        let mut d = app.ui.dialog.take().unwrap();
+        export_for_screens::preset_pick(app, &mut d, PresetPick::Save(name.into()));
+        app.ui.dialog = Some(d);
+    };
+    set(&mut app, "name", json!("ACME-<name>"));
+    set(&mut app, "prefix", json!("p-"));
+    set(&mut app, "folders", json!("format"));
+    set(&mut app, "counterStart", json!(5));
+    set(&mut app, "folder", json!(""));
+    save(&mut app, "No Folder");
+    set(&mut app, "folder", json!(folder));
+    set(&mut app, "tab", json!("assets"));
+    save(&mut app, "No Asset");
+    for (name, saved_folder) in [("No Folder", None), ("No Asset", Some(folder.as_str()))] {
+        let preset = app.session.prefs.screen_export_presets.iter().find(|p| p.name == name).unwrap_or_else(|| panic!("{name}: {}", app.ui.status));
+        let s = &preset.settings;
+        assert_eq!(
+            (&s["name"], &s["prefix"], &s["folders"], &s["counterStart"]),
+            (&json!("ACME-<name>"), &json!("p-"), &json!("format"), &json!(5)),
+            "{name}"
+        );
+        assert_eq!(s.get("folder").and_then(Value::as_str), saved_folder, "{name}");
+        assert_eq!(s["formats"], json!([{"format": "png", "scale": "1x", "suffix": ""}]), "{name}");
+    }
+}
+
+/// A preset saved with Full Document chosen chooses Full Document again when it is applied.
+#[test]
+fn a_preset_saved_with_full_document_chooses_it_again() {
+    use export_for_screens::PresetPick;
+    let (mut app, _) = app(2, false);
+    open(&mut app);
+    set(&mut app, "select", json!("full"));
+    let mut d = app.ui.dialog.take().unwrap();
+    export_for_screens::preset_pick(&mut app, &mut d, PresetPick::Save("Whole".into()));
+    app.ui.dialog = Some(d);
+    let saved = app.session.prefs.screen_export_presets.iter().find(|p| p.name == "Whole").unwrap_or_else(|| panic!("{}", app.ui.status));
+    assert_eq!(saved.settings["fullDocument"], json!(true));
+    let preset = json!({"name": "Whole", "settings": saved.settings.clone()});
+    set(&mut app, "select", json!("all"));
+    export_for_screens::apply_preset(app.ui.dialog.as_mut().unwrap(), &preset);
+    assert_eq!(field(&app, "select"), "full");
+}
+
+/// A preset saved without Full Document chooses All again after a Full Document preset, or Range
+/// when the checked artboards are not all of them.
+#[test]
+fn a_preset_saved_without_full_document_leaves_full_document() {
+    use export_for_screens::PresetPick;
+    let (mut app, _) = app(2, false);
+    open(&mut app);
+    let save = |app: &mut VectorcraftApp, name: &str| {
+        let mut d = app.ui.dialog.take().unwrap();
+        export_for_screens::preset_pick(app, &mut d, PresetPick::Save(name.into()));
+        app.ui.dialog = Some(d);
+        let saved = app.session.prefs.screen_export_presets.iter().find(|p| p.name == name).unwrap_or_else(|| panic!("{}", app.ui.status));
+        json!({"name": name, "settings": saved.settings.clone()})
+    };
+    set(&mut app, "select", json!("all"));
+    let boards = save(&mut app, "Boards");
+    assert_eq!(boards["settings"]["fullDocument"], json!(false));
+    set(&mut app, "select", json!("full"));
+    let whole = save(&mut app, "Whole");
+    let apply = |app: &mut VectorcraftApp, preset: &Value| export_for_screens::apply_preset(app.ui.dialog.as_mut().unwrap(), preset);
+    apply(&mut app, &boards);
+    assert_eq!(field(&app, "select"), "all");
+    apply(&mut app, &whole);
+    assert_eq!(field(&app, "select"), "full");
+    set(&mut app, "boards", json!([true, false]));
+    apply(&mut app, &boards);
+    assert_eq!(field(&app, "select"), "range");
+}
+
+/// A preset whose `folders` is empty uses its `subfolders`, as the export command does, and
+/// without either it keeps the dialog's Folders.
+#[test]
+fn a_preset_with_empty_folders_uses_its_subfolders() {
+    let (mut app, _) = app(2, false);
+    open(&mut app);
+    set(&mut app, "folders", json!("format"));
+    set(&mut app, "subfolders", json!(true));
+    let apply = |app: &mut VectorcraftApp, settings: Value| {
+        export_for_screens::apply_preset(app.ui.dialog.as_mut().unwrap(), &json!({"name": "Empty", "settings": settings}));
+    };
+    apply(&mut app, json!({"folders": ""}));
+    assert_eq!((field(&app, "folders"), field(&app, "subfolders")), (json!("format"), json!(true)));
+    apply(&mut app, json!({"folders": "", "subfolders": false}));
+    assert_eq!((field(&app, "folders"), field(&app, "subfolders")), (json!("none"), json!(false)));
+}
+
+/// The Asset Export panel's Export Preset… writes a preset to a file and its Import Preset… adds
+/// the presets of a file, which the open dialog's dropdown then lists, as it does after opening
+/// the file (the web's Import).
+#[cfg(not(target_arch = "wasm32"))]
+#[test]
+fn export_preset_and_import_preset_go_through_files() {
+    let dir = temp_folder("preset-files");
+    std::fs::create_dir_all(&dir).unwrap();
+    let file = dir.join("pack.vcexportpresets").to_string_lossy().to_string();
+    let (saved_to, picked) = (file.clone(), file.clone());
+    let suggested = Rc::new(RefCell::new(vec![]));
+    let shown = suggested.clone();
+    let services = Services {
+        pick_save: Some(Box::new(move |pick: &crate::FilePick| {
+            shown.borrow_mut().push(pick.name.clone());
+            Some(saved_to.clone())
+        })),
+        pick_open: Some(Box::new(move |_: &crate::FilePick| Some(picked.clone()))),
+        write: Some(Box::new(|p: &str, b: &[u8]| std::fs::write(p, b).map_err(|e| e.to_string()))),
+        ..Default::default()
+    };
+    let mut app = VectorcraftApp::new(Session::new(), services);
+    app.run("file.new", json!({"width": 60, "height": 40})).unwrap();
+    app.run("document.exportPresets.save", json!({"name": "Pack", "settings": {"formats": [{"format": "svg"}], "folders": "format"}})).unwrap();
+    open(&mut app);
+    let names = |app: &VectorcraftApp| -> Vec<String> {
+        field(app, "__exportPresets").as_array().unwrap().iter().filter_map(|p| p["name"].as_str().map(str::to_string)).collect()
+    };
+    assert_eq!(export_for_screens::export_preset(&mut app, "Pack").unwrap(), file);
+    // The save panel suggests the preset's name, not the document's.
+    assert_eq!(*suggested.borrow(), ["Pack.vcexportpresets"]);
+    app.run("document.exportPresets.delete", json!({"name": "Pack"})).unwrap();
+    frame(&mut app);
+    assert_eq!(names(&app), ["Logo Package", "Print-Ready PDF"]);
+    assert_eq!(export_for_screens::import_presets(&mut app).unwrap(), ["Pack"]);
+    assert_eq!(app.ui.status, "Imported export presets: Pack");
+    frame(&mut app);
+    assert_eq!(names(&app), ["Logo Package", "Print-Ready PDF", "Pack"]);
+    let pack = field(&app, "__exportPresets")[2].clone();
+    export_for_screens::apply_preset(app.ui.dialog.as_mut().unwrap(), &pack);
+    assert_eq!((field(&app, "formats")[0]["format"].clone(), field(&app, "folders")), (json!("svg"), json!("format")));
+    // The file opened again: a second copy, numbered, in the open dialog's dropdown.
+    let bytes = std::fs::read(&file).unwrap();
+    crate::io::open_bytes(&mut app, &file, &bytes, Some(file.clone())).unwrap();
+    frame(&mut app);
+    assert_eq!(kind(&app), Some(export_for_screens::KIND));
+    assert_eq!(names(&app), ["Logo Package", "Print-Ready PDF", "Pack", "Pack 2"]);
+    let _ = std::fs::remove_dir_all(&dir);
+}
+
+/// Export Preset… suggests the preset's name as a file name, as Export Library… does a library's.
+#[test]
+fn an_exported_preset_file_is_named_after_the_preset() {
+    let name = export_for_screens::preset_file_name;
+    assert_eq!(name("Logo Package 2"), "Logo Package 2.vcexportpresets");
+    assert_eq!(name("Print-Ready PDF"), "Print-Ready PDF.vcexportpresets");
+    assert_eq!(name("Web/App: v1.2"), "Web-App- v1-2.vcexportpresets");
+    assert_eq!(name("CON"), "CON-.vcexportpresets");
+}
+
+/// A preset saved with the mobile or density rows (as the Asset Export panel saves one) sets the
+/// dialog's rows to them.
+#[test]
+fn a_mobile_preset_sets_the_rows() {
+    let (mut app, _) = app(1, false);
+    app.run("document.exportPresets.save", json!({"name": "Phones", "settings": {"preset": "mobile", "subfolders": true}})).unwrap();
+    open(&mut app);
+    let phones = export_for_screens::export_presets(&mut app).as_array().unwrap().iter().find(|p| p["name"] == "Phones").cloned().unwrap();
+    export_for_screens::apply_preset(app.ui.dialog.as_mut().unwrap(), &phones);
+    assert_eq!(field(&app, "formats"), json!(fileio::screen_preset_rows("mobile").unwrap()));
+    assert_eq!((field(&app, "preset"), field(&app, "folders")), (json!("mobile"), json!("rows")));
+}
+
+/// `ui.dialog.set` of `exportPreset` applies the preset it names, as a pick in the dropdown does, so
+/// the dialog holds the settings of the preset it names; a name that is no preset's is an error and
+/// leaves the dialog as it was.
+#[test]
+fn setting_the_export_preset_field_applies_the_preset() {
+    use crate::control::{ControlRequest, Outcome};
+    let (mut app, _) = app(1, false);
+    let ctx = egui::Context::default();
+    open(&mut app);
+    let set_preset = |app: &mut VectorcraftApp, name: &str| {
+        let (req, _rx) = ControlRequest::new("ui.dialog.set", json!({"field": "exportPreset", "value": name}));
+        let Outcome::Done(r) = crate::control::handle(app, &ctx, &req) else { panic!("not done") };
+        r
+    };
+    let r = set_preset(&mut app, "Logo Package");
+    assert_eq!(r["ok"], true, "{r}");
+    let rows: Vec<Value> = field(&app, "formats").as_array().unwrap().iter().map(|r| r["format"].clone()).collect();
+    assert_eq!(rows, [json!("svg"), json!("png"), json!("png"), json!("pdf"), json!("pdf")]);
+    assert_eq!((field(&app, "folders"), field(&app, "name")), (json!("format"), json!("<filename>-<name>")));
+    assert_eq!(r["result"]["fields"]["folders"], "format", "{r}");
+    let r = set_preset(&mut app, "Never saved");
+    assert_eq!(r["ok"], false, "{r}");
+    assert_eq!(field(&app, "exportPreset"), "Logo Package");
+    assert_eq!(field(&app, "folders"), "format");
+    // A request that fails on a later field changes none of its fields.
+    let fields = json!({"fields": [{"field": "folders", "value": "item"}, {"field": "exportPreset", "value": "Never saved"}]});
+    let (req, _rx) = ControlRequest::new("ui.dialog.set", fields);
+    let Outcome::Done(r) = crate::control::handle(&mut app, &ctx, &req) else { panic!("not done") };
+    assert_eq!(r["ok"], false, "{r}");
+    assert_eq!((field(&app, "exportPreset"), field(&app, "folders")), (json!("Logo Package"), json!("format")));
+    // In one request the fields apply in order: a field after the preset goes over it.
+    let fields = json!({"fields": [{"field": "exportPreset", "value": "Logo Package"}, {"field": "folders", "value": "item"}]});
+    let (req, _rx) = ControlRequest::new("ui.dialog.set", fields);
+    let Outcome::Done(r) = crate::control::handle(&mut app, &ctx, &req) else { panic!("not done") };
+    assert_eq!(r["ok"], true, "{r}");
+    assert_eq!((field(&app, "exportPreset"), field(&app, "folders")), (json!("Logo Package"), json!("item")));
+}
+
+/// Create Sub-folders (`subfolders`) is on for a folder per row, as a pick in the Folders list sets
+/// it: after a preset, after `folders` is set, in the settings an export leaves, and when the
+/// dialog reopens. The Asset Export panel, with Create Sub-folders off, then puts its files in the
+/// folder itself.
+#[test]
+fn the_subfolders_field_follows_the_folders() {
+    use crate::control::{ControlRequest, Outcome};
+    let (mut app, _) = app(1, false);
+    let ctx = egui::Context::default();
+    let dir = temp_folder("subfolders");
+    let folder = dir.to_string_lossy().replace('\\', "/");
+    let set_field = |app: &mut VectorcraftApp, k: &str, v: Value| {
+        let (req, _rx) = ControlRequest::new("ui.dialog.set", json!({"field": k, "value": v}));
+        let Outcome::Done(r) = crate::control::handle(app, &ctx, &req) else { panic!("not done") };
+        assert_eq!(r["ok"], true, "{r}");
+        r
+    };
+    open(&mut app);
+    let r = set_field(&mut app, "exportPreset", json!("Logo Package"));
+    let fields = &r["result"]["fields"];
+    assert_eq!((fields["folders"].clone(), fields["subfolders"].clone()), (json!("format"), json!(false)), "{r}");
+    for (folders, on) in [("rows", true), ("none", false), ("rows", true), ("item", false), ("rows", true), ("itemFormat", false)] {
+        set_field(&mut app, "folders", json!(folders));
+        assert_eq!(field(&app, "subfolders"), json!(on), "{folders}");
+    }
+    set_field(&mut app, "folders", json!("format"));
+    set_field(&mut app, "formats", json!([{"format": "png", "scale": "1x", "suffix": ""}]));
+    set_field(&mut app, "folder", json!(folder));
+    let r = confirm(&mut app).unwrap();
+    assert!(r["files"][0].as_str().unwrap().contains("/png/"), "{r}");
+    let saved = app.run("document.exportSettings", json!({})).unwrap()["settings"].clone();
+    assert_eq!((saved["folders"].clone(), saved["subfolders"].clone()), (json!("format"), json!(false)), "{saved}");
+    open(&mut app);
+    assert_eq!((field(&app, "folders"), field(&app, "subfolders")), (json!("format"), json!(false)));
+    app.ui.dialog = None;
+    app.run("select.all", json!({})).unwrap();
+    app.run("assets.add", json!({})).unwrap();
+    let r = app.run("assets.export", json!({"folder": folder})).unwrap();
+    assert!(!r["files"][0].as_str().unwrap().contains("/png/"), "{r}");
+    let _ = std::fs::remove_dir_all(dir);
+}
+
+/// The Asset Export panel's Import Preset… and Export Preset… show their file dialogs off the UI
+/// thread (as sheets on macOS) and read or write the file when the dialog answers.
+#[cfg(not(target_arch = "wasm32"))]
+#[test]
+fn preset_files_ask_off_the_ui_thread() {
+    use crate::panels::asset_export;
+    use crate::picks::PickRequest;
+    use export_for_screens::PresetPick;
+    let mut services = Services { dialogs_are_sheets: true, ..Default::default() };
+    let (shown, written) = crate::tests_picks::off_the_ui_thread(&mut services);
+    let mut app = VectorcraftApp::new(Session::new(), services);
+    app.run("file.new", json!({"width": 60, "height": 40})).unwrap();
+    app.run("document.exportPresets.save", json!({"name": "Pack", "settings": {"formats": [{"format": "svg"}]}})).unwrap();
+    let dir = temp_folder("preset-sheets");
+    std::fs::create_dir_all(&dir).unwrap();
+    let file = dir.join("pack.vcexportpresets");
+    std::fs::write(&file, app.run("document.exportPresets.export", json!({"names": ["Pack"]})).unwrap()["data"].as_str().unwrap()).unwrap();
+    let file = file.to_string_lossy().to_string();
+    let names = |app: &VectorcraftApp| app.session.prefs.screen_export_presets.iter().map(|p| p.name.clone()).collect::<Vec<_>>();
+    let poll = |app: &mut VectorcraftApp| crate::picks::poll(app, &egui::Context::default());
+    let ctx = egui::Context::default();
+    let doc = app.session.active().unwrap().uid;
+    asset_export::preset(&mut app, &ctx, doc, PresetPick::Export("Pack".into()));
+    assert!(matches!(shown.requests().as_slice(), [PickRequest::Save(p)] if p.name == "Pack.vcexportpresets"), "{:?}", shown.requests());
+    assert!(written.borrow().is_empty() && app.ui.status.is_empty(), "{}", app.ui.status);
+    shown.answer(&["/presets/pack.vcexportpresets"]);
+    poll(&mut app);
+    assert_eq!(*written.borrow(), ["/presets/pack.vcexportpresets"], "{}", app.ui.status);
+    asset_export::preset(&mut app, &ctx, doc, PresetPick::Import);
+    assert!(matches!(shown.requests().as_slice(), [PickRequest::Open(_)]), "{:?}", shown.requests());
+    assert_eq!(names(&app), ["Pack"]);
+    shown.answer(&[&file]);
+    poll(&mut app);
+    assert_eq!(names(&app), ["Pack", "Pack 2"], "{}", app.ui.status);
+    let _ = std::fs::remove_dir_all(&dir);
+}
+
+/// The dialog's Export Preset row has no Import Preset… or Export Preset…: those picks change
+/// nothing there.
+#[test]
+fn the_dialog_leaves_preset_files_to_the_panel() {
+    use export_for_screens::PresetPick;
+    let (mut app, _) = app(1, false);
+    open(&mut app);
+    let before = serde_json::to_value(&app.ui.dialog).unwrap();
+    for pick in [PresetPick::Import, PresetPick::Export("Logo Package".into())] {
+        let mut d = app.ui.dialog.take().unwrap();
+        export_for_screens::preset_pick(&mut app, &mut d, pick);
+        app.ui.dialog = Some(d);
+    }
+    assert_eq!(serde_json::to_value(&app.ui.dialog).unwrap(), before);
+    assert_eq!(app.ui.status, "");
+}
+
+/// The dialog keeps its list of export presets from frame to frame, and builds it again when the
+/// saved presets change.
+#[test]
+fn the_dialog_builds_the_preset_list_again_only_when_the_presets_change() {
+    let (mut app, _) = app(1, false);
+    open(&mut app);
+    frame(&mut app);
+    let mark = |app: &mut VectorcraftApp| app.ui.dialog.as_mut().unwrap().fields.get_mut("__exportPresets").unwrap()[0]["mark"] = json!(true);
+    mark(&mut app);
+    frame(&mut app);
+    frame(&mut app);
+    assert_eq!(field(&app, "__exportPresets")[0]["mark"], true, "the list was not built again");
+    app.run("document.exportPresets.save", json!({"name": "Mine", "settings": {"formats": [{"format": "svg"}]}})).unwrap();
+    frame(&mut app);
+    let list = field(&app, "__exportPresets");
+    assert!(list[0].get("mark").is_none() && list.as_array().unwrap().iter().any(|p| p["name"] == "Mine"), "{list}");
+    // A saved preset changed under the same name is a change too.
+    mark(&mut app);
+    app.run("document.exportPresets.save", json!({"name": "Mine", "settings": {"formats": [{"format": "pdf"}]}})).unwrap();
+    frame(&mut app);
+    let list = field(&app, "__exportPresets");
+    let mine = list.as_array().unwrap().iter().find(|p| p["name"] == "Mine").unwrap();
+    assert_eq!(mine["settings"]["formats"][0]["format"], "pdf", "{list}");
+}
diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv
--- a/crates/ui-egui/src/i18n/bg.tsv
+++ b/crates/ui-egui/src/i18n/bg.tsv
@@ -1000,6 +1000,10 @@ font-technology	Variable	Променливи
 	Delete Export Preset	Изтриване на набор за експортиране
 	Export Presets	Набори за експортиране
 	Save Export Preset	Запазване на набор за експортиране
+	Export Export Presets	Експортиране на набори за експортиране
+	Export Preset…	Експортиране на набор…
+	Import Export Presets	Импортиране на набори за експортиране
+	Import Preset…	Импортиране на набор…
 	Export Layer	Експортиране на слой
 	Export Selection	Експортиране на избраното
 	All in One Folder	Всичко в една папка
@@ -5101,6 +5105,7 @@ file-filter	Flattener presets	Предварителни настройки за
 file-filter	PDF presets	Предварителни настройки за PDF
 file-filter	PDF-compatible .ai	Съвместими с PDF .ai файлове
 file-filter	Print presets	Предварителни настройки за печат
+file-filter	Export presets	Набори за експортиране
 file-filter	SVG Compressed	Компресиран SVG
 file-filter	Swatch libraries	Библиотеки с образци
 file-filter	Templates	Шаблони
diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv
--- a/crates/ui-egui/src/i18n/cs.tsv
+++ b/crates/ui-egui/src/i18n/cs.tsv
@@ -2869,6 +2869,16 @@ lighting	Light	Světlo
 	Delete Export Preset	Odstranit přednastavení exportu
 	Export Presets	Přednastavení exportu
 	Save Export Preset	Uložit přednastavení exportu
+	Export Export Presets	Exportovat přednastavení exportu
+	Export Preset…	Exportovat přednastavení…
+	Import Export Presets	Importovat přednastavení exportu
+	Import Preset…	Importovat přednastavení…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset` je název přednastavení z document.exportPresets.list
+@msg	no export preset `{n}` (see document.exportPresets.list)	přednastavení exportu `{n}` neexistuje (viz document.exportPresets.list)
+@msg	no saved export presets to export (name built-in ones in `names`)	žádná uložená přednastavení exportu k exportu (vestavěná uveďte v `names`)
+@msg	not an export presets file (format `{_1}`)	nejde o soubor přednastavení exportu (formát `{_1}`)
+@msg	not an export presets file: {e}	nejde o soubor přednastavení exportu: {e}
+@msg	`{_1}`: {msg}	`{_1}`: {msg}
 	Export Layer	Exportovat vrstvu
 	Export Selection	Exportovat výběr
 	All in One Folder	Vše v jedné složce
@@ -5152,6 +5162,7 @@ file-filter	Swatch libraries	Knihovny vzorníků
 file-filter	Flattener presets	Přednastavení sloučení průhlednosti
 file-filter	PDF presets	Přednastavení PDF
 file-filter	Print presets	Přednastavení tisku
+file-filter	Export presets	Přednastavení exportu
 file-filter	SVG Compressed	Komprimované SVG
 file-filter	PDF-compatible .ai	.ai kompatibilní s PDF
 file-filter	VectorCraft Template	Šablona VectorCraft
diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv
--- a/crates/ui-egui/src/i18n/da.tsv
+++ b/crates/ui-egui/src/i18n/da.tsv
@@ -4795,6 +4795,7 @@ file-filter	Swatch libraries	Farveprøvebiblioteker
 file-filter	Flattener presets	Flattener-forudindstillinger
 file-filter	PDF presets	PDF-forudindstillinger
 file-filter	Print presets	Printforudindstillinger
+file-filter	Export presets	Eksportforudindstillinger
 file-filter	SVG Compressed	Komprimeret SVG
 file-filter	PDF-compatible .ai	PDF-kompatibel .ai
 file-filter	VectorCraft Template	VectorCraft-skabelon
@@ -5139,6 +5140,10 @@ swatch	Bright Violet	Kraftig violet
 	Delete Export Preset	Slet eksportforudindstilling
 	Export Presets	Eksportforudindstillinger
 	Save Export Preset	Gem eksportforudindstilling
+	Export Export Presets	Eksportér eksportforudindstillinger
+	Export Preset…	Eksportér forudindstilling…
+	Import Export Presets	Importér eksportforudindstillinger
+	Import Preset…	Importér forudindstilling…
 	Export Layer	Eksportér lag
 	Export Selection	Eksportér markering
 	All in One Folder	Alt i én mappe
diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv
--- a/crates/ui-egui/src/i18n/de.tsv
+++ b/crates/ui-egui/src/i18n/de.tsv
@@ -994,6 +994,16 @@ font-technology	Variable	Variabel
 	Delete Export Preset	Exportvorgabe löschen
 	Export Presets	Exportvorgaben
 	Save Export Preset	Exportvorgabe speichern
+	Export Export Presets	Exportvorgaben exportieren
+	Export Preset…	Vorgabe exportieren…
+	Import Export Presets	Exportvorgaben importieren
+	Import Preset…	Vorgabe importieren…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset` ist der Name einer Vorgabe aus document.exportPresets.list
+@msg	no export preset `{n}` (see document.exportPresets.list)	keine Exportvorgabe `{n}` (siehe document.exportPresets.list)
+@msg	no saved export presets to export (name built-in ones in `names`)	keine gespeicherten Exportvorgaben zum Exportieren (integrierte in `names` angeben)
+@msg	not an export presets file (format `{_1}`)	keine Exportvorgabendatei (Format `{_1}`)
+@msg	not an export presets file: {e}	keine Exportvorgabendatei: {e}
+@msg	`{_1}`: {msg}	`{_1}`: {msg}
 	Export Layer	Ebene exportieren
 	Export Selection	Auswahl exportieren
 	All in One Folder	Alles in einem Ordner
@@ -1165,6 +1175,7 @@ file-filter	Swatch libraries	Farbfeldbibliotheken
 file-filter	Flattener presets	Reduzierungsvorgaben
 file-filter	PDF presets	PDF-Vorgaben
 file-filter	Print presets	Druckvorgaben
+file-filter	Export presets	Exportvorgaben
 file-filter	SVG Compressed	SVG komprimiert
 file-filter	PDF-compatible .ai	PDF-kompatible .ai
 file-filter	VectorCraft Template	VectorCraft-Vorlage
diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv
--- a/crates/ui-egui/src/i18n/el.tsv
+++ b/crates/ui-egui/src/i18n/el.tsv
@@ -966,6 +966,16 @@ font-technology	Variable	Μεταβλητές
 	Delete Export Preset	Διαγραφή προκαθορισμένου εξαγωγής
 	Export Presets	Προκαθορισμένα εξαγωγής
 	Save Export Preset	Αποθήκευση προκαθορισμένου εξαγωγής
+	Export Export Presets	Εξαγωγή προκαθορισμένων εξαγωγής
+	Export Preset…	Εξαγωγή προκαθορισμένου…
+	Import Export Presets	Εισαγωγή προκαθορισμένων εξαγωγής
+	Import Preset…	Εισαγωγή προκαθορισμένου…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	Το `exportPreset` είναι το όνομα ενός προκαθορισμένου από το document.exportPresets.list
+@msg	no export preset `{n}` (see document.exportPresets.list)	δεν υπάρχει προκαθορισμένο εξαγωγής `{n}` (δείτε document.exportPresets.list)
+@msg	no saved export presets to export (name built-in ones in `names`)	δεν υπάρχουν αποθηκευμένα προκαθορισμένα εξαγωγής για εξαγωγή (δώστε τα ενσωματωμένα στο `names`)
+@msg	not an export presets file (format `{_1}`)	δεν είναι αρχείο προκαθορισμένων εξαγωγής (μορφή `{_1}`)
+@msg	not an export presets file: {e}	δεν είναι αρχείο προκαθορισμένων εξαγωγής: {e}
+@msg	`{_1}`: {msg}	`{_1}`: {msg}
 	Export Layer	Εξαγωγή επιπέδου
 	Export Selection	Εξαγωγή επιλογής
 	All in One Folder	Όλα σε έναν φάκελο
@@ -2432,6 +2442,7 @@ file-filter	Flattener presets	Προεπιλογές ισοπέδωσης
 file-filter	PDF presets	Προεπιλογές PDF
 file-filter	PDF-compatible .ai	.ai συμβατό με PDF
 file-filter	Print presets	Προεπιλογές εκτύπωσης
+file-filter	Export presets	Προκαθορισμένα εξαγωγής
 file-filter	SVG Compressed	Συμπιεσμένο SVG
 file-filter	Swatch libraries	Βιβλιοθήκες δειγμάτων χρωμάτων
 file-filter	Templates	Πρότυπα
diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv
--- a/crates/ui-egui/src/i18n/es.tsv
+++ b/crates/ui-egui/src/i18n/es.tsv
@@ -1017,6 +1017,16 @@ font-technology	Variable	Variables
 	Delete Export Preset	Eliminar ajuste de exportación
 	Export Presets	Ajustes de exportación
 	Save Export Preset	Guardar ajuste de exportación
+	Export Export Presets	Exportar ajustes de exportación
+	Export Preset…	Exportar ajuste…
+	Import Export Presets	Importar ajustes de exportación
+	Import Preset…	Importar ajuste…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset` es el nombre de un ajuste de document.exportPresets.list
+@msg	no export preset `{n}` (see document.exportPresets.list)	no hay ningún ajuste de exportación `{n}` (consulta document.exportPresets.list)
+@msg	no saved export presets to export (name built-in ones in `names`)	no hay ajustes de exportación guardados para exportar (indica los integrados en `names`)
+@msg	not an export presets file (format `{_1}`)	no es un archivo de ajustes de exportación (formato `{_1}`)
+@msg	not an export presets file: {e}	no es un archivo de ajustes de exportación: {e}
+@msg	`{_1}`: {msg}	`{_1}`: {msg}
 	Export Layer	Exportar capa
 	Export Selection	Exportar selección
 	All in One Folder	Todo en una carpeta
@@ -5021,6 +5031,7 @@ file-filter	Swatch libraries	Bibliotecas de muestras
 file-filter	Flattener presets	Preajustes del acoplador
 file-filter	PDF presets	Preajustes de PDF
 file-filter	Print presets	Preajustes de impresión
+file-filter	Export presets	Ajustes de exportación
 file-filter	SVG Compressed	SVG comprimido
 file-filter	PDF-compatible .ai	.ai compatible con PDF
 file-filter	VectorCraft Template	Plantilla de VectorCraft
diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv
--- a/crates/ui-egui/src/i18n/fi.tsv
+++ b/crates/ui-egui/src/i18n/fi.tsv
@@ -2815,6 +2815,16 @@ lighting	Light	Valo
 	Delete Export Preset	Poista vientiesiasetus
 	Export Presets	Vientiesiasetukset
 	Save Export Preset	Tallenna vientiesiasetus
+	Export Export Presets	Vie vientiesiasetukset
+	Export Preset…	Vie esiasetus…
+	Import Export Presets	Tuo vientiesiasetukset
+	Import Preset…	Tuo esiasetus…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset` on luettelon document.exportPresets.list esiasetuksen nimi
+@msg	no export preset `{n}` (see document.exportPresets.list)	ei vientiesiasetusta `{n}` (katso document.exportPresets.list)
+@msg	no saved export presets to export (name built-in ones in `names`)	ei tallennettuja vientiesiasetuksia vietäväksi (anna sisäänrakennetut parametrissa `names`)
+@msg	not an export presets file (format `{_1}`)	ei vientiesiasetustiedosto (muoto `{_1}`)
+@msg	not an export presets file: {e}	ei vientiesiasetustiedosto: {e}
+@msg	`{_1}`: {msg}	`{_1}`: {msg}
 	Export Layer	Vie taso
 	Export Selection	Vie valinta
 	All in One Folder	Kaikki yhteen kansioon
@@ -5043,6 +5053,7 @@ file-filter	Swatch libraries	Värimallikirjastot
 file-filter	Flattener presets	Litistyksen esiasetukset
 file-filter	PDF presets	PDF-esiasetukset
 file-filter	Print presets	Tulostuksen esiasetukset
+file-filter	Export presets	Vientiesiasetukset
 file-filter	SVG Compressed	Pakattu SVG
 file-filter	PDF-compatible .ai	PDF-yhteensopiva .ai
 file-filter	VectorCraft Template	VectorCraft-malli
diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv
--- a/crates/ui-egui/src/i18n/fr.tsv
+++ b/crates/ui-egui/src/i18n/fr.tsv
@@ -1017,6 +1017,16 @@ font-technology	Variable	Variables
 	Delete Export Preset	Supprimer le paramètre prédéfini d’export
 	Export Presets	Paramètres prédéfinis d’export
 	Save Export Preset	Enregistrer le paramètre prédéfini d’export
+	Export Export Presets	Exporter les paramètres prédéfinis d’export
+	Export Preset…	Exporter le paramètre prédéfini…
+	Import Export Presets	Importer des paramètres prédéfinis d’export
+	Import Preset…	Importer un paramètre prédéfini…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset` est le nom d’un paramètre prédéfini de document.exportPresets.list
+@msg	no export preset `{n}` (see document.exportPresets.list)	aucun paramètre prédéfini d’export `{n}` (voir document.exportPresets.list)
+@msg	no saved export presets to export (name built-in ones in `names`)	aucun paramètre prédéfini d’export enregistré à exporter (indiquez les paramètres intégrés dans `names`)
+@msg	not an export presets file (format `{_1}`)	ce n’est pas un fichier de paramètres prédéfinis d’export (format `{_1}`)
+@msg	not an export presets file: {e}	ce n’est pas un fichier de paramètres prédéfinis d’export : {e}
+@msg	`{_1}`: {msg}	`{_1}` : {msg}
 	Export Layer	Exporter le calque
 	Export Selection	Exporter la sélection
 	All in One Folder	Tout dans un dossier
@@ -5213,6 +5223,7 @@ file-filter	Flattener presets	Paramètres prédéfinis d’aplatissement
 file-filter	PDF presets	Paramètres prédéfinis PDF
 file-filter	PDF-compatible .ai	.ai compatible PDF
 file-filter	Print presets	Paramètres prédéfinis d’impression
+file-filter	Export presets	Paramètres prédéfinis d’export
 file-filter	SVG Compressed	SVG compressé
 file-filter	Swatch libraries	Bibliothèques de nuances
 file-filter	Templates	Modèles
diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv
--- a/crates/ui-egui/src/i18n/hu.tsv
+++ b/crates/ui-egui/src/i18n/hu.tsv
@@ -1298,6 +1298,16 @@ font-technology	Variable	Változó
 	Delete Export Preset	Exportálási előbeállítás törlése
 	Export Presets	Exportálási előbeállítások
 	Save Export Preset	Exportálási előbeállítás mentése
+	Export Export Presets	Exportálási előbeállítások exportálása
+	Export Preset…	Előbeállítás exportálása…
+	Import Export Presets	Exportálási előbeállítások importálása
+	Import Preset…	Előbeállítás importálása…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset`: a document.exportPresets.list egyik előbeállításának neve kell
+@msg	no export preset `{n}` (see document.exportPresets.list)	nincs ilyen exportálási előbeállítás: `{n}` (lásd: document.exportPresets.list)
+@msg	no saved export presets to export (name built-in ones in `names`)	nincs exportálható mentett exportálási előbeállítás (a beépítetteket a `names` paraméterben add meg)
+@msg	not an export presets file (format `{_1}`)	nem exportálási előbeállítás-fájl (formátum: `{_1}`)
+@msg	not an export presets file: {e}	nem exportálási előbeállítás-fájl: {e}
+@msg	`{_1}`: {msg}	`{_1}`: {msg}
 	Export Layer	Réteg exportálása
 	Export Selection	Kijelölés exportálása
 	All in One Folder	Mind egy mappába
@@ -1476,6 +1486,7 @@ file-filter	Swatch libraries	Színminta-könyvtárak
 file-filter	Flattener presets	Lapítási előbeállítások
 file-filter	PDF presets	PDF-előbeállítások
 file-filter	Print presets	Nyomtatási előbeállítások
+file-filter	Export presets	Exportálási előbeállítások
 file-filter	SVG Compressed	Tömörített SVG
 file-filter	PDF-compatible .ai	PDF-kompatibilis .ai
 file-filter	VectorCraft Template	VectorCraft-sablon
diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv
--- a/crates/ui-egui/src/i18n/id.tsv
+++ b/crates/ui-egui/src/i18n/id.tsv
@@ -4795,6 +4795,7 @@ file-filter	Swatch libraries	Pustaka swatch
 file-filter	Flattener presets	Preset perata
 file-filter	PDF presets	Preset PDF
 file-filter	Print presets	Preset cetak
+file-filter	Export presets	Preset ekspor
 file-filter	SVG Compressed	SVG terkompresi
 file-filter	PDF-compatible .ai	.ai kompatibel PDF
 file-filter	VectorCraft Template	Templat VectorCraft
@@ -5139,6 +5140,10 @@ swatch	Bright Violet	Violet cerah
 	Delete Export Preset	Hapus preset ekspor
 	Export Presets	Daftar preset ekspor
 	Save Export Preset	Simpan preset ekspor
+	Export Export Presets	Ekspor preset ekspor
+	Export Preset…	Ekspor preset…
+	Import Export Presets	Impor preset ekspor
+	Import Preset…	Impor preset…
 	Export Layer	Ekspor lapisan
 	Export Selection	Ekspor seleksi
 	All in One Folder	Semua dalam satu folder
diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv
--- a/crates/ui-egui/src/i18n/it.tsv
+++ b/crates/ui-egui/src/i18n/it.tsv
@@ -1017,6 +1017,16 @@ font-technology	Variable	Variabili
 	Delete Export Preset	Elimina predefinito di esportazione
 	Export Presets	Predefiniti di esportazione
 	Save Export Preset	Salva predefinito di esportazione
+	Export Export Presets	Esporta predefiniti di esportazione
+	Export Preset…	Esporta predefinito…
+	Import Export Presets	Importa predefiniti di esportazione
+	Import Preset…	Importa predefinito…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset` è il nome di un predefinito di document.exportPresets.list
+@msg	no export preset `{n}` (see document.exportPresets.list)	nessun predefinito di esportazione `{n}` (vedi document.exportPresets.list)
+@msg	no saved export presets to export (name built-in ones in `names`)	nessun predefinito di esportazione salvato da esportare (indica quelli integrati in `names`)
+@msg	not an export presets file (format `{_1}`)	non è un file di predefiniti di esportazione (formato `{_1}`)
+@msg	not an export presets file: {e}	non è un file di predefiniti di esportazione: {e}
+@msg	`{_1}`: {msg}	`{_1}`: {msg}
 	Export Layer	Esporta livello
 	Export Selection	Esporta selezione
 	All in One Folder	Tutto in una cartella
@@ -1188,6 +1198,7 @@ file-filter	Swatch libraries	Librerie campioni
 file-filter	Flattener presets	Preset di appiattimento
 file-filter	PDF presets	Preset PDF
 file-filter	Print presets	Preset di stampa
+file-filter	Export presets	Predefiniti di esportazione
 file-filter	SVG Compressed	SVG compresso
 file-filter	PDF-compatible .ai	.ai compatibile con PDF
 file-filter	VectorCraft Template	Modello VectorCraft
diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv
--- a/crates/ui-egui/src/i18n/ja.tsv
+++ b/crates/ui-egui/src/i18n/ja.tsv
@@ -1443,6 +1443,16 @@ font-technology	Variable	バリアブル
 	Delete Export Preset	書き出しプリセットを削除
 	Export Presets	書き出しプリセット
 	Save Export Preset	書き出しプリセットを保存
+	Export Export Presets	書き出しプリセットを書き出し
+	Export Preset…	プリセットを書き出し…
+	Import Export Presets	書き出しプリセットを読み込み
+	Import Preset…	プリセットを読み込み…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset` には document.exportPresets.list にあるプリセットの名前を指定してください
+@msg	no export preset `{n}` (see document.exportPresets.list)	書き出しプリセット `{n}` が見つかりません (document.exportPresets.list を参照)
+@msg	no saved export presets to export (name built-in ones in `names`)	書き出す保存済みの書き出しプリセットがありません (組み込みのプリセットは `names` で指定してください)
+@msg	not an export presets file (format `{_1}`)	書き出しプリセットファイルではありません (形式: `{_1}`)
+@msg	not an export presets file: {e}	書き出しプリセットファイルではありません: {e}
+@msg	`{_1}`: {msg}	`{_1}`: {msg}
 	Export Layer	レイヤーを書き出し
 	Export Selection	選択範囲を書き出し
 	All in One Folder	すべて 1 つのフォルダーに
@@ -3395,6 +3405,7 @@ file-filter	Swatch libraries	スウォッチライブラリ
 file-filter	Flattener presets	分割・統合プリセット
 file-filter	PDF presets	PDF プリセット
 file-filter	Print presets	プリントプリセット
+file-filter	Export presets	書き出しプリセット
 file-filter	SVG Compressed	SVG 圧縮
 file-filter	PDF-compatible .ai	PDF 互換の .ai
 file-filter	VectorCraft Template	VectorCraft テンプレート
diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv
--- a/crates/ui-egui/src/i18n/ka.tsv
+++ b/crates/ui-egui/src/i18n/ka.tsv
@@ -4624,6 +4624,7 @@ file-filter	Swatch libraries	ფერის ნიმუშების ბი
 file-filter	Flattener presets	გაბრტყელების პრესეტები
 file-filter	PDF presets	PDF-ის პრესეტები
 file-filter	Print presets	ბეჭდვის პრესეტები
+file-filter	Export presets	ექსპორტის პრესეტები
 file-filter	SVG Compressed	შეკუმშული SVG
 file-filter	PDF-compatible .ai	PDF-თან თავსებადი .ai
 file-filter	VectorCraft Template	VectorCraft-ის შაბლონი
@@ -4996,6 +4997,16 @@ imageTrace	Outline	კონტური
 	Delete Export Preset	ექსპორტის პრესეტის წაშლა
 	Export Presets	ექსპორტის პრესეტები
 	Save Export Preset	ექსპორტის პრესეტის შენახვა
+	Export Export Presets	ექსპორტის პრესეტების ექსპორტირება
+	Export Preset…	პრესეტის ექსპორტირება…
+	Import Export Presets	ექსპორტის პრესეტების იმპორტირება
+	Import Preset…	პრესეტის იმპორტირება…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset` არის პრესეტის სახელი სიიდან document.exportPresets.list
+@msg	no export preset `{n}` (see document.exportPresets.list)	ექსპორტის პრესეტი `{n}` არ არის (იხ. document.exportPresets.list)
+@msg	no saved export presets to export (name built-in ones in `names`)	შენახული ექსპორტის პრესეტები ექსპორტისთვის არ არის (ჩაშენებულები მიუთითეთ `names`-ში)
+@msg	not an export presets file (format `{_1}`)	ეს არ არის ექსპორტის პრესეტების ფაილი (ფორმატი `{_1}`)
+@msg	not an export presets file: {e}	ეს არ არის ექსპორტის პრესეტების ფაილი: {e}
+@msg	`{_1}`: {msg}	`{_1}`: {msg}
 	Export Layer	შრის ექსპორტირება
 	Export Selection	მონიშნულის ექსპორტირება
 	All in One Folder	ყველა ერთ საქაღალდეში
diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv
--- a/crates/ui-egui/src/i18n/ko.tsv
+++ b/crates/ui-egui/src/i18n/ko.tsv
@@ -1438,6 +1438,16 @@ font-technology	Variable	가변
 	Delete Export Preset	내보내기 사전 설정 삭제
 	Export Presets	내보내기 사전 설정
 	Save Export Preset	내보내기 사전 설정 저장
+	Export Export Presets	내보내기 사전 설정 내보내기
+	Export Preset…	사전 설정 내보내기…
+	Import Export Presets	내보내기 사전 설정 가져오기
+	Import Preset…	사전 설정 가져오기…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset`은(는) document.exportPresets.list에 있는 사전 설정의 이름입니다
+@msg	no export preset `{n}` (see document.exportPresets.list)	내보내기 사전 설정 `{n}`이(가) 없습니다(document.exportPresets.list 참조)
+@msg	no saved export presets to export (name built-in ones in `names`)	내보낼 저장된 내보내기 사전 설정이 없습니다(기본 제공 사전 설정은 `names`에 지정하세요)
+@msg	not an export presets file (format `{_1}`)	내보내기 사전 설정 파일이 아닙니다(형식 `{_1}`)
+@msg	not an export presets file: {e}	내보내기 사전 설정 파일이 아닙니다: {e}
+@msg	`{_1}`: {msg}	`{_1}`: {msg}
 	Export Layer	레이어 내보내기
 	Export Selection	선택 영역 내보내기
 	All in One Folder	모두 폴더 하나에
@@ -3388,6 +3398,7 @@ file-filter	Swatch libraries	견본 라이브러리
 file-filter	Flattener presets	병합 프리셋
 file-filter	PDF presets	PDF 프리셋
 file-filter	Print presets	인쇄 프리셋
+file-filter	Export presets	내보내기 사전 설정
 file-filter	SVG Compressed	압축된 SVG
 file-filter	PDF-compatible .ai	PDF 호환 .ai
 file-filter	VectorCraft Template	VectorCraft 템플릿
diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv
--- a/crates/ui-egui/src/i18n/nl.tsv
+++ b/crates/ui-egui/src/i18n/nl.tsv
@@ -4807,6 +4807,7 @@ file-filter	Swatch libraries	Stalenbibliotheken
 file-filter	Flattener presets	Afvlakvoorinstellingen
 file-filter	PDF presets	PDF-voorinstellingen
 file-filter	Print presets	Afdrukvoorinstellingen
+file-filter	Export presets	Exportvoorinstellingen
 file-filter	SVG Compressed	Gecomprimeerde SVG
 file-filter	PDF-compatible .ai	PDF-compatibel .ai
 file-filter	VectorCraft Template	VectorCraft-sjabloon
@@ -5150,6 +5151,10 @@ swatch	Bright Violet	Felviolet
 	Delete Export Preset	Exportvoorinstelling verwijderen
 	Export Presets	Exportvoorinstellingen
 	Save Export Preset	Exportvoorinstelling opslaan
+	Export Export Presets	Exportvoorinstellingen exporteren
+	Export Preset…	Voorinstelling exporteren…
+	Import Export Presets	Exportvoorinstellingen importeren
+	Import Preset…	Voorinstelling importeren…
 	Export Layer	Laag exporteren
 	Export Selection	Selectie exporteren
 	All in One Folder	Alles in één map
diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv
--- a/crates/ui-egui/src/i18n/pl.tsv
+++ b/crates/ui-egui/src/i18n/pl.tsv
@@ -2752,6 +2752,16 @@ lighting	Light	Światło
 	Delete Export Preset	Usuń ustawienie eksportu
 	Export Presets	Ustawienia eksportu
 	Save Export Preset	Zapisz ustawienie eksportu
+	Export Export Presets	Eksportuj ustawienia eksportu
+	Export Preset…	Eksportuj ustawienie…
+	Import Export Presets	Importuj ustawienia eksportu
+	Import Preset…	Importuj ustawienie…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset` to nazwa ustawienia z document.exportPresets.list
+@msg	no export preset `{n}` (see document.exportPresets.list)	brak ustawienia eksportu `{n}` (zob. document.exportPresets.list)
+@msg	no saved export presets to export (name built-in ones in `names`)	brak zapisanych ustawień eksportu do wyeksportowania (wbudowane podaj w `names`)
+@msg	not an export presets file (format `{_1}`)	to nie jest plik ustawień eksportu (format `{_1}`)
+@msg	not an export presets file: {e}	to nie jest plik ustawień eksportu: {e}
+@msg	`{_1}`: {msg}	`{_1}`: {msg}
 	Export Layer	Eksportuj warstwę
 	Export Selection	Eksportuj zaznaczenie
 	All in One Folder	Wszystko w jednym folderze
@@ -4983,6 +4993,7 @@ file-filter	Swatch libraries	Biblioteki próbek
 file-filter	Flattener presets	Ustawienia spłaszczania
 file-filter	PDF presets	Ustawienia PDF
 file-filter	Print presets	Ustawienia drukowania
+file-filter	Export presets	Ustawienia eksportu
 file-filter	SVG Compressed	SVG skompresowany
 file-filter	PDF-compatible .ai	.ai zgodny z PDF
 file-filter	VectorCraft Template	Szablon VectorCraft
diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv
--- a/crates/ui-egui/src/i18n/pt-br.tsv
+++ b/crates/ui-egui/src/i18n/pt-br.tsv
@@ -900,6 +900,16 @@
 	Delete Export Preset	Excluir predefinição de exportação
 	Export Presets	Predefinições de exportação
 	Save Export Preset	Salvar predefinição de exportação
+	Export Export Presets	Exportar predefinições de exportação
+	Export Preset…	Exportar predefinição…
+	Import Export Presets	Importar predefinições de exportação
+	Import Preset…	Importar predefinição…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset` é o nome de uma predefinição de document.exportPresets.list
+@msg	no export preset `{n}` (see document.exportPresets.list)	nenhuma predefinição de exportação `{n}` (consulte document.exportPresets.list)
+@msg	no saved export presets to export (name built-in ones in `names`)	nenhuma predefinição de exportação salva para exportar (informe as integradas em `names`)
+@msg	not an export presets file (format `{_1}`)	não é um arquivo de predefinições de exportação (formato `{_1}`)
+@msg	not an export presets file: {e}	não é um arquivo de predefinições de exportação: {e}
+@msg	`{_1}`: {msg}	`{_1}`: {msg}
 	Export Layer	Exportar camada
 	Export Selection	Exportar seleção
 	All in One Folder	Tudo em uma pasta
@@ -5021,6 +5031,7 @@ file-filter	Swatch libraries	Bibliotecas de amostras
 file-filter	Flattener presets	Predefinidos de achatamento
 file-filter	PDF presets	Predefinidos de PDF
 file-filter	Print presets	Predefinidos de impressão
+file-filter	Export presets	Predefinições de exportação
 file-filter	SVG Compressed	SVG Compactado
 file-filter	PDF-compatible .ai	.ai compatível com PDF
 file-filter	VectorCraft Template	Modelo do VectorCraft
diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv
--- a/crates/ui-egui/src/i18n/ru.tsv
+++ b/crates/ui-egui/src/i18n/ru.tsv
@@ -1015,6 +1015,16 @@ font-technology	Variable	Вариативные
 	Delete Export Preset	Удалить набор настроек экспорта
 	Export Presets	Наборы настроек экспорта
 	Save Export Preset	Сохранить набор настроек экспорта
+	Export Export Presets	Экспортировать наборы настроек экспорта
+	Export Preset…	Экспортировать набор…
+	Import Export Presets	Импортировать наборы настроек экспорта
+	Import Preset…	Импортировать набор…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset` — имя набора из document.exportPresets.list
+@msg	no export preset `{n}` (see document.exportPresets.list)	нет набора настроек экспорта `{n}` (см. document.exportPresets.list)
+@msg	no saved export presets to export (name built-in ones in `names`)	нет сохранённых наборов настроек экспорта для экспорта (встроенные укажите в `names`)
+@msg	not an export presets file (format `{_1}`)	не является файлом наборов настроек экспорта (формат `{_1}`)
+@msg	not an export presets file: {e}	не является файлом наборов настроек экспорта: {e}
+@msg	`{_1}`: {msg}	`{_1}`: {msg}
 	Export Layer	Экспортировать слой
 	Export Selection	Экспортировать выделение
 	All in One Folder	Всё в одну папку
@@ -1186,6 +1196,7 @@ file-filter	Swatch libraries	Библиотеки образцов
 file-filter	Flattener presets	Наборы объединения
 file-filter	PDF presets	Наборы PDF
 file-filter	Print presets	Наборы печати
+file-filter	Export presets	Наборы настроек экспорта
 file-filter	SVG Compressed	Сжатый SVG
 file-filter	PDF-compatible .ai	.ai, совместимый с PDF
 file-filter	VectorCraft Template	Шаблон VectorCraft
diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv
--- a/crates/ui-egui/src/i18n/sv.tsv
+++ b/crates/ui-egui/src/i18n/sv.tsv
@@ -1002,6 +1002,16 @@ font-technology	Variable	Variabla
 	Delete Export Preset	Ta bort exportförinställning
 	Export Presets	Exportförinställningar
 	Save Export Preset	Spara exportförinställning
+	Export Export Presets	Exportera exportförinställningar
+	Export Preset…	Exportera förinställning…
+	Import Export Presets	Importera exportförinställningar
+	Import Preset…	Importera förinställning…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset` är namnet på en förinställning i document.exportPresets.list
+@msg	no export preset `{n}` (see document.exportPresets.list)	ingen exportförinställning `{n}` (se document.exportPresets.list)
+@msg	no saved export presets to export (name built-in ones in `names`)	inga sparade exportförinställningar att exportera (ange inbyggda i `names`)
+@msg	not an export presets file (format `{_1}`)	inte en fil med exportförinställningar (format `{_1}`)
+@msg	not an export presets file: {e}	inte en fil med exportförinställningar: {e}
+@msg	`{_1}`: {msg}	`{_1}`: {msg}
 	Export Layer	Exportera lager
 	Export Selection	Exportera markering
 	All in One Folder	Allt i en mapp
@@ -5118,6 +5128,7 @@ file-filter	Flattener presets	Förinställningar för plattning
 file-filter	PDF presets	PDF-förinställningar
 file-filter	PDF-compatible .ai	PDF-kompatibel .ai
 file-filter	Print presets	Utskriftsförinställningar
+file-filter	Export presets	Exportförinställningar
 file-filter	SVG Compressed	SVG komprimerad
 file-filter	Swatch libraries	Färgrutebibliotek
 file-filter	Templates	Mallar
diff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs
--- a/crates/ui-egui/src/i18n/tests.rs
+++ b/crates/ui-egui/src/i18n/tests.rs
@@ -1709,6 +1709,7 @@ const RENDER_SITE_ROWS: &[(&str, &str)] = &[
     ("file-filter", "Flattener presets"),
     ("file-filter", "PDF presets"),
     ("file-filter", "Print presets"),
+    ("file-filter", "Export presets"),
     ("file-filter", "SVG Compressed"),
     ("file-filter", "PDF-compatible .ai"),
     ("file-filter", "VectorCraft Template"),
diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv
--- a/crates/ui-egui/src/i18n/tr.tsv
+++ b/crates/ui-egui/src/i18n/tr.tsv
@@ -994,6 +994,16 @@ font-technology	Variable	Değişken
 	Delete Export Preset	Dışa Aktarma Ön Ayarını Sil
 	Export Presets	Ön Ayarları Dışa Aktarma
 	Save Export Preset	Dışa Aktarma Ön Ayarını Kaydet
+	Export Export Presets	Dışa Aktarma Ön Ayarlarını Dışa Aktar
+	Export Preset…	Ön Ayarı Dışa Aktar…
+	Import Export Presets	Dışa Aktarma Ön Ayarlarını İçe Aktar
+	Import Preset…	Ön Ayarı İçe Aktar…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset`, document.exportPresets.list içindeki bir ön ayarın adıdır
+@msg	no export preset `{n}` (see document.exportPresets.list)	`{n}` adında dışa aktarma ön ayarı yok (bkz. document.exportPresets.list)
+@msg	no saved export presets to export (name built-in ones in `names`)	dışa aktarılacak kayıtlı dışa aktarma ön ayarı yok (yerleşik olanları `names` içinde belirtin)
+@msg	not an export presets file (format `{_1}`)	dışa aktarma ön ayarları dosyası değil (`{_1}` biçimi)
+@msg	not an export presets file: {e}	dışa aktarma ön ayarları dosyası değil: {e}
+@msg	`{_1}`: {msg}	`{_1}`: {msg}
 	Export Layer	Katmanı Dışa Aktar
 	Export Selection	Seçimi Dışa Aktar
 	All in One Folder	Hepsi Bir Klasörde
@@ -4920,6 +4930,7 @@ effect	Regular	Düzenli
 effect	Vertical	Dikey
 file-filter	All placeable files	Yerleştirilebilir tüm dosyalar
 file-filter	All readable files	Okunabilir tüm dosyalar
+file-filter	Export presets	Dışa aktarma önayarları
 file-filter	Flattener presets	Düzleştirici önayarları
 file-filter	PDF presets	PDF önayarları
 file-filter	PDF-compatible .ai	PDF ile uyumlu .ai
diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv
--- a/crates/ui-egui/src/i18n/uk.tsv
+++ b/crates/ui-egui/src/i18n/uk.tsv
@@ -1011,6 +1011,16 @@ font-technology	Variable	Варіативні
 	Delete Export Preset	Видалити набір параметрів експорту
 	Export Presets	Набори параметрів експорту
 	Save Export Preset	Зберегти набір параметрів експорту
+	Export Export Presets	Експортувати набори параметрів експорту
+	Export Preset…	Експортувати набір…
+	Import Export Presets	Імпортувати набори параметрів експорту
+	Import Preset…	Імпортувати набір…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset` — це ім’я набору з document.exportPresets.list
+@msg	no export preset `{n}` (see document.exportPresets.list)	немає набору параметрів експорту `{n}` (див. document.exportPresets.list)
+@msg	no saved export presets to export (name built-in ones in `names`)	немає збережених наборів параметрів експорту для експорту (вбудовані вкажіть у `names`)
+@msg	not an export presets file (format `{_1}`)	не є файлом наборів параметрів експорту (формат `{_1}`)
+@msg	not an export presets file: {e}	не є файлом наборів параметрів експорту: {e}
+@msg	`{_1}`: {msg}	`{_1}`: {msg}
 	Export Layer	Експортувати шар
 	Export Selection	Експортувати виділення
 	All in One Folder	Усе в одну теку
@@ -1182,6 +1192,7 @@ file-filter	Swatch libraries	Бібліотеки зразків
 file-filter	Flattener presets	Набори зведення
 file-filter	PDF presets	Набори PDF
 file-filter	Print presets	Набори друку
+file-filter	Export presets	Набори параметрів експорту
 file-filter	SVG Compressed	Стиснутий SVG
 file-filter	PDF-compatible .ai	.ai, сумісний з PDF
 file-filter	VectorCraft Template	Шаблон VectorCraft
diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv
--- a/crates/ui-egui/src/i18n/vi.tsv
+++ b/crates/ui-egui/src/i18n/vi.tsv
@@ -1175,6 +1175,16 @@ font-technology	Variable	Biến thiên
 	Delete Export Preset	Xóa cài đặt sẵn xuất
 	Export Presets	Cài đặt sẵn xuất
 	Save Export Preset	Lưu cài đặt sẵn xuất
+	Export Export Presets	Xuất cài đặt sẵn xuất
+	Export Preset…	Xuất cài đặt sẵn…
+	Import Export Presets	Nhập cài đặt sẵn xuất
+	Import Preset…	Nhập cài đặt sẵn…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset` là tên của một cài đặt sẵn trong document.exportPresets.list
+@msg	no export preset `{n}` (see document.exportPresets.list)	không có cài đặt sẵn xuất `{n}` (xem document.exportPresets.list)
+@msg	no saved export presets to export (name built-in ones in `names`)	không có cài đặt sẵn xuất đã lưu nào để xuất (ghi các cài đặt tích hợp sẵn trong `names`)
+@msg	not an export presets file (format `{_1}`)	không phải tệp cài đặt sẵn xuất (định dạng `{_1}`)
+@msg	not an export presets file: {e}	không phải tệp cài đặt sẵn xuất: {e}
+@msg	`{_1}`: {msg}	`{_1}`: {msg}
 	Export Layer	Xuất lớp
 	Export Selection	Xuất vùng chọn
 	All in One Folder	Tất cả trong một thư mục
@@ -1379,6 +1389,7 @@ file-filter	Swatch libraries	Thư viện mẫu màu
 file-filter	Flattener presets	Thiết lập sẵn làm phẳng
 file-filter	PDF presets	Thiết lập sẵn PDF
 file-filter	Print presets	Thiết lập sẵn in
+file-filter	Export presets	Cài đặt sẵn xuất
 file-filter	SVG Compressed	SVG nén
 file-filter	PDF-compatible .ai	.ai tương thích PDF
 file-filter	VectorCraft Template	Mẫu VectorCraft
diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv
--- a/crates/ui-egui/src/i18n/zh-hans.tsv
+++ b/crates/ui-egui/src/i18n/zh-hans.tsv
@@ -957,6 +957,16 @@
 	Delete Export Preset	删除导出预设
 	Export Presets	导出预设
 	Save Export Preset	存储导出预设
+	Export Export Presets	导出导出预设
+	Export Preset…	导出预设…
+	Import Export Presets	导入导出预设
+	Import Preset…	导入预设…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset` 是 document.exportPresets.list 中某个预设的名称
+@msg	no export preset `{n}` (see document.exportPresets.list)	没有导出预设 `{n}` (参见 document.exportPresets.list)
+@msg	no saved export presets to export (name built-in ones in `names`)	没有可导出的已保存导出预设 (内置预设请在 `names` 中指定)
+@msg	not an export presets file (format `{_1}`)	不是导出预设文件 (格式为 `{_1}`)
+@msg	not an export presets file: {e}	不是导出预设文件：{e}
+@msg	`{_1}`: {msg}	`{_1}`：{msg}
 	Export Layer	导出图层
 	Export Selection	导出选区
 	All in One Folder	全部放在一个文件夹
@@ -1126,6 +1136,7 @@ file-filter	Swatch libraries	色板库
 file-filter	Flattener presets	拼合预设
 file-filter	PDF presets	PDF 预设
 file-filter	Print presets	打印预设
+file-filter	Export presets	导出预设
 file-filter	SVG Compressed	压缩 SVG
 file-filter	PDF-compatible .ai	PDF 兼容 .ai
 file-filter	VectorCraft Template	VectorCraft 模板
diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv
--- a/crates/ui-egui/src/i18n/zh-hant.tsv
+++ b/crates/ui-egui/src/i18n/zh-hant.tsv
@@ -981,6 +981,16 @@ font-technology	Variable	可變
 	Delete Export Preset	刪除轉存預設集
 	Export Presets	轉存預設集
 	Save Export Preset	儲存轉存預設集
+	Export Export Presets	匯出轉存預設集
+	Export Preset…	匯出預設集…
+	Import Export Presets	匯入轉存預設集
+	Import Preset…	匯入預設集…
+@msg	`exportPreset` is the name of a preset of document.exportPresets.list	`exportPreset` 是 document.exportPresets.list 中某個預設集的名稱
+@msg	no export preset `{n}` (see document.exportPresets.list)	沒有轉存預設集 `{n}` (請參閱 document.exportPresets.list)
+@msg	no saved export presets to export (name built-in ones in `names`)	沒有可轉存的已儲存轉存預設集 (內建預設集請在 `names` 中指定)
+@msg	not an export presets file (format `{_1}`)	不是轉存預設集檔案 (格式為 `{_1}`)
+@msg	not an export presets file: {e}	不是轉存預設集檔案：{e}
+@msg	`{_1}`: {msg}	`{_1}`：{msg}
 	Export Layer	轉存圖層
 	Export Selection	轉存選取範圍
 	All in One Folder	全部放在一個資料夾
@@ -1152,6 +1162,7 @@ file-filter	Swatch libraries	色票資料庫
 file-filter	Flattener presets	平面化預設集
 file-filter	PDF presets	PDF 預設集
 file-filter	Print presets	列印預設集
+file-filter	Export presets	轉存預設集
 file-filter	SVG Compressed	壓縮 SVG
 file-filter	PDF-compatible .ai	PDF 相容 .ai
 file-filter	VectorCraft Template	VectorCraft 範本
diff --git a/crates/ui-egui/src/io.rs b/crates/ui-egui/src/io.rs
--- a/crates/ui-egui/src/io.rs
+++ b/crates/ui-egui/src/io.rs
@@ -18,7 +18,7 @@ const TEMPLATE_EXTS: &[&str] = &["vctemplate", "ait", "vectorcraft", "drawcraft"
 
 /// Open bytes of any readable format as a new document (templates open untitled); swatch and
 /// graphic style library files open in the library panel, Libraries panel files are added to it,
-/// flattener, PDF, print and perspective grid presets files are imported, a shortcut set another
+/// flattener, PDF, print, perspective grid and export presets files are imported, a shortcut set another
 /// app saved (`.kys`) opens Import Shortcut Set, and a script written for the reference app
 /// (`.jsx`, `.jsxinc`, `.jsxbin`) shows its check (Check Script…). → `document.open`'s result for
 /// a document opened now (its `warnings` say what didn't come in as it was), else null.
@@ -40,21 +40,18 @@ pub fn open_bytes(app: &mut VectorcraftApp, name: &str, bytes: &[u8], path: Opti
         return dialogs::script_check::command(app, &p).map(|_| Value::Null);
     }
     if let Some(OpensAs::Presets { import, what }) = opens {
-        let r = app.run(import, serde_json::json!({"data": String::from_utf8_lossy(bytes)}))?;
-        let names: Vec<&str> = r["imported"].as_array().into_iter().flatten().filter_map(Value::as_str).collect();
-        app.status(format!("Imported {what}: {}", names.join(", ")));
+        import_presets(app, import, what, serde_json::json!({"dataBase64": vectorcraft_format::base64_encode(bytes)}))?;
         return Ok(Value::Null);
     }
     if opens == Some(OpensAs::Library) {
-        return crate::panels::libraries::import(app, json!({"data": String::from_utf8_lossy(bytes)})).map(|_| Value::Null);
+        return crate::panels::libraries::import(app, json!({"dataBase64": vectorcraft_format::base64_encode(bytes)})).map(|_| Value::Null);
     }
     let swatches = opens == Some(OpensAs::SwatchLibrary);
     if swatches || opens == Some(OpensAs::StyleLibrary) {
         let p = match path {
             Some(path) => serde_json::json!({ "path": path }),
-            // `.ase` swatch libraries are binary.
-            None if swatches => serde_json::json!({"name": name, "dataBase64": vectorcraft_format::base64_encode(bytes)}),
-            None => serde_json::json!({"name": name, "data": String::from_utf8_lossy(bytes)}),
+            // The file's bytes as they are (`.ase` swatch libraries are binary).
+            None => serde_json::json!({"name": name, "dataBase64": vectorcraft_format::base64_encode(bytes)}),
         };
         let load = if swatches { crate::panels::swatches::load_library } else { crate::panels::graphic_styles::load_library };
         return load(app, p).map(|_| Value::Null);
@@ -79,7 +76,7 @@ pub enum OpensAs {
     ShortcutSet,
     /// A script written for the reference app: checked without running it (Check Script…).
     Script,
-    /// Flattener, PDF, print or perspective grid presets: imported with command `import`, and
+    /// Flattener, PDF, print, perspective grid or export presets: imported with command `import`, and
     /// `what` names them in the status bar.
     Presets { import: &'static str, what: &'static str },
     /// A Libraries panel file: added to the Libraries panel as a new library.
@@ -90,6 +87,18 @@ pub enum OpensAs {
     StyleLibrary,
 }
 
+/// What the status bar calls the presets of an export presets file.
+pub(crate) const EXPORT_PRESETS: &str = "export presets";
+
+/// Run presets command `import` with `p` (the file) and say in the status bar which presets
+/// (`what`) it added → its result.
+pub(crate) fn import_presets(app: &mut VectorcraftApp, import: &str, what: &str, p: Value) -> Result<Value, String> {
+    let r = app.run(import, p)?;
+    let names: Vec<&str> = r["imported"].as_array().into_iter().flatten().filter_map(Value::as_str).collect();
+    app.status(format!("Imported {what}: {}", names.join(", ")));
+    Ok(r)
+}
+
 /// The kind of file named `name` (by its extension) that File → Open installs, imports, checks,
 /// shows in the library panel or the Libraries panel, or reviews in Import Shortcut Set
 /// ([`open_bytes`]), else `None`. A drop opens these files the same way, wherever they are dropped
@@ -101,6 +110,7 @@ pub fn opens_as(name: &str) -> Option<OpensAs> {
         (vectorcraft_engine::cmd::pdfcmds::PRESET_EXTS, "pdf.preset.import", "PDF presets"),
         (vectorcraft_engine::cmd::printpresets::PRESET_EXTS, "print.presets.import", "print presets"),
         (vectorcraft_engine::cmd::perspgrid::PRESET_EXTS, "perspective.presets.import", "perspective grid presets"),
+        (fileio::SCREEN_EXPORT_PRESET_EXTS, "document.exportPresets.import", EXPORT_PRESETS),
     ];
     if vectorcraft_engine::cmd::plugin::EXTS.contains(&ext.as_str()) {
         Some(OpensAs::Plugin)
@@ -442,6 +452,7 @@ const FILE_TYPES: &[(&str, &[&str])] = &[
     ("PDF presets", vectorcraft_engine::cmd::pdfcmds::PRESET_EXTS),
     ("Perspective grid presets", vectorcraft_engine::cmd::perspgrid::PRESET_EXTS),
     ("Print presets", vectorcraft_engine::cmd::printpresets::PRESET_EXTS),
+    ("Export presets", fileio::SCREEN_EXPORT_PRESET_EXTS),
     ("CSS", &["css"]),
     ("PostScript", &["ps"]),
 ];
@@ -1326,4 +1337,27 @@ mod tests {
         assert_eq!(&png[16..20], 60u32.to_be_bytes(), "30 pt at scale 2");
         assert!(written.borrow().is_empty(), "no file and no save dialog");
     }
+
+    /// A presets file, a Libraries panel file or a graphic style library that is opened or dropped
+    /// goes to its importer as the file's bytes: a byte that is not UTF-8 is refused instead of
+    /// read as a replacement character.
+    #[test]
+    fn opened_preset_and_library_files_keep_their_bytes() {
+        let (mut app, _) = app();
+        app.run("file.new", json!({"width": 60, "height": 40})).unwrap();
+        app.run("document.exportPresets.save", json!({"name": "Pack", "settings": {"formats": [{"format": "svg"}]}})).unwrap();
+        let presets = app.run("document.exportPresets.export", json!({"names": ["Pack"]})).unwrap()["data"].as_str().unwrap().to_string();
+        let lib = app.run("library.create", json!({"name": "Brand"})).unwrap()["id"].as_str().unwrap().to_string();
+        let library = app.run("library.export", json!({ "library": lib })).unwrap()["data"].as_str().unwrap().to_string();
+        let styles = app.run("graphicStyle.saveLibrary", json!({"name": "Kit"})).unwrap()["data"].as_str().unwrap().to_string();
+        for (file, text, name) in [("pack.vcexportpresets", presets, "Pack"), ("brand.vclibrary", library, "Brand"), ("kit.vcstyles", styles, "Kit")]
+        {
+            let mut bytes = text.as_bytes().to_vec();
+            bytes.insert(text.find(name).unwrap() + 2, 0xFF);
+            assert!(open_bytes(&mut app, file, &bytes, None).is_err(), "{file}: {}", app.ui.status);
+            // The file as it was still opens.
+            open_bytes(&mut app, file, text.as_bytes(), None).unwrap();
+        }
+        assert_eq!(app.session.prefs.screen_export_presets.iter().map(|p| p.name.as_str()).collect::<Vec<_>>(), ["Pack", "Pack 2"]);
+    }
 }
diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs
--- a/crates/ui-egui/src/lib.rs
+++ b/crates/ui-egui/src/lib.rs
@@ -597,6 +597,9 @@ pub struct VectorcraftApp {
     pub(crate) ime_discard: bool,
     /// Preferences were confirmed (see [`Self::take_prefs_changed`]).
     pub(crate) prefs_changed: bool,
+    /// The export presets the Asset Export panel lists (`document.exportPresets.list`'s rows), built
+    /// again when the saved presets change ([`dialogs::screen_preset_list`]).
+    pub(crate) asset_export_presets: Value,
     /// Whether the last frame's input reported a touch screen (Preferences › Devices › Touch Workspace).
     pub touch_screen: bool,
     /// A numeric field is being scrubbed: the document's edits meanwhile are one undo step
@@ -844,6 +847,7 @@ impl VectorcraftApp {
             ime_caret_rect: None,
             ime_discard: false,
             prefs_changed: false,
+            asset_export_presets: Value::Null,
             touch_screen: false,
             scrub_group: false,
             scrub_scope: None,
diff --git a/crates/ui-egui/src/panels/asset_export.rs b/crates/ui-egui/src/panels/asset_export.rs
--- a/crates/ui-egui/src/panels/asset_export.rs
+++ b/crates/ui-egui/src/panels/asset_export.rs
@@ -2,7 +2,8 @@
 //! names. Art dragged in off the canvas becomes assets, one per object (with Alt held, one asset
 //! of them all), and so does the selection with the + button. Clicking selects assets (Shift or
 //! Cmd adds), double-clicking one renames it and the bin removes the selected ones. Below are the
-//! export settings, the format rows Export for Screens shows too (one set per document), and
+//! export settings: Export for Screens' Export Preset row (apply, save, delete, import or export a
+//! preset) and the format rows it shows too (one set per document), and
 //! Export, which writes the selected assets (else all of them) into a picked folder (on the
 //! desktop with Background Export on, on the background thread); the web downloads them. The
 //! screen button opens Export for Screens on its Assets tab.
@@ -17,9 +18,13 @@ use egui::{Color32, Rect, Sense, Stroke, StrokeKind, Ui, vec2};
 use serde_json::{Value, json};
 use vectorcraft_doc::{ExportAsset, Node, NodeId};
 use vectorcraft_engine::DocState;
+use vectorcraft_engine::cmd::fileio::SCREEN_SETTINGS_KEYS;
 
 use super::{pstate, set_pstate};
-use crate::dialogs::{EXPORT_FOR_SCREENS, open_export_for_screens_assets, screen_formats, screen_saved_options, screen_saved_rows};
+use crate::dialogs::{
+    EXPORT_FOR_SCREENS, ScreenPresetPick, open_export_for_screens_assets, screen_export_preset, screen_formats, screen_import_presets,
+    screen_preset_list, screen_preset_row, screen_saved_options, screen_saved_rows,
+};
 use crate::state::Dialog;
 use crate::theme::Tokens;
 use crate::widgets::{self, menu_item};
@@ -30,6 +35,10 @@ pub const ID: &str = "assetExport";
 const TILE: egui::Vec2 = vec2(70.0, 84.0);
 const THUMB: f32 = 58.0;
 const SELECTED: &str = "asset-export-selected";
+/// The export preset applied last: (the document's uid, its name; "" for none).
+const PRESET: &str = "asset-export-preset";
+/// The name being typed to save a preset (none when not saving).
+const SAVING: &str = "asset-export-save-preset";
 
 /// The assets selected in the panel (the panel's own state) that are still in the document.
 fn selected(ctx: &egui::Context, st: &DocState) -> Vec<u64> {
@@ -249,19 +258,33 @@ enum Settings {
     Set(Value),
     /// Show a format's Format Settings (in Export for Screens).
     Format(String),
+    /// Apply, save, delete, import or export an export preset.
+    Preset(ScreenPresetPick),
 }
 
-/// The export settings: Export for Screens' format rows on the document's shared settings.
-/// Returns the change asked for and how many files one asset makes.
-fn settings(ui: &mut Ui, st: &DocState) -> (Option<Settings>, usize) {
+/// The export settings: Export for Screens' Export Preset row (`presets`: the rows of
+/// `document.exportPresets.list`) with Import Preset… and Export Preset…, and the format rows on
+/// the document's shared settings. Returns the change asked for and how many files one asset
+/// makes.
+fn settings(ui: &mut Ui, st: &DocState, presets: &Value) -> (Option<Settings>, usize) {
     let (rows, preset) = screen_saved_rows(&st.doc.export_settings);
     let preset = preset.unwrap_or_default().to_string();
     let n = rows.len();
-    let mut d = Dialog::new(EXPORT_FOR_SCREENS, json!({ "formats": &rows, "preset": preset, "__settings": "" }));
+    let (doc, current): (u64, String) = pstate(ui.ctx(), PRESET);
+    let current = if doc == st.uid { current } else { String::new() };
+    let mut d = Dialog::new(EXPORT_FOR_SCREENS, json!({ "formats": &rows, "preset": preset, "__settings": "", "exportPreset": current }));
+    if let Some(name) = pstate::<Option<String>>(ui.ctx(), SAVING) {
+        d.fields.insert("__savePreset".into(), json!(name));
+    }
+    let pick = screen_preset_row(ui, &mut d, presets, true);
+    set_pstate(ui.ctx(), SAVING, d.fields.get("__savePreset").and_then(Value::as_str).map(str::to_string));
+    ui.add_space(4.0);
     screen_formats(ui, &mut d);
     let (shown, picked) = (d.str("__settings"), d.str("preset"));
     let formats = d.fields.remove("formats").unwrap_or(Value::Null);
-    let change = if !shown.is_empty() {
+    let change = if let Some(pick) = pick {
+        Some(Settings::Preset(pick))
+    } else if !shown.is_empty() {
         Some(Settings::Format(shown))
     } else if picked != preset && !picked.is_empty() {
         Some(Settings::Set(json!({ "preset": picked, "subfolders": true })))
@@ -276,6 +299,7 @@ fn settings(ui: &mut Ui, st: &DocState) -> (Option<Settings>, usize) {
 
 pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
     let ctx = ui.ctx().clone();
+    screen_preset_list(&mut app.asset_export_presets, &app.session.prefs.screen_export_presets);
     let Some(st) = app.session.active() else {
         widgets::dim_label(ui, tl!("No document"));
         return;
@@ -302,7 +326,8 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
     });
     ui.add_space(6.0);
     widgets::section_header(ui, tl!("Export Settings"));
-    let (change, files_each) = settings(ui, st);
+    let (change, files_each) = settings(ui, st, &app.asset_export_presets);
+    let doc = st.uid;
     let all: Vec<u64> = st.doc.assets.iter().map(|a| a.id).collect();
     let has_art = !st.selection.is_empty();
     let alt = ui.input(|i| i.modifiers.alt);
@@ -345,7 +370,13 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
         collect(app, &ctx, Some(ids), single);
     }
     match change {
-        Some(Settings::Set(p)) => _ = run(app, "assets.settings.set", p),
+        Some(Settings::Set(p)) => {
+            // Rows of its own: no longer the preset's.
+            if run(app, "assets.settings.set", p).is_some() {
+                set_pstate(&ctx, PRESET, (doc, String::new()));
+            }
+        }
+        Some(Settings::Preset(pick)) => preset(app, &ctx, doc, pick),
         Some(Settings::Format(f)) => {
             open_export_for_screens_assets(app, Some(&sel_or_all(&sel, &all)));
             if let Some(d) = app.ui.dialog.as_mut() {
@@ -369,6 +400,72 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
     }
 }
 
+/// Act on the Export Preset row of document `doc` (uid): a preset applied to the shared settings,
+/// the shared settings saved as one, a saved one deleted, presets imported from a file or the
+/// current one written to a file.
+pub(crate) fn preset(app: &mut VectorcraftApp, ctx: &egui::Context, doc: u64, pick: ScreenPresetPick) {
+    let done = match pick {
+        ScreenPresetPick::Apply(p) => {
+            let name = p["name"].as_str().unwrap_or_default().to_string();
+            app.run("assets.settings.set", json!({ "exportPreset": name })).map(|_| set_pstate(ctx, PRESET, (doc, name)))
+        }
+        ScreenPresetPick::Save(name) => {
+            let settings = shared_settings(app);
+            // The name as saved (trimmed and capped).
+            app.run("document.exportPresets.save", json!({ "name": name, "settings": settings })).map(|r| {
+                set_pstate(ctx, SAVING, None::<String>);
+                set_pstate(ctx, PRESET, (doc, r["name"].as_str().unwrap_or_default().to_string()));
+            })
+        }
+        ScreenPresetPick::Delete(name) => {
+            app.run("document.exportPresets.delete", json!({ "name": name })).map(|_| set_pstate(ctx, PRESET, (doc, String::new())))
+        }
+        // A file dialog shown off the UI thread runs these again when it answers.
+        ScreenPresetPick::Import => {
+            crate::picks::button(app, |app| {
+                if let Err(e) = screen_import_presets(app)
+                    && e != "cancelled"
+                {
+                    app.status(e);
+                }
+            });
+            Ok(())
+        }
+        ScreenPresetPick::Export(name) => {
+            crate::picks::button(app, move |app| {
+                if let Err(e) = screen_export_preset(app, &name)
+                    && e != "cancelled"
+                {
+                    app.status(e);
+                }
+            });
+            Ok(())
+        }
+    };
+    if let Err(e) = done
+        && e != "cancelled"
+    {
+        app.status(e);
+    }
+}
+
+/// The settings the panel shows, as a preset keeps them: the shared settings, with the row shown
+/// when the document has no rows or mobile/density preset of its own.
+fn shared_settings(app: &VectorcraftApp) -> Value {
+    let Some(st) = app.session.active() else { return json!({}) };
+    let saved = &st.doc.export_settings;
+    let mut settings: serde_json::Map<String, Value> =
+        SCREEN_SETTINGS_KEYS.iter().filter_map(|k| Some((k.to_string(), saved.get(*k).filter(|v| !v.is_null())?.clone()))).collect();
+    let (rows, preset) = screen_saved_rows(saved);
+    if preset.is_none() {
+        settings.remove("preset");
+        if !settings.get("formats").and_then(Value::as_array).is_some_and(|a| a.iter().any(Value::is_object)) {
+            settings.insert("formats".into(), json!(rows));
+        }
+    }
+    Value::Object(settings)
+}
+
 /// The selected assets, else all of them.
 fn sel_or_all(sel: &[u64], all: &[u64]) -> Vec<u64> {
     if sel.is_empty() { all.to_vec() } else { sel.to_vec() }
diff --git a/crates/ui-egui/src/panels/tests_asset_export.rs b/crates/ui-egui/src/panels/tests_asset_export.rs
--- a/crates/ui-egui/src/panels/tests_asset_export.rs
+++ b/crates/ui-egui/src/panels/tests_asset_export.rs
@@ -218,6 +218,72 @@ fn the_rows_are_the_shared_export_settings() {
     assert_eq!(log[0].0, "Untitled-1.zip");
 }
 
+/// The panel's Export Preset row applies a preset to the shared settings, saves them as a preset,
+/// and lists presets imported while it is open.
+#[test]
+fn the_export_preset_row_applies_saves_and_lists_presets() {
+    let (mut app, _, [a, _]) = app(true);
+    app.run("assets.add", json!({"ids": [a]})).unwrap();
+    let ctx = egui::Context::default();
+    crate::theme::install_fonts(&ctx);
+    frame(&ctx, &mut app, vec![], false);
+    let texts = frame(&ctx, &mut app, vec![], false);
+    assert!(has(&texts, "Export Preset:"));
+    // The first Custom is the preset dropdown (the format rows' preset dropdown follows it).
+    frame(&ctx, &mut app, click(at(&texts, "Custom")), false);
+    let texts = frame(&ctx, &mut app, vec![], false);
+    frame(&ctx, &mut app, click(at(&texts, "Logo Package")), false);
+    let settings = app.run("document.exportSettings", json!({})).unwrap()["settings"].clone();
+    assert_eq!(settings["formats"].as_array().map(Vec::len), Some(5), "{settings}");
+    let texts = frame(&ctx, &mut app, vec![], false);
+    assert!(has(&texts, "Logo Package"), "the dropdown shows the preset applied");
+    // Save: the shared settings under a name of their own.
+    let doc = app.session.active().unwrap().uid;
+    super::asset_export::preset(&mut app, &ctx, doc, crate::dialogs::ScreenPresetPick::Save("Mine".into()));
+    let mine = app.session.prefs.screen_export_presets.iter().find(|p| p.name == "Mine").expect("saved").clone();
+    assert_eq!(mine.settings["formats"], settings["formats"]);
+    // Create Sub-folders, off after a preset that sorts by format, is saved with it.
+    assert_eq!((mine.settings.get("folders"), mine.settings.get("subfolders")), (None, Some(&json!(false))), "{:?}", mine.settings);
+    let texts = frame(&ctx, &mut app, vec![], false);
+    assert!(has(&texts, "Mine"), "the dropdown shows the preset saved");
+    // A preset file opened while the panel shows (the web's Import) is in its dropdown.
+    let data = app.run("document.exportPresets.export", json!({"names": ["Mine"]})).unwrap()["data"].as_str().unwrap().to_string();
+    crate::io::open_bytes(&mut app, "pack.vcexportpresets", data.as_bytes(), None).unwrap();
+    let texts = frame(&ctx, &mut app, vec![], false);
+    frame(&ctx, &mut app, click(at(&texts, "Mine")), false);
+    let texts = frame(&ctx, &mut app, vec![], false);
+    assert!(has(&texts, "Mine 2"), "{:?}", texts.iter().map(|t| &t.0).collect::<Vec<_>>());
+    // A mobile preset with no sub-folders is saved again with no sub-folders.
+    app.run("document.exportPresets.save", json!({"name": "Flat", "settings": {"preset": "mobile", "folders": "none"}})).unwrap();
+    app.run("assets.settings.set", json!({"exportPreset": "Flat"})).unwrap();
+    super::asset_export::preset(&mut app, &ctx, doc, crate::dialogs::ScreenPresetPick::Save("Flat Again".into()));
+    let again = app.session.prefs.screen_export_presets.iter().find(|p| p.name == "Flat Again").expect("saved").clone();
+    assert_eq!(
+        (again.settings.get("preset"), again.settings.get("subfolders")),
+        (Some(&json!("mobile")), Some(&json!(false))),
+        "{:?}",
+        again.settings
+    );
+}
+
+/// The panel keeps its list of export presets from frame to frame, and builds it again when the
+/// saved presets change.
+#[test]
+fn the_panel_builds_the_preset_list_again_only_when_the_presets_change() {
+    let (mut app, _, _) = app(true);
+    let ctx = egui::Context::default();
+    crate::theme::install_fonts(&ctx);
+    frame(&ctx, &mut app, vec![], false);
+    app.asset_export_presets[0]["mark"] = json!(true);
+    frame(&ctx, &mut app, vec![], false);
+    frame(&ctx, &mut app, vec![], false);
+    assert_eq!(app.asset_export_presets[0]["mark"], true, "the list was not built again");
+    app.run("document.exportPresets.save", json!({"name": "Mine", "settings": {"formats": [{"format": "svg"}]}})).unwrap();
+    frame(&ctx, &mut app, vec![], false);
+    let list = &app.asset_export_presets;
+    assert!(list[0].get("mark").is_none() && list.as_array().unwrap().iter().any(|p| p["name"] == "Mine"), "{list}");
+}
+
 #[cfg(not(target_arch = "wasm32"))]
 #[test]
 fn export_writes_into_the_picked_folder() {
diff --git a/crates/ui-egui/src/tests_place.rs b/crates/ui-egui/src/tests_place.rs
--- a/crates/ui-egui/src/tests_place.rs
+++ b/crates/ui-egui/src/tests_place.rs
@@ -151,7 +151,8 @@ fn libraries_presets_and_plugins_open_wherever_they_are_dropped() {
     let rect = laid_out(&mut app, &ctx);
     let on_canvas = rect.center() + vec2(60.0, -40.0);
     let libraries = ["a.vcswatches", "a.gpl", "a.ase", "a.acb", "a.vcstyles", "a.vclibrary"];
-    let presets_and_plugins = ["a.vcflattener", "a.vcpdfpresets", "a.vcprintpresets", "a.vcperspective", "a.wasm", "a.jsx", "a.jsxinc", "a.jsxbin"];
+    let presets_and_plugins =
+        ["a.vcflattener", "a.vcpdfpresets", "a.vcprintpresets", "a.vcperspective", "a.vcexportpresets", "a.wasm", "a.jsx", "a.jsxinc", "a.jsxbin"];
     for name in libraries.into_iter().chain(presets_and_plugins) {
         assert_eq!(app.drop_target(name, None, false), DropTarget::Open, "{name}");
         assert_eq!(app.drop_target(name, Some(on_canvas), true), DropTarget::Open, "{name} on the canvas");
diff --git a/crates/ui-egui/src/tests_saveext.rs b/crates/ui-egui/src/tests_saveext.rs
--- a/crates/ui-egui/src/tests_saveext.rs
+++ b/crates/ui-egui/src/tests_saveext.rs
@@ -93,6 +93,13 @@ fn save_panels_show_the_file_type_they_write() {
         ("print.presets.save", json!({"name": "Mine"}), "print.presets.export", "Print presets", printpresets::PRESET_EXTS),
         ("flattener.presets.save", json!({"name": "Mine"}), "flattener.presets.export", "Flattener presets", flatten::PRESET_EXTS),
         ("perspective.presets.save", json!({"name": "Mine"}), "perspective.presets.export", "Perspective grid presets", perspgrid::PRESET_EXTS),
+        (
+            "document.exportPresets.save",
+            json!({"name": "Mine", "settings": {"formats": [{"format": "svg"}]}}),
+            "document.exportPresets.export",
+            "Export presets",
+            vectorcraft_engine::cmd::fileio::SCREEN_EXPORT_PRESET_EXTS,
+        ),
     ];
     for (save, params, export, label, exts) in presets {
         app.run(save, params).unwrap();
diff --git a/docs/control-protocol.md b/docs/control-protocol.md
--- a/docs/control-protocol.md
+++ b/docs/control-protocol.md
@@ -113,7 +113,7 @@ the two can still win that race.
 | `engine.execute` `ui.savePdfDialog` | `{path?, preset?, range?, …document.exportPdf options}` | opens the Save PDF dialog (`file.export.pdf` with no params opens it too). Its fields are the `document.exportPdf` options: set `compatibility` with `ui.dialog.set {field: "compatibility", value: "1.5"}`, a whole section with `{field: "compression", value: {…}}`, the artboards with `__allArtboards: false` and `range`, the visible section with `__section` (General, Compression, Marks and Bleeds, Output, Advanced, Security, Summary). `ui.dialog.confirm` writes `path` (else asks for one), returns `{path, bytes, warnings}` and opens the file when `viewAfterSaving` is set |
 | `file.save` / `file.saveAs` / `file.saveCopy` / `file.saveAsTemplate` (via `engine.execute`) | `{path?, format?, options?, svg?}` | Save writes the document's own file in its own format (a document opened from or saved as SVG/PDF saves as that again; the result's `warnings` say what the format loses). With `path` the file is written at once (Save As or a Copy to an SVG path without SVG options first opens SVG Options, below). Without one (never saved, a converted older file, Save As, a Copy, a Template) the app shows a save panel with one file type per save format (`vectorcraft`, `template`, `pdf`, `svg`, `svgz`, `ai`; for a Template, `template` and `ait`), then the format's options: SVG opens the `svgOptions` dialog (below; returns `{"pending": "svgOptions", "path"}`), PDF the `savePdf` dialog (its `__save` field names the save command); `ui.dialog.confirm` finishes the save. A name typed in the panel without the file type's extension gets it, and a name the panel returns with the extension of a format the panel does not list has it replaced (in a native save, `card.png` and `card.ait` are saved as `card.vectorcraft`, and `notes.v2`, whose extension names no format, is saved as `notes.v2.vectorcraft`); when a file of that name exists, which the panel did not ask about, the save (and every export or other write through a save panel) answers `cancelled` and opens a `replaceFile` dialog (fields `message`, `detail`, `path`): `ui.dialog.confirm` writes that file, `ui.dialog.cancel` writes nothing. On the web a `saveOptions` dialog (fields `path`, `format`) first names the file and picks the format. `file.formatOptions {format?}` reads the options a save would use. `file.revert` opens a `confirm` dialog (`{"pending": "confirm"}`); `ui.dialog.confirm` reloads the saved file in the same tab |
 | SVG Options (`engine.execute` of `file.export.svg`, `file.saveAs`, `file.saveCopy`) | `{}` / `{path?, svg?: {…}}` | `file.export.svg` without params, and Save As or Save a Copy to an `.svg`/`.svgz` path without SVG options, open the `svgOptions` dialog (`ui.dialog.set` its fields: `styling`, `outlineText`, `images`, `objectIds`, `decimals`, `minify`, `responsive`, `fewerTspans`, `metadata`, `preserveEditing`, `useArtboards`, `allArtboards`, `range`, `showCode`, `profile` (`svg11`/`tiny12`), `encoding` (`utf8`/`utf16`/`latin1`), `embedFonts`; `ui.dialog.confirm` exports or saves and remembers the choices). With `svg: {…}` (the `document.formats` SVG options) they run directly. `app.save {path?, svg?}` takes the same options; a document saved as SVG saves as SVG again with the same options |
-| `engine.execute` `file.place` | `{paths?}` / `{path \| name+dataBase64, …file.place params}` | with no file, picks files and opens the `place` dialog; with `paths`, opens it for them (fields `files`, `link`, `template`, `replace`; `__replace` says whether Replace applies, `__info` describes each file). `ui.dialog.confirm` places one file centered in the view (or in place of the selected object) and loads the place cursor with several (`file.place.queue`). In the desktop app the dialog reads its files off the UI thread: `__info` is empty until they are read, `ui.dialog.confirm` returns `{background: true, status}`, `ui.inspect` lists the files still being read in `placing`, and the file is placed (or the cursor loaded) once it is read. With a file, it runs `file.place` centered in the view, at once. Files dropped on the window with no document open open. On the web, files dropped on the canvas are placed at the drop point (linked; Shift embeds) and files dropped off it (the tab bar) open, as in Illustrator; desktop drops carry no pointer position, so documents (native, SVG, PDF, `.ai`, EPS, DXF, EMF/WMF) open as tabs of their own and pictures and text are placed in the middle of the view. Swatch libraries and color books (`.vcswatches`, `.gpl`, `.ase`, `.acb`), graphic style libraries (`.vcstyles`), Libraries panel files (`.vclibrary`), presets (`.vcflattener`, `.vcpdfpresets`, `.vcprintpresets`, `.vcperspective`), plug-ins (`.wasm`) and shortcut sets (`.kys`, in Import Shortcut Set) open wherever they are dropped, as File › Open opens them. With the cursor loaded (`ui.inspect` → `tool: "place"`, `toolOptions: {count, current, name, names}`), `ui.pointer` clicks and drags place files (in the desktop app each file is read off the UI thread and placed once it is read, and `ui.inspect` lists it in `placing` until then) and `ui.key` ←/→/↑/↓ and Escape cycle and discard them |
+| `engine.execute` `file.place` | `{paths?}` / `{path \| name+dataBase64, …file.place params}` | with no file, picks files and opens the `place` dialog; with `paths`, opens it for them (fields `files`, `link`, `template`, `replace`; `__replace` says whether Replace applies, `__info` describes each file). `ui.dialog.confirm` places one file centered in the view (or in place of the selected object) and loads the place cursor with several (`file.place.queue`). In the desktop app the dialog reads its files off the UI thread: `__info` is empty until they are read, `ui.dialog.confirm` returns `{background: true, status}`, `ui.inspect` lists the files still being read in `placing`, and the file is placed (or the cursor loaded) once it is read. With a file, it runs `file.place` centered in the view, at once. Files dropped on the window with no document open open. On the web, files dropped on the canvas are placed at the drop point (linked; Shift embeds) and files dropped off it (the tab bar) open, as in Illustrator; desktop drops carry no pointer position, so documents (native, SVG, PDF, `.ai`, EPS, DXF, EMF/WMF) open as tabs of their own and pictures and text are placed in the middle of the view. Swatch libraries and color books (`.vcswatches`, `.gpl`, `.ase`, `.acb`), graphic style libraries (`.vcstyles`), Libraries panel files (`.vclibrary`), presets (`.vcflattener`, `.vcpdfpresets`, `.vcprintpresets`, `.vcperspective`, `.vcexportpresets`), plug-ins (`.wasm`) and shortcut sets (`.kys`, in Import Shortcut Set) open wherever they are dropped, as File › Open opens them. With the cursor loaded (`ui.inspect` → `tool: "place"`, `toolOptions: {count, current, name, names}`), `ui.pointer` clicks and drags place files (in the desktop app each file is read off the UI thread and placed once it is read, and `ui.inspect` lists it in `placing` until then) and `ui.key` ←/→/↑/↓ and Escape cycle and discard them |
 | `app.export` raster options | `{path?, format: png \| jpg \| webp, ppi?, background?, antiAlias?, interlaced?, quality?}` | `ppi` sets the pixel size (72 = one pixel per point) and is stored in the file; `background` is `transparent`, `white`, `black` or `"#rrggbb"`; `antiAlias` is `none`, `art` or `type` (text snapped to pixels); `interlaced` writes an Adam7 PNG. The PNG Options dialog (`pngOptions`; `jpgOptions` / `webpOptions` for the other raster formats) has the same fields: fill them with `ui.dialog.set` and press OK with `ui.dialog.confirm` |
 | `app.export` JPEG options | `{path?, format: jpg, quality?: 0–100, colorModel?: rgb \| cmyk \| gray, method?: baseline \| optimized \| progressive, scans?: 3–5, embedIcc?, imageMap?: none \| client \| server}` | `cmyk` writes ink amounts in the working CMYK space (CMYK colours keep their inks; RGB ones are separated with the colour settings); `embedIcc` (default true) embeds sRGB, the working CMYK profile or a grey profile, generated by the CMS; `imageMap` also writes `<stem>.html` (client-side `<map>`) or `<stem>.map` (NCSA) beside the image for the objects with a URL and an Image Map shape (`attributes.set`), returned in `linked`. The `jpgOptions` dialog has these fields too (`quality` is shown 0–10; a CMYK document defaults to `cmyk`) |
 | `app.export` PNG-8 and GIF | `{path?, format: png8 \| gif, colors?: 2–256, reduction?: perceptual \| selective \| adaptive \| web \| blackWhite \| gray, dither?: none \| diffusion \| pattern \| noise, ditherAmount?: 0–100, transparency?, matte?, interlaced?, …raster options}` | a palette of at most `colors` entries (art with that many colours or fewer keeps them exactly); pixels under half opacity become one transparent entry unless `transparency` is false, partly transparent edges are blended over `matte` (`none` keeps their colour). `png8` writes an indexed `.png` (1–8 bits per pixel); Export for Screens rows take both. The `png8Options` / `gifOptions` dialogs have these fields |
@@ -121,8 +121,8 @@ the two can still win that race.
 | `engine.execute` `document.exportForOffice` | `{}` / `{path?, ppi?: 72 \| 150 \| 300, transparent?, artboard?}` | File → Save for Office Documents…: without params opens `saveForOffice` (fields `ppi`, `transparent`, `artboard`; OK picks the file); with params writes the artboard as a PNG (`path`, else a picked file) → `{path}`, and the status bar shows the first of the export's `warnings` as Save As shows a save's notes. Headless it returns `{dataBase64, width, height, warnings}` |
 | Native file dialogs | any command with no `path` that needs one (`file.open`, `file.saveAs`, `file.place`, exports…) | on macOS (a sheet on the window) and Linux the dialog doesn't hold the window: the call returns `"cancelled"` at once, `ui.inspect` reports `fileDialog`, and when the person at the computer picks a file the command runs again on the document it was asked for. While it is open another dialog is refused: that call returns `"cancelled"` too, and the status bar (`ui.inspect` → `ui.status`) says `"A file dialog is open: choose there first"`; on macOS `app.quit`, `file.close` and `file.closeAll` return that message and the menu bar does nothing (#592, #867). On Windows the dialog is shown in line: the call returns once it closes. Pass `path` to skip the dialog |
 | Background Save / Export | `app.save`, `app.export {path}`, `file.save`, `file.saveAs`, `file.saveCopy`, `file.exportForScreens {folder, …}` | with the `backgroundSave` / `backgroundExport` preferences on (the default) the desktop app writes the file from a snapshot on a worker thread: the call returns `{path, background: true, status}` at once (`file.exportForScreens`: `{background: true, status}`, and the files are written into the folder from a snapshot taken at the call), `ui.inspect` lists the jobs still running in `background` (labels such as `"Saving a.vectorcraft"`), and the document counts as saved (as of the snapshot) once its file is written. Quit and Close wait for them. A job that fails reports the error in the status bar only (`ui.inspect` → `ui.status`), a path outside `--automation-write-root` included. With the preferences off (`prefs.set`), saves return once written. `app.save {path?, wait: true}` and `app.export {path, wait: true}` wait for the jobs already running, write the file before they reply whatever the preferences are set to, and return its result or the error. When the save asks first (the question before replacing a file that opened with losses, or on macOS and Linux the file dialog for an untitled document without `path`), the reply is `{pending}` or `"cancelled"` as before, and the save that runs after the answer uses the preferences. A `wait` that is not `true`, `false` or `null` is refused with "`wait` must be true or false", and nothing is written |
-| `engine.execute` `file.exportForScreens` | `{}` / `{…document.exportForScreens params}` | without params opens `exportForScreens` on the settings the document last exported with (`document.exportSettings`). While the Artboard tool is in use, the artboards it has selected (its `selected` option, the rows the Artboards panel highlights) are checked instead, and `select` is `range`, or `all` when every artboard is selected. Fields: `tab` (`artboards` \| `assets` \| `layers` \| `selection`; OK reads Export Layer or Export Selection on those), `layers` ([layer id…] checked), `selectionMode` (`one` \| `each`), `name` (the file name pattern of `document.exportForScreens`), `counterStart`, `folders` (`none` \| `rows` \| `format` \| `item` \| `itemFormat`), `exportPreset` (the preset applied last), `__savePreset` (the name typed to save one), `select` (`all` \| `range` \| `full`), `range` ("1-3, 5"), `boards` (a bool per artboard; checking them sets `select` and `range`), `includeBleed`, `folder` (desktop; default the Desktop, else home), `openLocation`, `subfolders`, `prefix`, `preset` (`""` \| `mobile` \| `density`), `formats` (rows `{format, scale: "2x" \| "100w" \| "100h" \| "72ppi", suffix, quality?, folder?}`), `settings` (`{png \| png8 \| jpg \| webp \| gif \| svg \| pdf: {…options}}`, the gear's Format Settings; `__settings` names the format shown). `ui.dialog.confirm` exports (a bad range or no folder keeps it open); on the web OK reads "Download" and the files download (several as one `.zip`). With params it runs `document.exportForScreens`: no `folder` returns the files (or with `zip: true` one store-only zip) as `dataBase64`; with `openLocation` the first file is shown in the file manager. When `warnings` is not empty, an export into a folder or a download shows the first in the status bar as Save As shows a save's notes |
-| Export for Screens' Assets tab, Asset Export panel | `ui.dialog.set` `tab: "assets"`, `assets: [asset id…]`; `window.panel {panel: "assetExport"}` | on the Assets tab of `exportForScreens` the `assets` field lists the checked asset ids (all of them when it opens); `ui.dialog.confirm` runs `document.exportForScreens {assets}` (nothing checked keeps it open) and OK reads "Export Asset". The Asset Export panel acts through `assets.*` and shares the format rows with the dialog (`assets.settings.set`) |
+| `engine.execute` `file.exportForScreens` | `{}` / `{…document.exportForScreens params}` | without params opens `exportForScreens` on the settings the document last exported with (`document.exportSettings`). While the Artboard tool is in use, the artboards it has selected (its `selected` option, the rows the Artboards panel highlights) are checked instead, and `select` is `range`, or `all` when every artboard is selected. Fields: `tab` (`artboards` \| `assets` \| `layers` \| `selection`; OK reads Export Layer or Export Selection on those), `layers` ([layer id…] checked), `selectionMode` (`one` \| `each`), `name` (the file name pattern of `document.exportForScreens`), `counterStart`, `folders` (`none` \| `rows` \| `format` \| `item` \| `itemFormat`; setting it turns `subfolders` on for `rows` and off for the others, as the Folders list does), `exportPreset` (the preset applied last), `__savePreset` (the name typed to save one), `select` (`all` \| `range` \| `full`), `range` ("1-3, 5"), `boards` (a bool per artboard; checking them sets `select` and `range`), `includeBleed`, `folder` (desktop; default the Desktop, else home), `openLocation`, `subfolders`, `prefix`, `preset` (`""` \| `mobile` \| `density`), `formats` (rows `{format, scale: "2x" \| "100w" \| "100h" \| "72ppi", suffix, quality?, folder?}`), `settings` (`{png \| png8 \| jpg \| webp \| gif \| svg \| pdf: {…options}}`, the gear's Format Settings; `__settings` names the format shown). `ui.dialog.confirm` exports (a bad range or no folder keeps it open); on the web OK reads "Download" and the files download (several as one `.zip`). With params it runs `document.exportForScreens`: no `folder` returns the files (or with `zip: true` one store-only zip) as `dataBase64`; with `openLocation` the first file is shown in the file manager. When `warnings` is not empty, an export into a folder or a download shows the first in the status bar as Save As shows a save's notes |
+| Export for Screens' Assets tab, Asset Export panel | `ui.dialog.set` `tab: "assets"`, `assets: [asset id…]`; `window.panel {panel: "assetExport"}` | on the Assets tab of `exportForScreens` the `assets` field lists the checked asset ids (all of them when it opens); `ui.dialog.confirm` runs `document.exportForScreens {assets}` (nothing checked keeps it open) and OK reads "Export Asset". The Asset Export panel acts through `assets.*` and shares the format rows with the dialog (`assets.settings.set`). The dialog and the panel both have the Export Preset row. In the dialog, `exportPreset` names the preset applied last (`ui.dialog.set` of it applies the preset it names, as picking it does; a name that is not a preset's is an error and leaves the dialog as it was), `__exportPresets` lists the presets, and `__savePreset` holds the name being typed after Save Preset…. The panel's row also has Import Preset…, which runs `document.exportPresets.import` on a picked file, and Export Preset…, which runs `document.exportPresets.export` for the current preset. In the panel a preset applies through `assets.settings.set {exportPreset}` |
 | `engine.execute` `file.exportSelection` | `{multiple?}` | File › Export Selection…: collects the selected objects as one asset (`assets.add`; one per object with `multiple: true`) and opens `exportForScreens` on its Assets tab with just them checked → `{assets}`. `document.exportSelection` still exports the selection in one go, without the dialog |
 | CSS Properties panel | `window.panel {panel: "cssProperties"}`; `engine.execute` `css.copy` / `css.exportFile` | the panel shows `css.selection` of the selection (after Generate CSS, while nothing is selected, `css.generate`); its menu holds the options the commands take (`units`, `position`, `dimensions`, `unnamed`, `rasterize`). `css.copy {scope?, …options}` copies the CSS to the clipboard → `{css, rules}`; `css.exportFile {path?, scope?, …options}` writes `css.export` to `path` (else a picked file; the web downloads it) with the pictures of rasterized art next to it → `{path, rules, images, warnings}`, and the status bar shows the first of `warnings` as Export As shows an export's notes |
 
diff --git a/docs/mcp.md b/docs/mcp.md
--- a/docs/mcp.md
+++ b/docs/mcp.md
@@ -3658,7 +3658,17 @@ Batch exports (#1935):
 - **Formats:** rows may also be `eps`, `ai` and `vectorcraft` (a document of the item alone). A PDF row with
   `marks` (`{trim, registration, colorBars, pageInfo}`) and a suffix such as `-print` sits next to a plain PDF row.
 - **Presets:** `document.exportPresets.list` (built-in: Logo Package, Print-Ready PDF), `.save {name, settings}`
-  (kept with the preferences) and `.delete {name}`; `exportPreset: "<name>"` uses one under the call's own params.
+  (kept with the preferences; a saved name is replaced) and `.delete {name}`; `exportPreset: "<name>"` uses one under
+  the call's own params. `.export {names?, path?}` writes presets (every saved one, or the named ones, built-in ones too)
+  as a `.vcexportpresets` JSON file (without `path` it returns `data`; more than 1000 presets is an error), and `.import {path? | data? | dataBase64?,
+  replace?}` adds a file's presets (a name in use gets a number unless `replace`; a built-in name always gets one; a
+  name that already ends in a number gets the next free one, so `Web 2` comes in as `Web 3`; a file with settings
+  `.save` refuses, or with more than 1000 presets, adds none). A preset name, saved or imported, keeps its first 255
+  characters. Opening a `.vcexportpresets` file imports it.
+  `assets.settings.set {exportPreset}` applies a preset to the settings the Asset Export panel shares: its rows or
+  `preset`, its format settings, its prefix (none when it has none), its `folders` (Create Sub-folders is on for `rows`
+  only) and anti-aliasing. With Create Sub-folders off, `assets.export` writes every file into the folder itself,
+  mobile and density rows included.
 
 ```json
 {"name":"run_command","arguments":{"command":"document.exportForScreens","params":{"range":"1-3","name":"COMPANY-Logo-<filename>-####-YYYYMMDD","folders":"format","formats":[{"format":"svg"},{"format":"png"},{"format":"pdf"},{"format":"pdf","suffix":"-print","marks":{"trim":true,"registration":true,"colorBars":true,"pageInfo":true}}]}}}
@@ -3900,7 +3910,8 @@ moving the art moves the crop, and deleting it removes it from its asset (an ass
 `assets.export {assets?, folder?, zip?}` writes each asset's art alone, cropped to it and named after it, in every
 format row of the document's export settings, the ones Export for Screens remembers (`document.exportSettings`); any
 `formats`, `preset`, `settings`, `prefix` or `subfolders` given win for that export. `assets.settings.set` changes
-those shared settings (not an undo step). `document.exportForScreens {assets: [id…]}` does the same with its own params
+those shared settings (not an undo step); its `subfolders: true` sets Export for Screens' Folders to a folder per row,
+and `subfolders: false` sets a folder per row back to All in One Folder. `document.exportForScreens {assets: [id…]}` does the same with its own params
 (the dialog's Assets tab). An asset's crop, and the crop of `export` with `selection: true`, is the bounds the
 renderer measures for what the art draws, its effects included.
 
diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md
--- a/docs/target-app-parity.md
+++ b/docs/target-app-parity.md
@@ -526,6 +526,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
   - Actions panel (record/playback, persisted), generic parameter dialogs for every "…" command.
   - Batch… over data sets names each file after the document and the data set (`action.batch`). It writes `/`, `\`, `:`, `<`, `>`, `"`, `|`, `?`, `*` and control characters in the data set's name as `-`, and adds `-2`, `-3`… to a name an earlier data set used.
   - Export for Screens: the dialog's Example line shows the path of the file the export writes first, sub-folder included, named as the export names it, for the first chosen artboard, asset, layer or object (`document.exportForScreens`).
+  - Export presets: the Asset Export panel has the Export Preset row of Export for Screens, with Import Preset… and Export Preset…, which read and write `.vcexportpresets` files (`document.exportPresets.import`, `document.exportPresets.export`). An imported preset whose name is taken gets a number. In the panel, a preset applies to the shared export settings (`assets.settings.set {exportPreset}`), and Save Preset… saves them. With Create Sub-folders off, the panel's Export writes every file into the folder itself, mobile and density rows included.
   - Save for Web: Preview in Browser writes each preview's page and images to a folder of its own inside `VectorCraft Save for Web` in the system's temporary folder. A later preview, from the same app or another one running, does not overwrite an earlier one. Each preview first removes the preview folders there that are an hour old or older and, on Linux, macOS and FreeBSD, belong to the current user; it leaves other files and folders alone. The folders a preview makes there are open to their owner only on those systems. When `VectorCraft Save for Web` is a link or is not a folder, or on those systems belongs to another user or can be written by other users, the preview goes to a new folder of its own in the temporary folder, which a later preview removes once it is an hour old (`file.saveForWeb.browser`).
   - Export for Screens, Save for Web (slice images, and files named after the document's title) and the Libraries panel name files with the characters Windows does not allow written as `-` and with a `-` after a Windows device name such as `CON`, on every platform (`document.exportForScreens`, `document.exportForWeb`, `library.create`).
   - Variables (data merge): text, visibility and image variables bound to objects (an object may carry several), dataset rows applied in one undo step (`variable.*`, `dataset.*`) from a Variables panel and Window › Variables, with dialogs to define, bind and edit, Make Text/Visibility/Image Dynamic, Capture/Update Data Set, CSV import/export (`dataset.import` / `dataset.export`), and a row in italics when an edit leaves the art not matching it; an image dataset value is a file path and apply keeps the object's bounds; bindings let go of deleted art and locked art is left alone; every command, like the rest, is drivable over MCP and the CLI.
