Skip to main content
Participant
December 27, 2025
Question

ExtendScript to loop through directory and save fm files as pdf

  • December 27, 2025
  • 3 replies
  • 228 views

I have 600+ fm files that I want to save as a pdf. They are all in the same directory. This is the script I started with (though I currently have Frankencode doing other stuff that was added between me and things suggested by Claudet/Gemini/etc).

#target framemaker

(function () {

    // --- CONFIG ---
    var PDF_SETTINGS_NAME = ""; 
    // Leave empty "" to use the document's default PDF settings
    // Or put the name of a PDF setup, e.g. "High Quality Print"

    // ----------------

    function log(msg) {
        $.writeln(msg);
        var oFile = new File ("C:/Catalog/output/run.log" );
        oFile.open("a");
        oFile.writeln(msg);
        oFile.close();
    }

    function saveAsPdf(doc, savePath) {
        // Get default save parameters
        var params = GetSaveDefaultParams();
        var i;

        // Set the file type to PDF
        i = GetPropIndex(params, Constants.FS_FileType);
        params[i].propVal.ival = Constants.FV_SaveFmtPdf;

        // Optional: Configure other PDF settings if needed
        // Example: Set to not use Distiller (rely on modern internal PDF generation)
        i = GetPropIndex(params, Constants.FS_PDFUseDistiller);
        if (i > -1) {
            params[i].propVal.ival = 0; // 0 for false, 1 for true
        }

        // Call the Save function
        // FA_errno is a global variable that will contain the error code after the call
        FA_errno = 0;
        var returnParams = new PropVals();
        
        // Check if the input is a book or a document to use the correct function
        if (doc.Type === Constants.FA_Book) {
            doc.Save(savePath, params, returnParams);
        } else {
            // For a single document, use F_ApiSave (often accessed via the document object in modern ExtendScript)
            // Note: Direct doc.Save(path, params, returnParams) works for documents too in many cases.
            // The Book object has a specific method signature in some versions.
            doc.Save(savePath, params, returnParams);
        }
        
        if (FA_errno !== 0) {
            log("Failed to save PDF. Error code: " + FA_errno);
        } else {
            log("PDF saved successfully to: " + savePath);
        }
    }

    var runFile = new File("C:/Catalog/output/run.go");
    if (!runFile.exists) {
        $.writeln("run.go file does not exist. Exiting.");
        return;
    }

    var folder = new Folder("C:/Catalog/output");

    var files = folder.getFiles(function (f) {
        return f instanceof File && /\.fm$/i.test(f.name);
    });

    if (files.length === 0) {
        log("No .fm files found.");
        app.Close(Constants.FF_CLOSE_MODIFIED);
    }

    log("Found " + files.length + " file(s).");

    for (var i = 0; i < files.length; i++) {
        var file = files[i];
        log("Processing: " + file.name);

        var doc = null;

        try {
            doc = SimpleOpen(file.fsName, false);
            

            var pdfPath = file.fsName.replace(/\.fm$/i, ".pdf");

            if (saveAsPdf(doc, pdfPath)) {
                log("  PDF created: " + pdfPath);
            }

        } catch (e) {
            log("  ERROR opening file: " + e);
        } finally {
            if (doc) {
                try {
                    // Close without saving changes
                    doc.Close(Constants.FF_CLOSE_MODIFIED);
                    
                } catch (closeErr) {
                    log("  ERROR closing file: " + closeErr);
                }
            }
        }
    }

    log("Batch PDF export complete.");

    app.Close(Constants.FF_CLOSE_MODIFIED);

})();

 

I get timeout errors at random places. It will handle anywhere between 2-7 files, then give me a timeout error. I have the script in the startup folder so that I can place a file in the output folder, open FM, and then let it go to work. I'd also like to move the fm files to an archive folder once it's done.

 

Can someone give me a push in the right direction? I also don't really know if the SaveAsPdf function is what it should be, I'd be surprised if it was.

3 replies

Community Expert
January 7, 2026

Hi,

When you want to automate publishing, it could be that this is not allowed with a regular FrameMaker licence. You would need FrameMaker Publishing Server.

https://www.adobe.com/products/framemaker/publishing-server.html

Best regards, Winfried

frameexpert
Community Expert
Community Expert
December 29, 2025

Here is a script that works on a book, but you could modify it to work on a folder. Or, you could put all of the folder's FM files into a temporary book. I originally wrote it for a client, and thus the AC_SCP namespace.

#target framemaker

var AC_SCP = AC_SCP || {}; // Save each component to PDF.
AC_SCP.version = "Version 0.1b January 11, 2022";
AC_SCP.file = $.fileName;

AC_SCP.main = function (book) {
	
	var doc;
	
	if ((book) && (book.ObjectValid () === 1)) {
		AC_SCP.processBook (book);
	}
	else {
		book = app.ActiveBook;
		if (book.ObjectValid () === 1) {
			AC_SCP.processBook (book);
		}
	}
};

AC_SCP.saveDocAsPdf = function (doc, name) {	
    
    var params, returnParams, i;
    
    if (!name) {
        name = doc.Name.replace (/[^\.]+$/, "pdf");
    }
        
    params = GetSaveDefaultParams();
    returnParams = new PropVals();
    
    i = GetPropIndex (params, Constants.FS_FileType);
    params[i].propVal.ival = Constants.FV_SaveFmtPdf;
	i = GetPropIndex (params, Constants.FS_PDFUseDistiller);
    params[i].propVal.ival = 0;

    FA_errno = 0;
    doc.Save (name, params, returnParams);
	if (FA_errno !== 0) {
		PrintSaveStatus (returnParams);
	}
    return FA_errno;
};

AC_SCP.processBook = function (book) {
	
    var bookComp, file;
    
    // Loop through all of the components in the book.
    bookComp = book.FirstComponentInBook;
    while (bookComp.ObjectValid ()) {
        if (bookComp.ComponentType === 512) {
            file = new File (bookComp.Name);
			// Get the document returned in a JavaScript object.
			doc = CP.getDocument (bookComp.Name, undefined, true);
			if ((doc) && (doc.ObjectValid () === 1)) {
				book.StatusLine = "Processing " + file.name;
				AC_SCP.saveDocAsPdf (doc);
				// If the document was opened by the script, close it.
				if (doc.openedByScript === true) {
					doc.Close (true);
				}
			}
        }
        bookComp = bookComp.NextBookComponentInDFSOrder;
    }
    // Reset the book status line.
    book.StatusLine = "";
};

 

www.frameexpert.com
frameexpert
Community Expert
Community Expert
December 29, 2025

I just realized that the script refers to a function in another script (CP.getDocument). You should be able to use the code from your original script to open each document.

www.frameexpert.com
Participant
December 29, 2025

After looking at more posts on here, I was able to cobble something together that works. 

Jeff_Coatsworth
Community Expert
Community Expert
December 29, 2025

Would you care to post your solution here to help other beginner scripters?