Licensing

Put blocks and features behind your own paid plans — and understand exactly what this does and does not enforce.

@storvexa/pdf-builder Chapter 13 of 14
Documentation

Read this first

This is presentation, not security. Never describe it as a security boundary. The package never verifies a licence, never reads a key, and never calls anything. It shows what you tell it to show. Anyone can open devtools and change the props — so the gate stops honest users from wandering into a paid feature, and that is all it is for.
Real enforcement belongs on your server, where documents are saved and rendered. See the last section — it is the important one.

Declaring the plan

You validate the subscription on your own server and pass the result down. The package trusts it completely, because it has no way not to.

<PdfBuilder license={{ plan: 'professional', features: ['ai-design', 'premium-blocks', 'custom-templates'], }} />

The feature strings are yours. The package never interprets them — it only compares the ones a block asks for against the ones you granted.

Gating a block

registry.extend('lineItems', { requiresFeature: 'premium-blocks' }) // …or on a block of your own const premiumChart = { type: 'chart', requiresFeature: 'premium-blocks', /* … */ }

A gated block whose feature is not granted:

stays visible in the palettewith a lock badge
is not draggableit cannot be added
calls onLicenseRequired on clickso you can show your own upgrade screen
Visible-but-locked beats hidden, and it is deliberate. A user who cannot see a feature cannot want it. A lock badge is how someone discovers there is a reason to upgrade — hiding it means they never learn the paid tier exists.
No built-in block is gated out of the box. Everything in the palette works with no license prop at all. Gating is entirely your decision.

Gating AI

AI is usually the first thing worth charging for, because it costs you money per use.

<PdfBuilder onAiDesign={callMyServer} aiRequiresFeature="ai-design" license={{ features: user.plan === 'pro' ? ['ai-design'] : [] }} onLicenseRequired={(feature) => openUpgradeModal(feature)} />

Without the feature, the AI button shows a lock and activating it calls onLicenseRequired instead of opening the dialog. The licence is re-checked when the request is actually submitted, so flipping the prop mid-session cannot leave a dialog open that should not be.

Do not rely on this to control your provider bill. The check that protects your spend is the one on your AI endpoint. This one only keeps the button quiet.

The upgrade path

<PdfBuilder onLicenseRequired={(feature, blockType) => { analytics.track('upgrade_prompt', { feature, blockType }) router.push(`/pricing?want=${feature}`) }} />
The package ships no pricing page, no checkout and no upgrade dialog. It tells you which feature was wanted and from which block; the offer, the wording and the payment are yours. That is the only way it can fit your pricing rather than dictating it.

onLicenseRequired is also a good analytics signal: it tells you which locked feature people actually reach for, which is better evidence for pricing than a survey.

Where real enforcement lives

Put the check at every point where a paid capability costs you something or produces something of value:

EndpointEnforce
POST /api/ai/designReject with 402 unless the account's plan includes it. This is the one that protects your provider bill.
POST /api/documentsReject a document containing gated block types.
POST /api/render-pdfRefuse to render what the plan does not cover.
POST /api/uploadsEnforce storage quotas here, not in the UI.
// Express, but the shape is the same anywhere app.post('/api/ai/design', requireAuth, async (req, res) => { if (!req.user.plan.features.includes('ai-design')) { return res.status(402).json({ error: 'Upgrade required' }) } res.json(await callProvider(req.body)) })
Belt and braces, and they do different jobs. The UI gate makes the product legible — people can see what they would get. The server gate is what actually holds. Ship both; describe only the second one as enforcement.