I have a person looking to count pages of perhaps 2-3000 PDFs. She needs a total count of all the pdfs combined (it will probably amount to something like 20,000 spread across the 3,000 PDFs) - no other details are necessary.

I considered adobe combine and then get a page count, but just testing on ~500 files, it is pretty slow going!

Worth mentioning you used to be able to open multiple pdfs simultaneously in preview and get a page count! Used to work pretty well, even across 2-300 files!

Thank you for your thoughts.

  • 2
    Does she want to know the page count for each PDF or only a total page count? Are the PDFs in one folder or many folders in a tree? Apr 18, 2018 at 21:59
  • 1
    total page count. not for each. The files are across a few folders but I can have her move them no problem.
    – Gryph
    Apr 18, 2018 at 22:05
  • it would be good to know if either of these solutions is faster than your merge method. The merge method is pretty simple and straightforward. Just a thought.
    – Natsfan
    Apr 27, 2018 at 18:37
  • @jmh Josh's script was much, much faster than merge in my use case (hundreds of files with hundreds of pages each). Probably a Merging would take hours, days even, and you end up with a duplicate of your files (a lot of space on the drive, in this case). Josh's script took minutes (perhaps 15 in processing).
    – Gryph
    Apr 28, 2018 at 13:18

2 Answers 2



Create an Apple Script and export it as an Application and then send the Application to her.

AppleScript Code:

set totalPages to 0
set numDocs to 1
set myFiles to choose file with prompt "Select all PDF's" with multiple selections allowed
set nummyFiles to length of myFiles
set progress total steps to nummyFiles
set progress completed steps to 0
set progress description to "Processing PDF's..."
set progress additional description to "Preparing to process."
repeat with i in myFiles
    set progress additional description to "Processing PDF " & numDocs & " of " & nummyFiles
    set progress completed steps to numDocs
    set myfile to POSIX path of i
    set pageCount to (do shell script "/usr/bin/mdls " & quoted form of myfile & " | /usr/bin/awk '/kMDItemNumberOfPages/{print $3}'") as integer
    set totalPages to (totalPages + pageCount)
    set numDocs to (numDocs + 1)
end repeat
display dialog "There are " & totalPages & " pages in this PDF"
  1. Open /Applications/Utilities/Script Editor.app
  2. File>New
  3. Copy and Paste above code
  4. File>Export
  5. File Format: Application
  6. Send Exported Application
  • 1
    I was wondering if myFiles might break with several hundred files selected. 3,0000 is a lot of files. Why not get the user to select a folder and then run the loop in the shell script? Apr 18, 2018 at 23:09
  • 1
    That is a possibility, although I have never seen that happen. Can you please explain what you mean? I don’t understand what your question means.
    – JBis
    Apr 18, 2018 at 23:11
  • 1
    I am going to test with 3,000 files now.
    – JBis
    Apr 18, 2018 at 23:29
  • 1
    @TonyWilliams Just tested with 3,000 files it worked great! Each had 3 pages totaling 9,000. I even added a progress bar.
    – JBis
    Apr 18, 2018 at 23:41
  • 1
    Glad I could help! Unfortunately, this is a limitation of Apple Script. I don't know/think that theres a way to have a loading dialog box while it is entering all the files into the list. I'll see what I can do though....
    – JBis
    Apr 19, 2018 at 4:25

I tried Josh's approach using mdls and found a surprising number of (nulls) for kMDItemNumberOfPages.

So I switched tacks and used AppleScriptObjC to directly count the pages in the found PDF files.

The script will run directly from the Script Editor.app or from a script applet.

It will produce a report in TextEdit that looks like this:

PDF files found  :  460
Total Pages      :  27052
Total Errors     :  0

This run took right at 10 seconds on my 17" Mid-2010 i7 MacBook Pro.

The following line must be altered in the script to properly reflect the target directory on the user's system:

property searchPath : "~/Downloads"

(Although I'd be happy to make it work on the front window in the Finder upon request.)

The script is presently set to be recursive in the target directory.

# Auth: Christopher Stone { With many thanks to Shane Stanley and Nigel Garvey }
# dCre: 2018/04/27 01:30
# dMod: 2018/04/27 02:50
# Appl: AppleScriptObjC, TextEdit
# Task: Find all PDF files in a directory tree – count and report all pages.
# Libs: None
# Osax: None
# Tags: @Applescript, @Script, @ASObjC, @TextEdit, @Find, @PDF, @Files, @Directory, @Tree, @Recursive, @Count, @Report, @Pages, @Progress_Bar, @Bar
# Vers: 1.00
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "Quartz" -- for PDF features
use scripting additions
property searchPath : "~/Downloads"
property searchRecursively : true

set pageCountList to {}
set searchPath to ((current application's NSString's stringWithString:searchPath)'s stringByExpandingTildeInPath) as text
set foundItemList to my filteredContents:searchPath withUTI:{"com.adobe.pdf"} |returning|:"path" recursive:searchRecursively

set totalStepNum to length of foundItemList
set progress total steps to totalStepNum
set progress completed steps to 0
set progress description to "Processing PDF's..."
set progress additional description to "Preparing to process."
set numberOfProcessedDocuments to 0

repeat with pdfFilePath in foundItemList
    set numberOfProcessedDocuments to (numberOfProcessedDocuments + 1)
    set progress additional description to "Processing PDF " & numberOfProcessedDocuments & " of " & totalStepNum
    set progress completed steps to numberOfProcessedDocuments
        set anNSURL to (current application's |NSURL|'s fileURLWithPath:(contents of pdfFilePath))
        set theDoc to (current application's PDFDocument's alloc()'s initWithURL:anNSURL)
        set end of pageCountList to theDoc's pageCount() as integer
    on error
        set end of pageCountList to "Error --> " & name of (info for (contents of pdfFilePath))
    end try
end repeat

set errorList to text of pageCountList
set filesFoundCount to length of foundItemList
set pageCountList to integers of pageCountList
set pageCount to its sumList(pageCountList)

set pdfPageReport to "
PDF files found  :  " & filesFoundCount & "
Total Pages      :  " & pageCount & "
Total Errors     :  " & length of errorList & "

tell application "TextEdit"
    launch -- prevent the Open dialog from opening.
    set newDoc to make new document with properties {text:pdfPageReport}
    tell newDoc
        set font to "Menlo"
        set size to "14"
    end tell
end tell

on filteredContents:folderPath withUTI:wUTI |returning|:returnType recursive:wRecursive
    set theFolderURL to current application's |NSURL|'s fileURLWithPath:folderPath
    set typeIdentifierKey to current application's NSURLTypeIdentifierKey
    set keysToRequest to current application's NSArray's arrayWithObject:(typeIdentifierKey)
    set theFileManager to current application's NSFileManager's defaultManager()

    # Get all items in folder descending into subfolders if asked.
    if wRecursive = true then
        set allURLs to (theFileManager's enumeratorAtURL:theFolderURL includingPropertiesForKeys:keysToRequest options:6 errorHandler:(missing value))'s allObjects()
        set allURLs to theFileManager's contentsOfDirectoryAtURL:theFolderURL includingPropertiesForKeys:keysToRequest options:4 |error|:(missing value)
    end if

    # Build an or predicate to test each URL's UTI against all the specified ones.
    set predArray to current application's NSMutableArray's new()
    repeat with aKind in wUTI
        (predArray's addObject:(current application's NSPredicate's predicateWithFormat_("self UTI-CONFORMS-TO %@", aKind)))
    end repeat
    set thePredicate to current application's NSCompoundPredicate's orPredicateWithSubpredicates:predArray

    # Build a list of those URLs whose UTIs satisfy the predicate …
    script o
        property theURLs : {}
    end script
    # … keeping AS texts listing the UTIs tried so that they don't need to be tested again.

    set conformingUTIs to ""
    set unconformingUTIs to ""

    repeat with oneURL in allURLs
        set thisUTI to end of (oneURL's getResourceValue:(reference) forKey:typeIdentifierKey |error|:(missing value))
        # It's only necessary to test this UTI for conformity if it hasn't come up before.
        set thisUTIAsText to linefeed & thisUTI & linefeed
        if (unconformingUTIs contains thisUTIAsText) then
            # Do nothing.
        else if (conformingUTIs contains thisUTIAsText) then
            # Add this URL to the output list.
            set end of o's theURLs to oneURL
        else if ((thePredicate's evaluateWithObject:thisUTI) as boolean) then -- This works even if thisUTI is missing value.
            # Add this URL to the output list and append the UTI to the conforming-UTI text.
            set end of o's theURLs to oneURL
            set conformingUTIs to conformingUTIs & thisUTIAsText
            # Append this UTI to the unconforming-UTI text.
            set unconformingUTIs to unconformingUTIs & thisUTIAsText
        end if
    end repeat

    # Get an array version of the URL list and use this to derive the final output.
    set theURLs to current application's NSArray's arrayWithArray:(o's theURLs)
    if returnType = "name" then return (theURLs's valueForKey:"lastPathComponent") as list
    if returnType = "path" then return (theURLs's valueForKey:"path") as list
    if returnType = "url" then return theURLs
    return theURLs as list

end filteredContents:withUTI:|returning|:recursive:
on sumList(theList)
    set theNSArray to current application's NSArray's arrayWithArray:theList
    set theSum to (theNSArray's valueForKeyPath:"@sum.self") as integer
    return theSum
end sumList

As this critter is only lightly tested I make no guarantees, but I'm pleased with it so far.


  • Interesting - I didn't get any nulls. I tested various merge combinations vs a hand count vs Josh's approach and they all came out the same. This method is faster though, I can confirm this. It would be nice if one could give two "correct" answers.
    – Gryph
    Apr 28, 2018 at 13:27
  • Just a followup - this script is far faster than the above script. It's also quite nice to have the number of pdfs counted included and possible errors (have not had any over multiple tests of hundreds of files).
    – Gryph
    May 5, 2018 at 21:27
  • I would like to work on combining our scripts. Using your method of counting, but mine of selecting the directory, also adding ability to select specific documents. Additionally, creating a different method to show results.
    – JBis
    May 10, 2018 at 14:54
  • Best of both worlds, yes I agree - they each have different use cases as you note above. I can use either without issue, but yours is easier for non-literate. Christophers is much, much faster however. Really interesting to see the differences. I've quite liked this question.
    – Gryph
    May 10, 2018 at 15:30

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .