Selling the theme you built: the author's guide
This is the other side of the counter.
This is the other side of the counter. Build a theme ends the moment your theme runs; this one starts there and finishes when somebody you have never met has it installed, has paid for it and knows where to write when it breaks.
If you are the one running a shop and wondering how to take other people's work in, you want Selling other people's themes and extensions. That guide is written from the panel. This one is written from your machine.
Almost everything here applies to extensions as well, with one difference worth
saying up front: there is no pack for plugins. pcc-plugin has nuevo,
comprobar and dev and nothing else, so you run pcc-plugin comprobar and
zip the folder yourself — a plugin is copied into plugins/, so the folder is
what has to end up inside the archive.
1. From a theme that works to a package somebody can install
Three commands, and they answer three different questions. Running one is not a substitute for running the others.
npx pcc-theme validate
npx pcc-theme audit
npx pcc-theme packvalidate asks whether it is a theme. It checks theme.json, tokens.json
and settings.schema.json against the contract's schemas, then the coherence
rules on top — that a template does not compose a section you never declared,
that a setting has somewhere to live, that the manifest says which stack it is.
It never runs your code, which is why it can pass on a theme that does not
compile. Ship with no warnings at all, not with few: a package that arrives
with warnings teaches the person installing it to scroll past warnings, and from
then on the validator is worth nothing to anybody.
audit asks what would happen if somebody installed it. It reads the code
and the markup and answers two questions: what would run, and who it talks to.
These stop the package dead:
child_process,exec,spawnand friends. A theme renders pages; it does not launch programs.evalornew Function, and long base64 strings decoded at runtime. Those are how a payload travels.next/font/google. The font is downloaded while the theme builds, and the panel builds themes with no network, so it dies on your buyer's server and never on your laptop, where the font is already cached. Put the.woff2inside the theme, load it withnext/font/local, and keep its licence next to it.- An
install,preinstall,postinstall,prepareorprepublishscript inpackage.json. The installer blocks those anyway, but they should not be there to begin with. - No dependency lockfile. Without one, nobody can know what your buyer's server
is about to download. Inside this repository the theme has no lockfile of its
own, so run
audit --monorepo; anywhere else, runnpm installfirst.
And these come out as warnings, to be read rather than obeyed: writing or
deleting files, reading environment variables that look like secrets, a
next.config without output: "standalone", and every outside domain your
theme talks to that runtime.hosts does not declare. A theme that fetches a
stylesheet from a font service sends every visitor's address to that service on
every visit, which in the EU is somebody else's legal problem that you created.
If you do it deliberately, declare it.
pack asks whether it is shippable, and it is the only one of the three
that produces something. In one command it validates, audits just the files
that are about to travel, picks those files, reads their contents looking for
secrets, copies them to a staging area — your folder is never touched —, vendors
any contract you are consuming from a local path, installs, builds, and
writes the .zip.
npx pcc-theme pack # my-theme-1.0.0.zip
npx pcc-theme pack --salida ~/out/mine.zip
npx pcc-theme pack --sin-construir # only if a lockfile is already thereTwo things about that list are easy to miss and both matter.
The build is a test, not a deliverable. A theme is distributed as source
and built where it is installed, which is what lets the same package run on
Node versions and machines you will never see. pack builds a copy in the
staging area purely to prove it compiles, and then the compiled output does not
go into the archive: .next, dist, build, .output, .astro,
.svelte-kit are never packaged, no matter what is sitting in your folder.
What goes in is an allow-list, not an exclusion list. pack knows the
names a theme is made of — the manifest, the tokens, the settings, the
templates, the locales, the demo content, your source folders, the config file
of your stack, the lockfile, the README and the LICENCE — and everything else
stays out and is named on screen so you can see what was left behind. Written
the other way round, as a list of things to delete, the file that leaks is
always the one nobody thought to put on the list.
Do not assemble the .zip by hand. The usual way that ends is a dependency
still pointing at a folder on your machine: the package installs perfectly for
you and for nobody else, and you find out from a buyer.
2. Signing it
A signature answers one question for the person installing your theme: is this the package the author published, byte for byte?
pcc-theme signs with RS256 over a manifest that holds a SHA-256 of every file,
plus your publisher name, the theme's id, its version and when it was signed. It
all lands in a theme.sig next to the manifest.
Signing and packing are the same step. sign on your working folder fails
as soon as there is build output in it, and after npm run build there always
is — so in practice you sign while you pack:
npx pcc-theme pack --key path/to/private-key.pem --publisher "Your Studio"Both flags or neither; one alone stops the command. --kid names the key, and
defaults to 1; give it a real name the day you have two.
To check your own work, unpack the archive somewhere and verify it:
npx pcc-theme verify ./unpacked --pubkey path/to/public-key.pemverify is strict on purpose: a file that was changed, a file that went
missing and a file that was added all fail it. That last one is the useful
one — it is what catches something slipping into the archive after it was
signed.
What your buyer sees. On upload, before anything is installed, the panel
shows a report, and the first line of it is the signature: signed by whom,
unsigned, or "the signature is not valid" — and that last one does not install,
ever. An unsigned theme installs, with a warning saying nobody can tell who
made it or whether it has been touched. But a shop that has set
THEMES_EXIGIR_FIRMA=1 refuses it outright, and so does a signed theme whose
key is not on that shop's list. So if you want to sell to people who run their
shops that way, publish your public key and tell them to add it to
THEME_TRUSTED_KEYS, keyed by the same kid you signed with.
The private key lives outside the theme and outside your repository. If you
forget, pack is the backstop twice over: a .pem is never packaged, and a
private key pasted into a text file is one of the patterns the secret scan stops
on.
There is more on how the panel decides what to trust in Theme security.
3. Selling it in the pcreative Commerce market
Getting in
You apply from a shop's storefront, at /vender in the factory themes. The
application asks for your name, your email, whether you publish themes,
extensions or both, and a link to something you have made — that last one is
not optional, because without it there is nothing for anyone to review. Country,
website, the stacks you work in and your experience are all optional and all
help.
One email, one live application. Accepted, you become a seller with your own panel, and the default split is 70 % of every sale to you. A shop can agree a different one with you; what applies is what is on your seller record, not what this page says.
The item and its versions
In your panel, under My items, you create an item — a theme or an extension, with a name, summary, description, price, demo link, licence and the niches it is aimed at — and then you upload versions to it.
The package is a .zip, and it is checked by its first two bytes rather than by
its name: a renamed .rar is refused at upload instead of wasting a reviewer's
afternoon. It has to be under 60 MB (the shop can change that). Every
version carries a number and its notes, and the notes are what your buyers
actually read.
The number is compared as SemVer, not as text. It has to be higher than
anything you have already published: 1.10.0 comes after 1.9.0, and a
pre-release sits below its release, so 1.4.0-beta.1 < 1.4.0-rc.1 <
1.4.0. Build metadata after a + counts for nothing.
Screenshots — up to eight — are what sell the thing, and the first one is the one the listing shows, so put your best one first. The sizes the market is built around are 2340 × 1560 for the cover, in 3:2 and never under 1170 px wide, and 1170 px wide for the gallery shots, at most 1500 tall. Take them from a real storefront with real content in it. A screenshot of an interface that does not exist is a refund with a delay on it.
To send an item for review it needs an uploaded package, a written summary and a price — zero is a price. While it sits in the queue you cannot touch it: what is reviewed is what gets published, and that is the whole point of the freeze.
The review, and what comes out of it
Three outcomes, and you get an email for all three. Published, and it goes on sale. Changes requested, with what has to change written down; you fix it and send the same item back, you do not start again. Rejected, with a reason — neither of the last two is allowed to be saved without one, so you will never be left guessing.
When it is published, the item becomes a product: the shop creates it, ties it to you, turns your package into its download and your first screenshot into its image. That tie is what credits your share when somebody buys. From there the money follows the ordinary rules — a 30-day hold, a 50 € minimum, and a payout profile you fill in yourself. See Paying authors and sellers.
Publishing a new version
A new version replaces the file, it does not add a second one. Everyone who already bought the item downloads the new one without paying again, gets an email with your notes, and has their download allowance renewed.
Which makes one thing your responsibility, and it fails silently: if you rename
or drop a setting, say so in migraciones in theme.json. A shop's
customisation is stored separately from your theme, by key. Rename a colour
token without declaring the rename and the theme asks for a key nobody has, gets
the factory value, and on Monday somebody's shop is a different colour and they
will swear they changed nothing. The manifest takes renombra, elimina and
anade, each from a desde version — a list of what happened, not a script,
because nothing a theme ships gets executed.
Support, and what it says about you
Every purchase includes six months of support from you, and it runs through private one-to-one threads rather than a forum: in a forum the angry question is read by the next person who was thinking about buying.
A buyer needs a licence key to open one. If their support has run out the thread still opens, flagged, and you decide whether to take it — shutting the door on somebody who paid you is worse business than answering. A thread waiting more than two days is marked late, and one you have replied to closes itself fourteen days later if the buyer does not come back.
On closing, the buyer is asked one question: did it help. That single answer feeds the two numbers on your public profile — what share of rated threads were resolved and your median hours to first reply, over the last 60 days — and the percentage only appears once three people have answered, so one bad day does not put a 0 % next to your name. Those two numbers are computed, not editable, and that is exactly why they are worth anything.
The rest of the profile is yours: name, introduction, website, country, links,
logo, and the list of what you sell. Links have to start with https://.
4. Selling it somewhere else
Where
The big curated marketplaces are organised by platform, and none of them has a pcreative Commerce shelf yet. Which leaves two honest answers.
Your own shop. You are holding an ecommerce platform, and the author market is not a service we host: it is machinery that runs in every installation, including yours. Publish your theme as an item in your own shop and you get the same downloads, versions, licences, purchase emails and support threads described above, on your own domain, with no marketplace taking a cut. The panel side of that is Selling other people's themes and extensions; the delivery rules are in Sell downloadable products.
A general marketplace that takes standalone code — templates, apps, starter kits — rather than plugins for one platform. You get its traffic and you play by its rules.
What those rules tend to be
These are the conventions we measured across the large curated marketplaces while designing our own, in September 2026. They are not universal and they change, but they repeat often enough to plan around:
Review is human and takes up to about two weeks. Rejections come in two flavours and the difference matters: the soft kind comes with feedback and you resubmit, the hard kind comes without it and you do not. Signing up and handing over tax details are deliberately decoupled, so you can be selling before your paperwork is finished but not paid. Payouts run on a threshold and a fixed calendar, not on request. Refunds are accounted in two different months — the sale in the month it happened, the refund in the month it was returned — which is why your earnings report and your bank statement never match. And a chargeback invalidates the purchase code, which is the marketplace telling you that support entitlements die with the payment.
What a price ceiling looks like
Price is a property of the market, not of the product, and anyone who quotes you a number without saying which market and when is guessing. One measurement is worth carrying anyway, because it is about how a product is built rather than how it was marketed.
In a public marketplace for a different kind of software — control panels, not shops — the paid products in one category split cleanly in two in September 2026. The ones built as plugins of somebody else's framework averaged a little under $12, and not one of them passed $17. The ones that owned their own rendering averaged around $21, and the dearest was just under $54. Same marketplace, same buyers, same month.
The reading is not that one number is right. It is that a product whose ceiling is set by what its host framework allows it to change gets priced like an add-on, and a product that decides what it renders gets priced like a product. A pcreative Commerce theme is on the second side of that line by construction: it is a standalone app that happens to talk to a shop over HTTP, and it can be worth what its design is worth. That is a September 2026 reading of one category in one marketplace. Do not carry it around as a law.
About licence keys, honestly
theme.json has a license_check block, your buyer pastes a key on the theme's
card in their panel, verification then works offline against an embedded public
key, and a licence never switches off a storefront — the only thing it can
refuse is publishing, with the owner standing right there.
What does not exist yet is the part you would need to use it as a third party:
activation and renewal talk to the pcreative licence service, and there is no
self-service way for an author to issue keys for their own product through
it. The endpoint field in the manifest is reserved for pointing somewhere else
and is not read today. So do not promise your buyers key activation you have
no way to issue. If you sell outside our market, either agree the arrangement
with us first, or ship without license_check and let the marketplace's own
delivery be the proof of purchase.
5. What gets a listing turned down
Everything up to here is about the package. This is about the page that sells it, which is where most of the rejections actually happen.
The cover is read at thumbnail size. In a listing grid it is a couple of hundred pixels wide, so four or five enormous words are the whole budget. Marketplaces tend to stamp the price over one of its corners, so leave that corner empty. And the headline says what the theme does, not what it is called — the name is already printed under the card by the site itself.
The screenshots are the product. Real storefront, real products, real content. Images of an interface that does not exist are the single fastest way to turn a sale into a refund and a review.
Declare your dependencies where the marketplace asks for them, in its own field, not buried in the description. Reviewers read that field and so do buyers; anything a buyer needs and does not have — a licence key of yours, a particular version, server access — belongs there. A requirement discovered after purchase is a one-star review with a valid complaint attached.
And write like one person made one thing. Reviews are done by people, and reviewers turn down work that looks mass-produced. What gives it away is rarely any single file: it is uniformity — the same blocks, the same structure and the same filler repeated identically across several products by the same author. Nothing here asks you to write less carefully. It asks you not to ship the same carefully-written thing five times with the colours changed.
6. Before you press publish: open the package
Build the .zip, then open it and look inside. It takes two minutes and it is
the last point at which anything is still retractable — a download that has
been downloaded cannot be recalled.
pack does most of this for you, and it is worth knowing which half is which.
The allow-list keeps out whole categories by name: .env and everything
that starts with it, .pem and .key and keystores, node_modules, .git,
build output, an emitted licencia.json, and anything that is simply not part
of a theme. The content scan is the other half: it reads every text file
that is about to travel, line by line, looking for private keys, provider
tokens, connection strings with a password in them and hand-assigned secrets,
and it stops the command dead rather than warning.
That split is the whole lesson. Search by content, never by filename. A list of names to delete only ever protects you from the leaks you already thought of, and a pattern in it that silently matches nothing looks exactly like a pattern that works.
Two more places to look, because a clean package is not a clean project. Your listing text gets pasted somewhere public in one piece — read it as a stranger would. And if you publish your source anywhere, the history carries whatever was ever committed, even after you delete the file; removing it from the current commit changes nothing, and the only real fix for a credential that was once committed is to rotate it.
7. Obfuscation: no
It comes up every time somebody prices their first theme, so here is the reasoning, and it is specific to how themes travel here rather than a general opinion.
A theme is shipped as source and built on the buyer's machine. There is no compilation step between you and them that could hide anything: whatever you obfuscate is what they install, what they have to debug, and what they were supposed to be able to customise.
Our own installer treats obfuscation as an attack, and it cannot tell yours
from anybody else's. audit blocks eval, new Function and long base64 blobs
decoded at runtime, with the reason printed on screen: that is how a payload
gets hidden. An obfuscated theme does not sell badly — it does not install.
It does not stop copying, it only raises the price of a few hours' work for someone who was never going to pay you. What actually helps is dull by comparison: a signature, so an altered copy is visibly altered and the panel says so out loud, and — where the channel supports it — a per-download marker, so a file that turns up where it should not can be traced to the copy it came from. Both of those need readable files to work. Obfuscation fights them.
8. Licences: what the system actually issues
For items sold through the author market, one licence is issued per order line, when the payment is captured. A payment notice that arrives twice does not produce two keys.
The key is the credential — there is no user and no password. Your product,
installed on the buyer's site, talks to the shop it was bought from with it:
POST /licencias/activar ties it to a site, /licencias/comprobar asks whether
it is still good, /licencias/soltar frees the site up again. The answer is
signed, so your product can tell it came from the shop it was bought from and
not from something pretending.
One licence, one site, unless the variant bought says otherwise — a
"three-site licence" is a different variant, not a different kind of key. The
address is normalised before anything is counted, so http://Example.com/,
https://www.example.com and example.com are one site and the port is
ignored; without that a single website burns three activations and the buyer
ends up in your inbox. Reinstalling the same site costs nothing. And
localhost, private ranges, .local, .test, staging. and dev. cost
nothing either, because a developer who runs out of licence before launch writes
to support instead of launching.
Refund the whole order and its licences are cancelled; refund part of it and they stand.
Support expires. The right to use does not. The support date is stored on the licence and returned by the check alongside whether it is still current, and that is all it governs. After it passes, the licence is still valid and new versions still arrive. Switching somebody's product off because their support window closed is a refund request with a delay fuse on it, and it is not what this system does.
The mistakes that keep happening
sign refuses, saying there is compiled output → it is meant to. Sign while
you pack: pack --key … --publisher ….
audit blocks on "no dependency lockfile" → inside this repository, add
--monorepo. Outside it, run npm install first.
The build works on your laptop and dies on the buyer's server → almost always a font downloaded at build time. The panel builds with no network.
A version is refused as lower than the published one → versions are compared
as SemVer, and a pre-release ranks below its release. 1.4.0-rc.1 cannot follow
1.4.0.
You cannot edit your item → it is in the review queue, and it is frozen on purpose until the review is done.
The upload says it is not a .zip → it is checked by its contents, so a
renamed archive of any other kind is caught at the door.
Shops changed colour after your update → a renamed setting with no
migraciones entry. The old value is still stored under the old key; the theme
is asking for the new one.
The theme installs with a warning about who made it → it is unsigned. Pack
it with --key and --publisher.
See also
- Build a theme
- Write a plugin
- Theme contract
- Theme security
- Paying authors and sellers
- Selling other people's themes and extensions — the same market, seen from the panel