Payload è un CMS headless open source pensato per lo stack Next.js/React. A differenza di piattaforme dove lo schema si configura quasi tutto da interfaccia grafica, in Payload collection, campi, permessi e logica di business si definiscono in TypeScript, nello stesso repository dell’applicazione.
Non si tratta di un semplice CMS: Payload si presenta come backend per il web moderno con contenuti, autenticazione, upload, API, job queue e un pannello admin scritto in React ed estendibile. Essendo open source e self-hosted, i dati restano nel tuo database Postgre, senza vincolo a un SaaS chiuso.
Per chi sviluppa in TypeScript su Next.js, il vantaggio principale è ridurre il divario tra “CMS per redattori” e applicazione con regole complesse. Non è necessario adattare un prodotto generico con plugin fragili: modelli e comportamento vivono nel codice, versionati con Git, testabili e tipizzati.
In questo articolo il nostro Front-end developer, Gianluca La Rosa, ci parla di Payload CMS tramite un progetto da lui realizzato nel quale ha potuto testare le funzionalità di questo CMS per realizzare un sito e un’applicazione gestionale che condividono la stessa source of truth.
Un solo schema, più applicazioni
In molti progetti il CMS serve solo al sito vetrina. In realtà Payload può diventare il layer dati condiviso di più app Next.js che parlano con lo stesso database.
Nel progetto da cui ho estrapolato questi esempi lo schema vive in un package condiviso importato da tre applicazioni distinte. Il sito pubblico consuma i contenuti; l’admin Payload gira accanto al frontend; un’app interna su sottodominio dedicato gestisce operazioni che non devono finire nel pannello di gestione dell’associazione.
Tutte le app ottengono la stessa istanza Payload con poche righe:
import configPromise from '@payload-config'
import { getPayload } from 'payload'
export async function getPayloadClient() {
return getPayload({ config: configPromise })
}
Niente API REST custom da mantenere in parallelo: Server Components e Server Actions chiamano direttamente payload.find(),payload.update() e così via. Un solo schema, un solo set di migrazioni, tipi generati condivisi.
Schema nel codice, non in un pannello
Le collection si definiscono come oggetti TypeScript: slug, campi, hook, permessi, label admin. Dopo ogni modifica, Payload genera un file payload-types.ts allineato al database che fornisce autocomplete e type-checking su tutto il progetto.
Per PostgreSQL conviene disattivare lo “schema push” automatico e affidarsi alle migrazioni versionate:
db: vercelPostgresAdapter({
pool: { connectionString: process.env.POSTGRES_URL },
push: false, // the schema evolves only with payload migrate
}),
Su un progetto che vive mesi o anni, questo è importante: ogni cambiamento passa da una migration SQL revisionabile, applicata in CI e produzione con lo stesso comando. Non ci si affida a un sync implicito che in dev “sistema tutto” ma in produzione sorprende.
Permessi flessibili, oltre admin ed editor
Molti CMS offrono pochi ruoli fissi. Payload espone funzioni access su ogni operazione CRUD, con due modalità di risposta.
Permesso binario, cioè true o false, su tutta la collection:
export const canCreatePosts: Access = ({ req: { user } }) => {
return user?.roles?.includes('editor') ?? false
}
Oppure query constraint: sì, ma solo su certi documenti. La funzione restituisce un filtro where, come in una query:
export const canReadPosts: Access = ({ req: { user } }) => {
if (user?.roles?.includes('admin')) return true
// Editors only see their own articles
return { author: { equals: user?.id } }
}
Payload non si limita a filtrare i dati in memoria tramite Node.js, ma traduce i vincoli di accesso direttamente in clausole SQL WHERE prima di interrogare il database. Questo garantisce massime performance sfruttando gli indici nativi ed elimina alla radice le vulnerabilità di sicurezza: se un utente non ha i permessi per vedere un determinato record, per il database quel documento semplicemente non esiste.
Nel progetto di riferimento questo pattern è spinto più avanti: ruoli “specialist” per ambiti editoriali diversi (eventi, cataloghi giochi, …), visibilità sidebar differenziata, permessi sui file media legati a uno scope specifico. Questo ha permesso, isolando gli ambiti di competenza di ogni user, di rendere la gestione anche più chiara.
Hook: comportamento vicino ai dati
Gli hook (beforeChange, afterChange, afterRead, …) sono il punto in cui Payload distingue da CMS più contenutistici. Puoi eseguire logica ogni volta che un documento viene letto, creato o modificato, da admin, da REST o da Local API.
Due casi d’uso tipici, entrambi presenti nel progetto:
- Campi derivati: valori calcolati da altri campi e dalla data corrente, aggiornati automaticamente invece che editati a mano.
- Effetti collaterali: invalidare la cache Next.js quando un contenuto viene pubblicato, sincronizzare record collegati, inviare notifiche.
Un hook di revalidation, semplificato:
export const revalidatePost: CollectionAfterChangeHook<Post> = ({
doc,
req: { context }
}) => {
if (context.disableRevalidate) return doc
if (doc._status === 'published') {
revalidatePath(`/articoli/${doc.slug}`)
revalidateTag('posts-sitemap')
return doc
}
}
In un caso specifico, l’utente anziché compilare il form di una collection per aggiungere un elemento, inserisce un url, da cui parte una fetch che riempie tutti i campi, risparmiando molto tempo.
Admin estendibile
Essendo scritto interamente in React, il pannello admin permette di sostituire o affiancare qualsiasi elemento di serie (campi, viste intere, link nella sidebar) indicando il proprio componente nel file di configurazione. Ad esempio, è possibile definire un campo di tipo ui che sfrutta gli hook integrati come useAllFormFields per leggere i dati del modulo e mostrare un riepilogo calcolato all’istante mentre l’operatore scrive:
{
name: 'paymentPreview',
type: 'ui',
admin: {
components: {
Field: '@/components/admin/PaymentPreview#PaymentPreview',
},
},
}
Questo approccio permette di creare interfacce dinamiche e su misura senza ricorrere a iframe o plugin esterni. Inoltre, la funzione di Live Preview nativa affianca l’anteprima del frontend direttamente all’editor, consentendo di testare i vari breakpoint mobile, tablet e desktop durante la modifica.
Multilingua integrata nel CMS
Payload gestisce le traduzioni a livello di campo (localized: true), con locale di default e fallback. Le lingue supportate si dichiarano in config:
localization: {
locales: [
{ code: 'it', label: 'Italiano' },
{ code: 'en', label: 'English' },
],
defaultLocale: 'it',
fallback: true,
},
Definire le traduzioni nel CMS garantisce l’integrità del modello dati, poiché la lingua esiste nello schema prima ancora che il sito la mostri. Aggiungere una lingua richiede una migrazione controllata su Postgres, evitando i tipici disallineamenti dei file JSON statici. In questo modo, il frontend si occupa solo delle traduzioni dell’interfaccia (pulsanti, label fisse) e richiede a Payload i contenuti dinamici già filtrati all’origine per la lingua corretta.
Local API: server-side queries without HTTP
Il sito pubblico non scarica contenuti via REST dal browser. I Server Component interrogano Payload lato server:
const { docs } = await payload.find({
collection: 'events',
where: { _status: { equals: 'published' } },
overrideAccess: false,
locale: 'it',
sort: '-date',
})
Utilizzando la Local API nei Server Components, Payload comunica direttamente via codice Node.js, escludendo chiamate HTTP e l’esposizione di chiavi segrete o token nel browser. Per evitare il sovraccarico di metadati o la visualizzazione di dati sensibili nascosti nei documenti grezzi, la best practice prevede di mappare i risultati in DTO snelli, passando ai Client Component solo le proprietà strettamente necessarie al rendering.
Plugin e infrastruttura
I plugin ufficiali si integrano direttamente nel file di configurazione, estendendo in modo nativo collezioni, tipi e migrazioni. Grazie alla libreria Sharp integrata, il ridimensionamento e la compressione delle immagini avvengono sul server durante l’upload. Infine, la job queue e il sistema di cron nativi consentono di gestire compiti pesanti in background usando lo stesso database dell’applicazione, senza dover configurare infrastrutture complesse.
Non è l’ecosistema di WordPress, ma copre le esigenze tipiche; il resto si estende con hook e componenti, nello stesso linguaggio del resto del progetto.
Pros and Cons
Perché Payload
- TypeScript end-to-end — schema, tipi, frontend, admin
- Controllo totale — self-hosted, open source, niente vendor lock-in sui dati
- Next.js nativo — admin, API e sito nello stesso monorepo
- Estensibilità — hook, access e UI admin senza limiti artificiali
- Performance — query dirette al DB, niente GraphQL obbligatorio
Limiti da considerare
- Curva iniziale — serve competenza TypeScript/React; non è “installa e dimentica”
- Admin per utenti occasionali — meno immediato di WordPress per chi entra raramente
- Ecosistema — meno temi e plugin di terze parti
- Operatività — database, migrazioni e storage li gestisci tu (a differenza di Contentful o Sanity)
- Evoluzione rapida — gli aggiornamenti major vanno pianificati
Conclusione
Payload non è lo strumento da scegliere per un semplice blog pronto in cinque minuti. Diventa invece la soluzione ideale quando il CMS deve integrarsi profondamente con l’applicazione, agendo come un vero e proprio backend flessibile. È perfetto quando serve condividere lo stesso schema tra più app, gestire permessi granulari, inserire la logica di business direttamente negli hook o personalizzare l’interfaccia admin senza dover ricostruire un backoffice da zero.
In contesti reali e complessi – come un ecosistema che unisce un sito multilingua, cataloghi strutturati, aree riservate e la gestione degli utenti – Payload garantisce che il modello dei dati e i comportamenti rimangano sempre coerenti tra il sito pubblico, il pannello di controllo e le applicazioni interne. C’è sicuramente un investimento iniziale maggiore in termini di sviluppo e configurazione rispetto a un CMS tradizionale o a una piattaforma SaaS pronta all’uso. Tuttavia, questo sforzo viene ampiamente ripagato nel tempo: si ottiene un’architettura robusta, pulita e scalabile, dove esiste un’unica e solida fonte di verità per tutti i dati del progetto.
Scopri di più sui nostri servizi di front-end engineering e contattaci per conoscere i nostri esperti
Autore: Gianluca La Rosa, Front-end developer @ Bitrock