Skip to content
pcreative Commerce

Configuration

Everything is configured in two places, and it is worth knowing which is which.

Everything is configured in two places, and it is worth knowing which is which:

  • The backend .env — what the system needs in order to start: database, secrets, keys for outside services. You edit it in a text editor and it requires a restart.
  • The panel — what changes day to day: store name, tax details, payment methods, AI provider. Requires no restart at all.

The rule is simple: if it is a secret, it goes in the .env. Everything else goes in the panel. There is one exception: the payment gateway keys can also be entered in Settings → Payments, and then they are stored in the database encrypted (table pcc_pasarela) and take precedence over the .env. No key ever travels to the browser, except the public ones gateways are designed to expose.

With Docker, the backend reads the whole .env next to docker-compose.yml, so every variable on this page works the same inside and outside Docker. A few are derived from others there: CORS_TIENDA and STOREFRONT_URL come from TIENDA_URL, CORS_PANEL from PANEL_URL and PCC_BACKEND_URL from BACKEND_URL, and those win over the same name written in the .env.

The full list of variables, generated from the code, is in reference/environment-variables.md. What follows is what you need to understand in order to decide.


The minimum to start

Only one variable is required:

DATABASE_URL=postgres://user:password@localhost:5432/my_store

With nothing else, the system starts and works. Everything else turns things on.

Secrets

JWT_SECRET=
COOKIE_SECRET=
PCC_CLAVE_CIFRADO=     # optional; if empty, JWT_SECRET is used

Sessions do not depend on these: they are random tokens stored in the database, not signed tokens. What does depend on them is encryption. PCC_CLAVE_CIFRADO —or JWT_SECRET when it is empty— is the key that encrypts the gateway keys saved from the panel, the digital keys and codes you sell and the sellers' payout accounts, and it also signs private download links and licences. That is the real risk of leaving the example values: anyone who knows them can decrypt what your database stores. Change them before you go live.

Without JWT_SECRET or PCC_CLAVE_CIFRADO nothing sensitive is stored or read: gateway keys, digital keys and codes, payout accounts, download links and licence signatures are all refused with an error that says which variable is missing (HTTP 503). There is no built-in fallback key. In practice: always set one of the two (Docker does it for you). An empty PCC_CLAVE_CIFRADO= counts as unset, so JWT_SECRET is used.

Under Docker you can leave them empty: the container generates a set on first run and keeps it in the datos volume. Two consequences:

  • With several backend replicas, set them by hand with the same value on all of them: with different secrets, one replica cannot decrypt what another one saved.
  • If you lose the datos volume, new secrets are generated and everything encrypted becomes unreadable. Back it up, or set PCC_CLAVE_CIFRADO yourself and keep it somewhere safe.

If you change PCC_CLAVE_CIFRADO (or JWT_SECRET when it acts as the key), what was already encrypted can no longer be read.

CORS: who is allowed to talk to the store

CORS_TIENDA=https://mystore.com          # the storefront
CORS_PANEL=https://panel.mystore.com    # the panel

These are comma-separated lists. If an address is missing, the browser blocks the request and the error does not tell you this is why: it says "CORS", and you lose half an afternoon looking in the wrong place. When you stand up a new theme on another port, remember to add it to CORS_TIENDA. In Docker both come from TIENDA_URL and PANEL_URL.


What each thing turns on

Everything below is optional. Without the variable, the feature simply is not there — it does not fail, it does not warn, it does not get in the way.

Email

SMTP_HOST=
SMTP_PORT=587
SMTP_USER=
SMTP_PASS=
SMTP_FROM=
CONTACT_TO=          # mailbox notified of each contact-form message (it also lands in Panel → Support)

Without this there are no order confirmations and no password resets. The notices are not lost: they stay in the queue, the startup log says so, and they go out as soon as you set the mail server up.

Email going out is not the same as email arriving: the domain you write from has to authorise it, and that lives in its DNS. To see it:

pcc correo                  # the domain in SMTP_FROM
pcc correo mystore.com      # or one you name

It checks MX, SPF, DKIM and DMARC and says what is missing. It needs no database, so it works when the store will not start either. Without DKIM you cannot prove a message came from you — SPF breaks the moment anyone forwards it — and Gmail and Yahoo require it from anyone sending volume.

Artificial intelligence

ANTHROPIC_API_KEY=
OPENAI_API_KEY=
GOOGLE_API_KEY=
MISTRAL_API_KEY=
OPENROUTER_API_KEY=
VOYAGE_API_KEY=      # for meaning-based search only
COHERE_API_KEY=
OLLAMA_URL=http://localhost:11434   # on your own machine, at no cost

Set the key for the provider you want to use, not all of them. The provider itself is chosen afterwards in the panel, under AI.

The key is yours and so is the bill: the system ships with the brakes on (a daily cap and a per-visitor limit), but you are the one paying for the calls. How it works and what it costs: AI.

With no key at all the store works exactly the same — keyword search, product pages, alerts. What needs a key is what writes or reasons.

Shipping

Zones, rates and shipping classes do not go in the .env: you set them up in the panel, under Shipping. How, in set up shipping.

There is no Sendcloud integration that works out shipping costs or prints labels. All that exists is the route that receives their parcel status notifications, POST /hooks/sendcloud:

SENDCLOUD_WEBHOOK_SECRET=    # the signature on their notifications
# or, if it is not set, SENDCLOUD_SECRET_KEY is used

It checks the signature and writes the notification to the server log; it does not change the order. With neither secret set, it answers 503 to anything that arrives: an open route with no signature would be a door for anyone to write into your log.

If your .env carries SENDCLOUD_PUBLIC_KEY, SENDCLOUD_FROM_*, SENDCLOUD_DEFAULT_WEIGHT_KG or SENDCLOUD_FALLBACK_EUR, you can delete them: nothing reads them. Filling them in turns nothing on.

Themes and extensions

THEMES_DIR=      # where the themes live. Default: themes/
PLUGINS_DIR=     # where the plugins live. Default: plugins/ at the repository root; /plugins in Docker
PLUGINS_DISABLED=one,another    # turn one off without uninstalling it
LICENSE_PUBLIC_KEY=             # verify licences for paid plugins

LICENSE_PUBLIC_KEY is public on purpose: it can only verify signatures, never create them.

STOREFRONT_URL=https://mystore.com
STOREFRONT_RUTA_PRODUCTO=producto     # /producto/<handle>

These produce the links the AI and your emails send to customers. If your theme uses a different path — /p/, /item/ — change it here or the links will 404.


The storefront is configured separately

The theme does not read the backend .env: it is a different application, very often on a different server. It needs two values of its own:

COMMERCE_URL=https://api.mystore.com   # the backend's address
COMMERCE_KEY=pk_...                    # publishable key for the sales channel

When the panel runs a theme, it passes exactly these two. Each stack exposes them to the browser its own way: Next wants NEXT_PUBLIC_, Vite wants VITE_. The dulce-obrador factory theme, built with Vite, calls them VITE_PCC_BACKEND_URL and VITE_PCC_PUBLISHABLE_KEY. Check the theme's .env.example.

The publishable key decides which products that storefront can see: it is tied to a sales channel.

The backend creates one on its first start (preparar, which runs on every start in Docker and writes it to datos/clave-publicable.txt). There is no screen in the panel to create more: the panel reads the first active publishable key and hands it to every theme it runs.


What is configured in the panel

No files, no restart:

WhereWhat
SettingsStore name and branding; under Billing, the tax details for invoices; under Payments, payment methods and gateway keys
AIProvider and model, daily spending cap, what may be written unattended
ThemesWhich theme is active and its demo content
ExtensionsInstalled plugins and their settings
TeamWho gets into the panel

Tax details live in the store, not in the code: changing a tax ID should not require rebuilding anything. What is saved in Settings → Billing takes precedence over the COMPANY_* variables, which are only the fallback while nothing has been saved. If you rely on that fallback, the invoice prefix is INVOICE_PREFIX for the panel and the backend alike (the old COMPANY_INVOICE_PREFIX and NEXT_PUBLIC_INVOICE_PREFIX are still read).