Last March I checked my Adobe Stock dashboard after a month of daily uploads and saw the number that broke me: 2,400 views, 3 downloads. That is a 0.12% conversion rate. I had spent weeks generating art, and I was ready to blame the art. I was wrong.
The problem was not the images. It was that I had no idea what the views were telling me. I was looking at views as a success metric when they were actually a diagnosis. Here is what I learned, and the one number I now watch instead.
Views are a search problem, sales are a match problem
Stock platforms show your work to people searching for something specific. A view means the thumbnail appeared or was clicked. A sale means the file matched what that person actually needed. Those are two different systems, and most of us only optimize the first one.
When I pulled my three download records apart, the pattern was obvious. The assets that sold had titles that described a use case, not an object. "Watercolor eucalyptus border for wedding invitations" outperformed "eucalyptus illustration" by a wide margin on the same style of art. Same portfolio, same month. The difference was whether a buyer could tell, in one second, that this file solved their problem.
So the one metric is this: sales per 100 views, per asset. Not portfolio-wide, per asset. A portfolio average hides your best and worst work behind each other. Once I sorted by that number, I stopped guessing which of my 39 products to make more of.
Metadata is where most of the match happens
All three major channels I upload to reject a wrong CSV header instantly. Adobe wants 5 columns, Vecteezy wants 4, Dreamstime wants 15. Get one wrong and your batch bounces before a human ever sees it, and you lose a day. I know because it happened to me twice in the same week.
But passing validation is the floor. The columns are where you put the matching information. Title and keywords are the only text a buyer sees before downloading, so they carry almost the entire conversion burden. My early uploads had filename-derived titles, because that was fast. The conversion data said fast was expensive: those assets sat near zero while the ones I had hand-titled earned almost everything.
The fix that worked: write the title as the sentence a buyer would type, then pick keywords that describe use, style, and subject, in that order. I batch this now with a script that builds all three platform CSVs from one folder of files, with the exact header each platform demands and nothing else. Hand-write the titles in a JSON file, let the script handle the columns.
Double-posting kills accounts, and killed my data
Here is a mistake with two costs. I re-ran an upload batch once, thinking the first run had failed. It had not. The result was duplicate assets, which platforms treat as spam, and a mess in my own ledger: I could no longer tell which asset a sale belonged to. My per-asset metric was garbage for two months after.
The platforms flag duplicate submissions. That is not a hunch, it is in their contributor guidelines. So now a ledger script records every posted filename, and a re-run checks the ledger first. pending shows what is not done, done marks an item, status prints the count. Boring, and it saved my account.
Your pipeline should run on nothing
I run all of this on a 1 vCPU, 2 GB RAM server with no GPU. That constraint forced habits that turned out to be good practice anywhere:
- One process at a time. Never fan out image jobs in parallel on a single core.
- Call
gc.collect()after each asset to free memory between items. - Measure peak RSS with
resource.getrusage(RUSAGE_SELF).ru_maxrss, not average. The peak is what crashes you. - Render heavy steps one file per subprocess so memory returns to the OS.
- Inspect previews at 60 dpi, not 300. You do not need full resolution to check composition.
The whole pipeline, from prompt to finished product, produced 39 products and 856 MB of output on that box. The tooling is roughly 19,000 lines of Python in the parent project, backed by about 300 pages of research into what each platform actually enforces.
Packaging is the last mile, and it is not optional
A bare file does not sell. The same content, zipped, with a real 1280x720 cover and a 600x600 thumbnail, converts noticeably better in my own listings. It reads as a product instead of a leftover file. My packaging script takes a folder, a title, a subtitle, and a price, and writes the zip plus both images in one command. It uses Pillow for the covers, and a separate script turns a Markdown document into a styled PDF with reportlab for the products that are documents rather than images. Those are the only two third-party packages in the whole kit. The metadata builder and the upload tracker use the Python standard library alone.
What I would tell March-me
Stop watching views. Sort your assets by sales per 100 views, fix the titles and keywords on the ones with traffic but no sales, and never re-run an upload batch without a ledger. That sequence took my worst month to a portfolio where I know exactly what to make next, and why.
Everything above is built from real work: the metadata builders for Adobe, Vecteezy and Dreamstime, the upload tracker, the PDF renderer, and the packaging script all live in one small kit, no framework, no cloud account, runs on a 2 GB box. If you want the same tools instead of rebuilding them, it is here:
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)