Partner frontend
You do not have to assemble a client's design and frontend from the platform template. You can change the look through files, or build your own application on top of the Shop API. Checkout, payments and the customer account always stay on the platform.
Quick start
A client without programming. A client changes the look of their shop in the administration: colours, fonts and the shop template under Shop › Look and feel, website pages under Content › Website pages. They preview the change, publish it on the Look versions screen and go back to the previous version with one click. The assistant in the administration can do the same.
A partner with the starter. The starter @portican/create is being prepared for release on public npm; until it is released, npm create @portican@latest does not work. The starter sets up a frontend on top of the SDK with the platform blocks, the cart handed over to checkout, legal texts, the consent bar and SEO.
A partner with a template. You change the look in files and upload it with portican vzhlad push as a draft; the merchant publishes it. You save a finished look to your template library and pick it for your next client already at the registration of their shop.
Upload and hosting. You upload a finished custom frontend as a static site to Portican hosting with portican frontend deploy (section Uploading a frontend), or run it yourself.
Two ways
- Look as files. The store runs on a platform template and you change its theme, the store layout, the home page, custom CSS and website pages. You keep the files in git and upload them with
portican vzhladas a draft; the merchant publishes it. See the section Look as files below. - Your own frontend. You write your own application that reads the store catalogue and content through the Shop API using the
@portican/storefrontSDK. You upload it to Portican hosting as a finished static site withportican frontend deploy: the hosting only serves files, so there is no server-side rendering (SSR) there. A frontend that needs SSR runs on your own hosting. See the sections SDK client and Uploading a frontend below. - You can combine either way with what Markup hooks, Blocks and fields and Website preset describe. Read the content the merchant is meant to edit from the store (settings, website pages, products) or from your own blocks with a content schema (section Partner blocks): the admin edits only what the platform knows, and it knows a custom block through the schema you upload.
From the key to a deployed frontend
- A frontend on your own hosting needs a frontend key: the store operator creates it in the admin under Shop › Integrations, names it and enters the website addresses the browser will call from. An address is
httpswithout a path and must not belong to the platform or its subdomain; at most 5 addresses per key and 5 keys per market. The key is shown only once. - The market token and the Shop API address of the store are on the same screen, with a button to copy the address; the server builds the address from the market domain, so do not guess it from examples. The key is valid only with the token of its own market, sent in the
portican-kluc-frontenduheader together with thevendure-tokenheader. - The SDK
@portican/storefrontis being prepared for release on public npm; until it is released,npm install @portican/storefrontdoes not work. The package will have no dependencies and every version will be published only by Portican CI from the main branch. - A static site (Portican hosting included) creates the client in the browser, without the key. The key belongs only on your own server, when you render the frontend on a server outside Portican. The example is below in the section SDK client.
- Read the catalogue, settings and website pages. Keep the cart on your side and hand it to the platform with a one-time code at checkout (section Cart and checkout).
- You upload the finished static frontend to Portican hosting (section Uploading a frontend); the platform gives it the Shop API address, the market token and the language. A frontend on your own hosting must run at the address you entered with the key.
What the key allows
- A frontend key allows only public read queries from the list below and a single mutation,
odovzdajKosik. Every other operation ends with the errorKLUC_FRONTENDU_OPERACIA. - A request with the key, or from the registered website address, carries no customer session. Partner code does not create a Vendure cart and cannot see the account, orders, payments or links with a token.
- The limit is 3,000 requests per minute per key. To the platform a partner server is a single address for all its visitors, so it can sign the visitor's address with the key secret in the
x-portican-navstevnikheader (the SDK does that when you passadresaNavstevnika); the limits then apply per visitor. Without a signature, a limit per server address applies alongside the key limit. - When the limit is hit you get the
PRILIS_VELA_POZIADAVIEKerror with the number of seconds to wait. A wrong, revoked or foreign key returnsKLUC_FRONTENDU_NEPLATI. The operator revokes a key in the admin and the website stops loading products. - The key belongs on the server only. The browser calls the Shop API without the key, and only from the registered website address (CORS). A key in code delivered to the browser could be used by anyone.
Queries the key allows
The public catalogue and store content. Nothing about the customer, the order or links with a token. The list is an allow list, not a block list: a new plugin query is not open by itself.
From the platform code. This list is built directly from the platform source code, and every deployment checks that the two still match.
activeChannelavailableCountriescollectionscollectionfacetsfacetproductsproductsearchstorefrontSettingsschemaBlokovstrankyWebustrankaWebusuboroveStrankyObchodusuborovaStrankaObchodupravneTextyObchodupravnyTextObchoduvypisBloguclankyBloguclanokBloguadresyClankovjazykyClankumapaClankovakcioveProduktyproduktyPodlaIdnajpredavanejsieProduktynovinkyProduktyodporucaniaKProduktuvacsieVariantyrecenzieProduktusocialnyDokazProduktunastavenieVypredanychskryteProduktycenovePasmapresmerovaneKategoriepresmerovaneKartypresmerovaniaObchoduzobrazenieTrhusviatocneTerminyblokyPartnera
SDK client
The SDK is a typed Shop API client with no dependencies. It runs in Node 18 and later, in Cloudflare Workers, in Deno and in the browser. It reads store settings, categories, products, search and website pages.
Errors carry a code (kod), the HTTP status (stav) and, at the limit, cakajSekund. The headers and error codes in the SDK match the platform core; a package test guards that.
What the SDK does besides reading: legal texts of the store (pravneTexty, pravnyText), the platform block schema (schemaBlokov), reading page blocks, the content of partner blocks (blokyPartnera, it calls the query blokyPartnera and returns verzia, odtlacok, jazyk and bloky, see Reading block content and the webhook), SEO helpers, consent and handing over the cart. What the SDK does not do: it does not render blocks, you draw them.
import { vytvorKlienta, ChybaObchodu } from '@portican/storefront';
// Statický web (aj na hostingu Porticanu): v prehliadači, bez kľúča, zo zapísanej adresy webu.
// Adresu Shop API, token trhu a jazyk zapíše hosting Porticanu do /portican-env.json.
const premenne = await fetch('/portican-env.json').then(r => r.json());
const obchod = vytvorKlienta({
adresaApi: premenne.PORTICAN_ADRESA_SHOP_API, // Shop API obchodu
tokenTrhu: premenne.PORTICAN_TOKEN_TRHU,
jazyk: premenne.PORTICAN_JAZYK,
});
// Vlastný server mimo Porticanu (SSR): vytvorServerovehoKlienta z @portican/storefront/server
// s kluc: process.env.PORTICAN_KLUC_FRONTENDU a pri dopyte { adresaNavstevnika }, aby stropy platili na návštevníka.
try {
const produkt = await obchod.produkt('kytica-ruzi');
const vysledky = await obchod.hladaj({ vyraz: 'ruže', take: 24 });
const nastavenia = await obchod.nastavenia();
const stranka = await obchod.stranka('o-nas');
} catch (e) {
// Pri strope príde kód chyby so sekundami, ktoré treba počkať.
if (e instanceof ChybaObchodu && e.strop) await pockaj(e.cakajSekund ?? 1);
else throw e;
}
Cart and checkout
- The cart is a list of variants and quantities in your frontend, at most 50 lines and 999 units of one variant. The platform does not accept a bigger cart and does not round it quietly: the partner sees an error.
- At checkout you call
odovzdajKosik. You get a one-time code that is valid for 10 minutes and works once. At most 120 hand-overs per minute are allowed per key or website address. - The customer brings the code in a POST form to
/prevziat-kosikof the store (or/<language>/prevziat-kosik), never in the address. Checkout takes the cart over only from the website that handed it over, so the form must be sent from the registered website address. - Prices, stock, delivery and discounts are calculated by the platform, not by your code. A variant outside the store is rejected at hand-over, and a sold-out unit simply drops out of the cart on take-over. The order is created only by checkout, in the customer's session.
import { nacitajKosik, pridajDoKosika, ulozKosik, formularPokladne } from '@portican/storefront';
// Košík je zoznam variantov a množstiev u vás (localStorage, cookie, KV).
const kosik = pridajDoKosika(nacitajKosik(localStorage), variantId, 1);
ulozKosik(localStorage, kosik);
// Pri pokladni ho odovzdáte platforme; ceny, sklad a dopravu prepočíta ona.
const { kod } = await obchod.odovzdajDoPokladne(kosik);
const f = formularPokladne(kod, 'https://www.obchod-klienta.sk', 'sk');
// <form method="POST" action={f.action}><input type="hidden" name="kod" value={f.polia.kod} /></form>
Look as files: command line
- The
portican vzhladcommand downloads the store look (theme, store layout, home page, custom CSS and website pages) as files that you keep in git, and uploads them back as a draft. The merchant previews the draft and publishes it on their own in the admin on the Look versions screen. The command line publishes nothing. - It needs an API key with the Look drafts scope (Shop › Integrations, see Webhooks and API). The key belongs in the
PORTICAN_API_KLUCenvironment variable, never in the command: it would stay in the shell history. For a store with several markets set the market token inPORTICAN_TRH. verzielists the look versions,pulldownloads the published (or a chosen) version,pushuploads the folder as a new draft and prints how many parts it changes compared with the live look, andnahladprints a preview link that is valid for an hour. A package must not exceed 2 MB.- The JSON files have sorted keys and indentation, so the git diff shows only the real change. The command line
@portican/cliis being prepared for release on public npm; until it is released, neithernpx @portican/cli vzhladnornpm install --save-dev @portican/cliworks.
export PORTICAN_API_KLUC="..." # kľúč s rozsahom Vzhľad, nikdy v príkaze
portican vzhlad verzie
portican vzhlad pull --do vzhlad
portican vzhlad push --z vzhlad --nazov "Jesenná kampaň"
portican vzhlad nahladFiles in the vzhlad/ folder
pull writes these files and push uploads them as a draft. A part whose file is missing from the folder is not carried by the draft.
| File | What it holds |
|---|---|
vzhlad.json | The package version and the look version it was downloaded from. pull writes it, do not edit it by hand. |
tema.json | Theme colours and fonts. |
podoba-eshopu.json | Store layout and template. |
uvodna-strana.json | Home page sections. |
bloky-partnera.json | The block schema of the partner's own frontend (see the Partner blocks section below). |
sekcie-stranok.json | Order, visibility and band of the page sections (see the Sections below the product card section). |
navigacia.json | Header menu and shop footer. |
vlastne-css.json | Whether custom CSS is on, and its version. |
vlastne.css | Custom CSS as a real .css file. |
stranky/<jazyk>/<slug>.json | The website pages, one file per page in the language folder. A slash in the page address becomes ~ in the file name: the page o-nas/tim is the file o-nas~tim.json. |
Sections below the product card
- The
sekcie-stranok.jsonfile changes the order of the sections that the platform store draws below the product card, not their content. Each section haszapnuta(yes or no) andpozadie(platno,pas). The product photos and details stay on top, they belong to buying, not to a section. - The sections come from a closed platform vocabulary:
zalozky,dovera,recenzie,odporucania,podobne,otazky,otazkyProduktu,tabulkaVelkosti,bezpecnostVyrobku,nedavno,podkategorie,filtre,vypis,popis,uzavierka,odmeny,zasielky,zlavovykod,body,zdielanie. An unknown section and a second occurrence of the same section are dropped. A section the file does not mention stays where it is, and a section the platform adds later arrives switched off. - A draft without this file leaves the order unchanged: it takes the one that is live. It goes live only when the merchant publishes the draft, not the command line. The registry knows the pages
produkt,kategoria,kosik; more arrive when the platform adds them, and this section lists them on its own.
{
"produkt": [
{
"druh": "recenzie",
"zapnuta": true,
"pozadie": "pas"
},
{
"druh": "zalozky",
"zapnuta": true,
"pozadie": "platno"
},
{
"druh": "nedavno",
"zapnuta": false,
"pozadie": "platno"
}
]
}produkt
Product page: the sections below the card in today's order.
| Section | What it draws | Default |
|---|---|---|
zalozky | Description, parameters, files and the returns answer in tabs; drawn only with the zalozky product layout. | on |
dovera | Trust badges; drawn only when the store has at least one badge switched on. | on |
recenzie | Product reviews. | on |
odporucania | The paired offer below the card. | on |
podobne | Similar products from the category. | on |
otazky | The store's frequently asked questions, the same as on the Contact page; the returns answer is for this product. | on |
otazkyProduktu | Customer questions about the product and the shop's answers; off by default. | off |
tabulkaVelkosti | Product size chart; off by default. | off |
bezpecnostVyrobku | Manufacturer and safety information under GPSR; off by default. | off |
nedavno | Strip of recently viewed products. | on |
kategoria
Category page: sections above the listing and the listing, in today's order.
| Section | What it draws | Default |
|---|---|---|
podkategorie | Subcategories of the current category. | on |
filtre | Filters of the category listing. | on |
vypis | Product listing of the category. | on |
popis | Category description. | on |
kosik
Cart: sections around the order summary; the checkout does not change with them.
| Section | What it draws | Default |
|---|---|---|
odporucania | The paired offer below the card. | on |
uzavierka | A line saying by when to order for the next delivery. | on |
odmeny | Rewards for the order value in the cart. | on |
zasielky | Splitting the order into several shipments in the cart. | on |
zlavovykod | Discount code field. | on |
body | Loyalty points in the cart. | on |
zdielanie | Link for sharing the cart. | on |
Section background
platno: the section on the page canvas, as before.pas: the section in a full-width band in the theme surface, like the website blocks.
Block schema
- The Shop API serves the
schemaBlokovquery: a machine-readable description of page block and home page section kinds with fields, types, length and count limits, options and a version. With it you can check which fields a block carries without reading the platform code. - The schema is public and read-only, the same for every market and free of store data. The version rises with every change of fields, types or limits and is confirmed by a fingerprint, so you can tell whether you know exactly that version.
- The
schemaBlokovquery is in the list of frontend key queries above, so the SDK can call it with the key too. The schema is public and the same for every market, so the key reveals nothing new.
Partner blocks: content schema
- A custom frontend draws its own blocks, but you do not hard-code the texts, images and links in them. The merchant changes them in the admin on the screen Shop › Partner blocks, or the assistant does. That is why every block carries a content schema: fields, their types, limits and defaults. The platform builds the form from it and validates the stored content against it. A block without a single field is rejected (finding
bez-schemy-obsahu), because its texts would stay in the code. - You write the schema into the file
bloky-partnera.jsonin the look (see the section Look as files) and upload it with the draft usingportican vzhlad push. It goes live only when the merchant publishes that look version. Raiseverziawith every change of fields: the platform rejects a changed schema that has the same version as the one live in the shop. - Field types:
text,viacriadkovy,adresa,obrazok,cislo,volba,prepinac,zoznam,produkt,kolekcia,stranka. Text allows 200 characters by default (up to 1,000), multi-line text 2,000 (up to 10,000). A number has the limitsnajmenejandnajviac, a choice has the listhodnoty, a list has the fieldspolozky(no nested list) andnajviacitems: 20 by default, at most 100. - Schema limits: at most 60 blocks, 40 fields per block and per list item, a schema of at most 256 KiB and the content of one block in one language of at most 64 KiB. The block code is slug-like (
uvod-pas), the field name an identifier as in JavaScript (nadpis); you can give names for the merchant in other languages too (nazvy). - The default is what the website shows until the merchant fills in the field, and after the button Back to the partner default. Stored content is not deleted when the schema changes: a field that left the schema is not delivered to the frontend, and when the schema comes back the text is back.
{
"verzia": 1,
"bloky": [
{
"kod": "uvod-pas",
"nazov": "Úvodný pás",
"nazvy": {
"cs": "Úvodní pás",
"en": "Hero"
},
"polia": [
{
"pole": "nadpis",
"typ": "text",
"nazov": "Nadpis",
"najviacZnakov": 80,
"predvolba": "Kvety na každý deň"
},
{
"pole": "fotka",
"typ": "obrazok",
"nazov": "Fotka"
},
{
"pole": "tlacidlo",
"typ": "adresa",
"nazov": "Odkaz tlačidla",
"predvolba": "/kategorie"
},
{
"pole": "vyhody",
"typ": "zoznam",
"nazov": "Výhody",
"najviac": 4,
"polozky": [
{
"pole": "text",
"typ": "text",
"nazov": "Výhoda",
"najviacZnakov": 60
}
]
}
]
}
]
}
Reading block content and the webhook
- The frontend reads block content with the Shop API query
blokyPartnera(example below); it is among the queries a frontend key allows. The answer has the schemaverziaandodtlacok, thejazykand theblokyby block code with the values of all fields: a missing or invalid value is replaced by the partner default. Without an uploaded and published schemaverziaandodtlacokarenullandblokyis empty. - The language is optional: without it the request language applies, and a language the market does not have gets the default language of the market, so the frontend always draws something. The fingerprint tells the frontend whether it reads the schema it was built with.
- The
@portican/storefrontSDK cannot call the queryblokyPartneratoday. You call it as plain GraphQL with the key and market headers, like the other queries. - The frontend learns about a content change from the webhook
obsah.zmeneny: the fieldznackyin its body containsbloky-partnerawhen the merchant changed block content or the block schema changed. The webhook is signed with the headerportican-podpiswith a tolerance of 300 seconds and retried on failure; headers, signature check and retries are the same as for the other events, see Webhooks and API. The merchant enters the webhook address on the screen Integrations.
POST {adresa Shop API obchodu}
vendure-token: {token trhu}
portican-kluc-frontendu: {kľúč frontendu} # len na serveri
{ "query": "{ blokyPartnera(jazyk: \"sk\") }" }
# Odpoveď (pole blokyPartnera v data):
{
"verzia": 1,
"odtlacok": "9f2c…",
"jazyk": "sk",
"bloky": {
"uvod-pas": {
"nadpis": "Kvety na každý deň",
"fotka": null,
"tlacidlo": "/kategorie",
"vyhody": [{ "text": "Doručenie v deň objednávky" }]
}
}
}
Uploading a frontend
- You upload the finished frontend (not the source code) from your own CI with
portican frontend deploy. The command reads a folder (distby default), sends it to the platform as one version and prints the address of its preview. Nothing changes live. - Portican hosting serves static output only:
index.html, scripts, stylesheets and images. No partner code runs on the server, so there is no server-side rendering (SSR), no server functions and no API routes. The site is assembled in the browser and calls the Shop API without the key. The platform writes the market Shop API address, the market token, the checkout and website addresses, the language and the shop domain to/portican-env.jsonnext toindex.html, so one build runs on every market you release it to. The@portican/createstarter builds such a site for you. - The root of the folder must contain the manifest
portican.json:verzia(2),assety(the website folder inside the bundle, containingindex.html) and, optionally,hostitelia. Outside the website folder only the manifest,package-lock.jsonandbloky-partnera.json(the custom block schema, section Partner blocks) are allowed. A worker bundle (a version 1 manifest with ahlavnyfield) is rejected with a sentence telling you what to change. - You need an API key with the scope Frontend deployment (
nasadenie-frontendu; Shop › Integrations, see Webhooks and API), stored in the secrets of your CI asPORTICAN_API_KLUC. Such a key can only upload a version and read the list of versions (portican frontend verzie); it neither publishes nor rolls back. - The platform stops the upload when: the manifest is missing or has another version,
index.htmlis missing or a file sits outside the website folder where it does not belong,package-lock.jsonis missing (the CLI can add it with--zamok), the bundle has a known high-severity vulnerability, the version exceeds 25 MB or 2,000 files (a single upload at most 14 MiB), or it has a custom block without a content schema. Keep hidden files (.env) and source maps out of the bundle. The CLI prints the findings of the check. A market can have at most 10 unpublished versions at a time. - From the browser, the site may call the Shop API of the shop and the hosts in the
hostiteliafield of the manifest (at most 10,httpsonly, no wildcard, never an IP address or a platform domain); everything else is blocked by the content security policy (CSP) the hosting adds to every page. The policy does not allow an inline<script>in the HTML, so scripts belong in files. Static files must not sit under paths that the platform serves on the shop domain (/shop-api,/assets,/health,/na-stiahnutie,/sukromne-subory,/_next,/api,/.well-known,/robots.txt,/sitemap,/llms.txt,/feed,/ucp): such a bundle is not uploaded. - Search engines. A static site returns the same
index.htmlfor every address and the content is rendered in the browser. Google and Bing render it and read titles, descriptions, canonical addresses and structured data (JSON-LD from the SDK helpers), only later; tools that do not run scripts (link previews, some crawlers) see an empty page. The sitemap, product feeds,robots.txtandllms.txtare served on the shop domain by the platform from the store data. Pages that rarely change can be prerendered to plain HTML at build time; content the merchant edits then shows up only after the next build. - A version has the state draft, preview, published or archive; at most one is published per market. The merchant publishes, not the key: in the admin the assistant does it (“publish the new frontend”) and it can also go back to the previous version or to the platform template. Going back to the template is one step.
- Until the platform switches on frontend hosting on its server, the upload ends with a sentence about hosting that is not connected and nothing is stored. A frontend on your own hosting with the frontend key keeps working without it.
// portican.json v koreni priečinka balik/
{
"verzia": 2,
"assety": "verejne",
"hostitelia": [
"https://cms.partner.sk"
]
}
// balik/verejne/index.html, balik/verejne/static/…, balik/package-lock.json
# v CI partnera:
npm run build # statický web (napríklad vite build) a balík v balik/
export PORTICAN_API_KLUC="..." # kľúč s rozsahom Nasadenie frontendu
portican frontend deploy --z balik --nazov "$GITHUB_SHA"
portican frontend verzie
What the platform does not do
- Checkout, sign-in, the customer account, payments and refunds are always on the platform. A partner frontend has no access to them and payment data does not pass through it.
- Portican neither builds nor runs a partner frontend: you build it in your own CI and upload the finished static site. Portican hosting does not support server-side rendering (SSR) or partner server code; a frontend that needs them runs on your own hosting with the frontend key.
- Do not hard-code texts and images that the merchant is meant to change: read them from the settings, website pages and products the merchant edits in the admin.