Hi @Paul_St_George I have never done this exact thing before, but I've made an attempt that I think may be useful, although due to some surprising complications, the script is a bit more complicated than I would have expected.
Are you a scripter? If so, you can follow the code to see what's going on, but you will see some strangeness. For example, to export a single path item as SVG, the way I got this to work was by deleting all the other items, temporarily, prior to the export. You will see that I only use item's UUIDs to access DOM references—this is for two reasons: (1) the references were broken by the use of app.undo(), even though I would have thought that the items were still valid, and (2) Illustrator crashed a lot when I tried simpler approaches.
In the end I think I've come up with a robust script—but I'm also sure there are going to be plenty of bugs, so let me know what happens if you end up using it, or parts of it.
You can edit the `setttings` object as you like. For example, try turning off `enforceArtboardBounds`.
- Mark
/**
* @file Export Selected PathItems As SVG.js
*
* Demonstration script:
* - Exports every selected path item and compound path item
* on the active artboard as separate SVG file.
* - Exported files will be named by their item name
* or item typename and uuid.
*
* @author m1b
* @version 2025-05-25
* @discussion https://community.adobe.com/t5/illustrator-discussions/how-to-export-multiple-svg-from-one-layer/m-p/15338630
*/
(function () {
var doc = app.activeDocument;
var settings = {
// whether to export an unpainted rectangle to enforce the SVG viewBox
enforceArtboardBounds: true,
// where to export to
exportFolderPath: '#DOCUMENT_FOLDER#/SVG',
// the SVG export options to use
exportOptions: {
coordinatePrecision: 1,
cssProperties: SVGCSSPropertyLocation.STYLEATTRIBUTES,
fontType: SVGFontType.OUTLINEFONT,
rasterImageLocation: RasterImageLocation.EMBED,
svgId: SVGIdType.SVGIDREGULAR,
svgMinify: false,
svgResponsive: true,
},
// these are the things to export, choose `doc.pageItems` to export every item
targetItems: doc.selection,
// whether to show the results dialog afterwards
showResult: true,
};
if (!doc.fullName.exists)
return alert('Please save this document first.');
if (0 === settings.targetItems.length)
return alert('Please select some path items to export and try again.');
var counter = 0;
// collect the selection, so we can restore it later
var selectedUUIDs = [];
for (var i = 0; i < doc.selection.length; i++)
selectedUUIDs.push(doc.selection[i].uuid);
// uuids of every page item
var allItemUUIDs = [];
// uuids of the items to export
var targetItemUUIDs = [];
// arrays to record whether an item has been processed
var allAlreadyDone = [],
targetAlreadyDone = [];
// in this loop we collect UUIDs for the things do and don't want to export
for (var i = 0, item; i < doc.pageItems.length; i++) {
item = doc.pageItems[i];
if ('CompoundPathItem' === item.parent.constructor.name)
// we don't want a mere path of a compound path item
item = item.parent;
if (allAlreadyDone[item.uuid])
// already got this one
continue;
// store uuid for later
allItemUUIDs.push(item.uuid);
allAlreadyDone[item.uuid] = true;
if (
'PathItem' !== item.constructor.name
&& 'CompoundPathItem' !== item.constructor.name
)
// not a path item or compound path item
continue;
if (-1 === indexOfArray(settings.targetItems, item))
// not a target item to export
continue;
if (targetAlreadyDone[item.uuid])
// already got this one
continue;
// store item for later
targetItemUUIDs.push(item.uuid);
// mark as done
targetAlreadyDone[item.uuid] = true;
}
// prepare the layers
var layerStatus = [];
for (var i = 0; i < doc.layers.length; i++) {
layerStatus[i] = {
locked: doc.layers[i].locked,
visible: doc.layers[i].visible,
};
doc.layers[i].locked = false;
doc.layers[i].visible = true;
}
// add an undo level here so this work doesn't get undone later
app.redraw();
// get the exporter function
var exportArtboard = getSVGExporter(doc, settings.exportFolderPath, settings.exportOptions, settings.enforceArtboardBounds);
// this loop is where we do the exporting
for (var i = 0, item, itemUUID; i < targetItemUUIDs.length; i++) {
itemUUID = targetItemUUIDs[i];
item = getThing(doc.pageItems, 'uuid', itemUUID)
// remove all the items we don't want to export
for (var j = allItemUUIDs.length - 1; j >= 0; j--)
if (allItemUUIDs[j] !== itemUUID)
getThing(doc.pageItems, 'uuid', allItemUUIDs[j]).remove();
// establish the artboard index for this item
var artboardIndex = getArtboardIndexOfItem(item, doc.artboards);
if (undefined == artboardIndex)
continue;
// export with its uuid as filename
exportArtboard(artboardIndex, item.name || item.typename + '-' + itemUUID);
counter++;
// bring everything back for the next go
app.undo();
}
// reinstate the selection
var selection = [];
for (var i = 0; i < selectedUUIDs.length; i++)
selection.push(getThing(doc.pageItems, 'uuid', selectedUUIDs[i]));
doc.selection = selection;
// restore the layers' properties
for (var i = 0; i < doc.layers.length; i++) {
doc.layers[i].locked = layerStatus[i].locked;
doc.layers[i].visible = layerStatus[i].visible;
}
if (settings.showResult)
// show result
alert('Exported ' + counter + ' path items as SVG.');
// reveal the exported files
doc.fullName.parent.execute();
})();
/**
* Returns a pre-configured exporter function.
* @author m1b
* @version 2025-05-25
* @param {Document} doc - an Illustrator Document.
* @param {String} [exportFolderPath] - the export folder path (default: SVG folder in document's folder).
* @param {Object} [properties] - the export options properties to set (default: none, use default values).
* @param {Boolean} [enforceArtboardBounds] - whether to force the svg viewBox to match the artboard bounds (default: false).,
*/
function getSVGExporter(doc, exportFolderPath, properties, enforceArtboardBounds) {
properties = properties || {};
if (!exportFolderPath)
exportFolderPath = '#DOCUMENT_FOLDER#/SVG';
// replace placeholders in export folder path
exportFolderPath = exportFolderPath
.replace('#DOCUMENT_FOLDER#', String(doc.fullName.parent));
var exportFolder = Folder(exportFolderPath);
if (!exportFolder.exists)
exportFolder.create();
// set up the SVG export options
var exportOptions = new ExportOptionsWebOptimizedSVG();
// apply export options properties
for (var key in properties)
if (properties.hasOwnProperty(key))
exportOptions[key] = properties[key];
return exportArtboardAsSVG;
/**
* Exports an artboard as SVG into the same folder as `doc`.
* @author m1b
* @version 2025-05-25
* @param {Number} artboardIndex - the artboard index (zero based).
* @param {String} fileName - the filename for the exported file (do not include file extension).
*/
function exportArtboardAsSVG(artboardIndex, fileName) {
artboardIndex = Math.min(artboardIndex, doc.artboards.length - 1);
// draw this to force the svg viewBox to match the artboard bounds
var tempRectangle = enforceArtboardBounds
? drawRectangle(doc, doc.artboards[artboardIndex].artboardRect, undefined, doc, { filled: false, stroked: false })
: undefined;
// set artboard range
doc.artboards.setActiveArtboardIndex(artboardIndex);
exportOptions.artboardRange = artboardIndex + 1;
var file = File(exportFolder + '/' + fileName + '.svg');
doc.exportFile(file, ExportType.WOSVG, exportOptions);
if (tempRectangle)
tempRectangle.remove();
};
};
/**
* Returns a thing with matching property.
* If `key` is undefined, evaluate the object itself.
* @author m1b
* @version 2024-04-21
* @param {Array|Collection} things - the things to look through.
* @param {String} [key] - the property name (default: undefined).
* @param {*} value - the value to match.
* @returns {*?} - the thing, if found.
*/
function getThing(things, key, value) {
for (var i = 0, obj; i < things.length; i++)
if ((undefined == key ? things[i] : things[i][key]) == value)
return things[i];
};
/**
* Returns index of `obj` within `array`,
* or -1 if `obj` is not found.
* @param {Array<*>} array - the array to search in.
* @param {*} obj - the object to look for.
* @returns {Number}
*/
function indexOfArray(array, obj) {
for (var i = 0; i < array.length; i++)
if (array[i] == obj)
return i;
return -1;
};
/**
* Draws a rectangle in the container.
* Takes Large Scale Documents into account.
* @author m1b
* @version 2024-09-26
* @param {Document} doc - an Illustrator document.
* @param {Array<Number>} rect - the rectangle dimensions [left, top, width, height].
* @param {Number} [unitFactor] - eg. 72 to convert to inches. All values will be scaled by this (default: 1).
* @param {Document|Layer|GroupItem} container - an Illustrator page item container (default: doc).
* @param {Object} [props] - properties to assign to the rectangle (default: none).
* @return {PathItem}
*/
function drawRectangle(doc, rect, unitFactor, container, properties) {
properties = properties || {};
unitFactor = unitFactor || 1;
container = container || doc;
var sf = 1 / doc.scaleFactor * unitFactor;
var rectangle = container.pathItems.rectangle(rect[1] * -sf, rect[0] * sf, (rect[2] - rect[0]) * sf, (rect[3] - rect[1]) * -sf); // T,L,W,H
// defaults
rectangle.filled = true;
rectangle.stroked = false;
// apply properties
for (var key in properties)
if (properties.hasOwnProperty(key))
rectangle[key] = properties[key];
return rectangle;
};
/**
* Returns the index of the artboard that has most overlap the item's bounds.
* @author m1b
* @version 2025-05-25
* @param {PageItem} item - a page item.
* @param {Array<Artboard>} artboards - artboards to search, if undefined, search all.
* @param {Boolean} preferIndex - if true, returns array of artboard indices {Array[Number]}
* @returns {Number?} - the artboard index;
*/
function getArtboardIndexOfItem(item, artboards) {
var artboardIndex;
for (var i = 0, overlap, maxOverlap = 0; i < artboards.length; i++) {
overlap = getOverlapArea(item.geometricBounds, artboards[i].artboardRect);
if (!overlap)
continue;
if (overlap > maxOverlap) {
maxOverlap = overlap;
artboardIndex = i;
}
}
if (undefined !== artboardIndex)
return artboardIndex;
};
/**
* Returns the overlapping area between
* two rectangular bounds.
* @author m1b
* @version 2025-05-25
* @param {bounds} a - First bounds [L, T, R, B].
* @param {bounds} b - Second bounds [L, T, R, B].
* @returns {Number|null} - area of overlap, or null if no overlap.
*/
function getOverlapArea(a, b) {
var left = Math.max(a[0], b[0]),
top = Math.min(a[1], b[1]),
right = Math.min(a[2], b[2]),
bottom = Math.max(a[3], b[3]);
var width = right - left,
height = top - bottom;
if (
width > 0
&& height > 0
)
return width * height;
// No overlap
return null;
};
Edit 2025-05-25: added check for locked or hidden layers.
Edit 2025-05-25: fixed bug where selection was not restored when `settings.targetItems` is set to `doc.pageItems`, ie. to export every path item in document.