Folders become collections. PDFs go where they belong.
A small frustration with organizing PDFs turned into an open-source Zotero plugin.
I use Zotero a lot for managing PDFs, references, and collections (especially when preparing research papers for analysis and literature review in NotebookLM).
At some point, I had a simple problem: I already had folders on my computer arranged the way I wanted.
Something like:
Papers/
├── Deep Learning/
│ ├── paper-1.pdf
│ ├── paper-2.pdf
│ └── Transformers/
│ ├── paper-3.pdf
│ └── paper-4.pdf
├── Time Series/
│ └── paper-5.pdf
└── Other/
└── paper-6.pdf
What I wanted was equally simple.
Drag that folder into Zotero and get:
Papers
├── Deep Learning
│ └── Transformers
├── Time Series
└── Other
with the PDFs imported into the correct collections.
I did not want to manually create every collection, open every folder, select the PDFs, import them, go back, create another collection, and repeat the process.
It felt like something the computer should be doing for me.
So I started building it.
The problem
Zotero is excellent at managing references once they are inside the library.
But my files already had structure before entering Zotero.
I had folders.
I had subfolders.
I had PDFs inside those folders.
The folder names already represented how I wanted things grouped.
I did not want to rebuild that structure manually inside Zotero.
What I wanted was basically:
Folders become collections. Subfolders become subcollections. PDFs get imported into the matching collection.
Nothing complicated.
The first version was just an experiment to see whether Zotero would let me do it.
It did.
Then the interesting problems started.
“It works” is very different from “it works properly”
The first prototype installed successfully.
The menu appeared.
That felt like progress.
Then I clicked it.
Nothing happened.
The plugin was technically loaded, but parts of the implementation were using APIs that had changed in newer Zotero versions.
After fixing that, the folder picker started working.
Then Zotero told me to select a collection even though I had already selected one.
Another API change.
Fixed that.
Then I tried importing a folder.
The collection appeared, but some PDFs did not.
One inaccessible filesystem entry could stop an entire folder scan.
Fixed that.
Then I dragged multiple folders at once.
Five folders became ten collections.
Definitely not the feature I was aiming for.
Another fix.
Then everything worked, I closed Zotero, opened it again…
…and parts of the plugin disappeared.
That turned into another lesson about Zotero’s plugin lifecycle and when UI elements should be registered.
This little plugin quickly became a good reminder that software is rarely finished when the happy path works.
The edge cases are the product.
What Folder Drop Importer does
The goal is still intentionally simple.
Built for modern Zotero (versions 8 through 10), it gives you three ways to import:
- Right-click any collection or subcollection and choose:
Import Folder Here… - Select an active collection and choose File → Import Folder…
- Drag and drop folders directly from your file manager onto Zotero.
The plugin then reads the selected folder and recreates its structure inside Zotero.
For example:
Local folder
Reading/
├── Machine Learning/
│ ├── A.pdf
│ └── B.pdf
└── Forecasting/
├── C.pdf
└── Advanced/
└── D.pdf
becomes:
Zotero
Reading
├── Machine Learning
└── Forecasting
└── Advanced
with the PDFs placed in their respective collections.
The idea is that you organize things once.
The plugin should respect that structure instead of asking you to rebuild it.
I do not want it to become a huge plugin
One thing I decided quite early was that this project should stay focused.
There are endless features that could be added.
Automatic folder watching.
Cloud synchronization.
Deleting files when collections are removed.
Moving original files.
Automatic metadata processing.
Dozens of settings.
Complex synchronization rules.
At some point the plugin would stop being a folder importer and become another file management system.
That is not what I want.
The core idea is much smaller:
Take the folders I explicitly give you and import them safely into Zotero.
No background process constantly watching my computer.
No surprise file deletion.
No telemetry.
No external service required by the plugin.
No moving my original files behind my back.
You trigger an import.
It imports.
It stops.
I like software that can explain what it does in a sentence.
Safe defaults instead of endless settings
Early versions of the plugin had a Settings menu.
It asked questions such as:
Which file types?
Preserve root folder?
Skip hidden files?
How should duplicates be handled?
Technically, flexibility is good.
But using it felt annoying.
Why should a simple folder import require answering a questionnaire?
So I started moving toward sensible defaults instead.
For the normal case:
- PDFs are imported.
- Folder hierarchy is preserved.
- Hidden files are ignored.
- Inaccessible files are skipped instead of crashing everything.
- Existing duplicates are skipped.
- Original files are not deleted or moved.
- Nothing runs in the background.
The user can just import a folder and continue working.
If advanced configuration becomes genuinely useful later, it can be added based on actual use cases rather than imagined ones.
Duplicate handling is more complicated than it sounds
Another interesting problem was duplicates.
Imagine this:
paper.pdf
paper-copy.pdf
paper (1).pdf
Are those three different papers?
Maybe.
Or maybe someone copied the same PDF three times.
Checking only the filename is not enough.
Checking filename plus file size is better, but it still is not perfect.
A stronger approach is to hash the actual file contents.
If two files produce the same content hash, they are almost certainly the same file regardless of what someone renamed them.
That is one of the areas I want to improve as the plugin develops.
The same issue exists with collections.
If a collection called:
Transformers
already exists, importing another folder named Transformers should not automatically give you:
Transformers
Transformers
A useful importer needs to understand what already exists and reuse it where appropriate.
These small details determine whether a tool saves time or creates another cleanup job.
Drag and drop has its own surprises
Drag and drop sounds simple until you implement it.
The operating system, browser platform, Zotero UI, and plugin all have to agree on what exactly was dropped.
When multiple folders are selected, the drag event may expose the same filesystem object in more than one representation.
If you process everything blindly, this can happen:
Selected:
A
B
C
D
E
and somehow the importer sees:
A
A
B
B
C
C
D
D
E
E
That was one of the bugs I ran into.
The solution is not simply “remove duplicate names.”
Paths need to be normalized, duplicate roots need to be removed, nested paths need to be handled correctly, and one drag operation should correspond to exactly one import session.
Again, something that looks like a tiny feature from the outside becomes surprisingly interesting once you start dealing with real user input.
Importing needs a Stop button
Another thing that became obvious while testing larger folders:
What happens if you start importing hundreds of PDFs and change your mind?
You should not have to kill Zotero.
An importer needs to show progress and let the user stop.
But even “Stop” needs a definition.
If a PDF is currently being copied into Zotero, forcefully killing that operation halfway through could leave an incomplete attachment.
So the safer behaviour is:
Finish the file currently being processed, then stop before starting the next one.
Anything successfully imported before that point stays in the library.
There should also be a difference between:
Stop
and
Close
Closing the progress window should just hide it.
Stopping should actually stop the import.
Small UI decisions like these matter a lot more once software moves beyond being a script for one person.
Why I decided to open source it
Originally, I built this because I wanted it.
That was enough reason to start.
But once it began working, I looked around and found that folder importing is not a problem unique to me.
People have been trying to solve variations of this workflow in the Zotero community for years.
There are existing plugins and related projects, which is a good thing. It proves there is a real need and also gives me examples of problems users run into: duplicate imports, compatibility changes, folder structure decisions, bibliographic metadata, cloud folders, and more.
I do not think open source needs another project claiming to replace everything that already exists.
Instead, I want this project to have a clear purpose:
A lightweight, predictable folder hierarchy importer for Zotero.
Opening the code also means other people can inspect exactly what the plugin does to their files.
That matters for a tool with filesystem access.
You should not have to trust a vague promise that a plugin is not uploading your documents somewhere.
You can look at the code.
No telemetry should mean no telemetry in the source.
No background monitoring should mean there is not a hidden watcher running somewhere.
No deletion should mean the plugin does not contain code that quietly removes your original files.
Transparency is one of the biggest reasons this kind of utility makes sense as open source.
Open source also makes the weird cases discoverable
My Windows setup is only one environment.
Someone else will use macOS.
Someone will run Linux.
Someone will import from OneDrive.
Someone will use Dropbox.
Someone will point it at a network drive.
Someone will have filenames containing characters I never tested.
Someone will have 20 PDFs.
Someone else will have 20,000.
Someone will find a broken symbolic link buried six folders deep.
And eventually someone will do something I never imagined.
That is where an open-source project becomes much more useful than a personal script.
Instead of pretending I have anticipated every possible environment, issues and pull requests can document the real ones.
What I want the project to feel like
I do not want someone to need a tutorial just to use it.
Ideally:
- Install the plugin.
- Right-click a collection.
- Click Import Folder Here…
- Choose a folder.
- Done.
Or eventually:
Drag a folder directly onto the collection where you want it.
That is the entire mental model.
The difficult code should stay behind the interface.
What I am working on next
There are still several things I want to improve before calling it a stable 1.0.
Better duplicate detection is high on the list.
So is more reliable multi-folder drag and drop.
Import progress and cancellation need more testing.
Direct dropping onto a specific collection row would make the workflow much more natural.
I also want better import summaries, so after a large operation you can clearly see something like:
Found: 426
Imported: 391
Duplicates: 27
Skipped: 7
Failed: 1
And if something fails, the plugin should tell you which file failed and why instead of presenting a mysterious error message.
Cross-platform testing is another important part before I consider it mature.
A small project, but a useful one
This is not the biggest project I have built.
It is not supposed to be.
It started with a very ordinary thought:
Why am I manually recreating folders that already exist?
Sometimes those are the best tools to build.
Not because they introduce a completely new technology, but because they remove a repetitive step that should not have been there in the first place.
If you use Zotero and have folders full of PDFs, I would love for you to try it, break it, report what happened, or contribute a fix.
The project is open source on GitHub:
Try the Alpha (1.1.0-alpha.3)
If you want to test it out:
- Download the latest
.xpibundle from the Releases page. - In Zotero (v8 – v10), open Tools → Plugins (or Add-ons), click the ⚙️ gear icon in the top-right corner, and choose Install Add-on From File….
- Select the downloaded
.xpifile and restart Zotero if prompted.
Note: Because it is currently in alpha, test with copies of your files before running large imports on critical libraries.
If it saves you a few minutes of repetitive importing, then it has already done what I built it to do.
