spip-plugins
Use when developing a SPIP plugin (paquet.xml present, _pipelines.php, spip_* table references in PHP) or when the user asks about plugin architecture, pipelines, hooks, or the SPIP PHP/SQL API. Writt
SKILL.mdskills/spip-plugins/SKILL.md
GitHubSPIP plugin development
SPIP 5.0
This skill describes SPIP 4.x. Check the version first and name the marker you relied on. A site is 5.0 if any
of: ecrire/src/SpipVersion.php declares VERSION = '5.…', bin/spip exists, vendor/spip/framework/ exists,
plugins live in plugins-dist/spip/. A plugin targets 5.0 if its paquet.xml compatibilite reaches 5.x or
its composer.json requires spip/framework.
- Never read settings from
$GLOBALS['meta']in code that runs on 5.0: it is a raw mirror of the SQL string, and arrays are stored as JSON once rewritten (one save of aconfigurer_form is enough).unserialize($GLOBALS['meta']['acme'])then returnsfalseand the feature silently disappears. Read withlire_config('acme/cle')(4.4 and 5.0, not deprecated). Fix every such read in the files you touch, including an existing helper you reuse. - Unchanged in 5.0:
<pipeline>and<genie>inpaquet.xml,prefixe_pipeline()functions,affichage_final,$GLOBALS['html']. Plugin for 4.4 and 5.0: keep this model only. The 5.0-only model (listeners,config/services.php,#[AsCronTask]) is inspip5-plugins; never register one pipeline both ways. compatibilitemust accept the site's exact version: on5.0.0-beta,[5.0.0;5.*]refuses the plugin. Write[5.0.0-dev;5.*](5.0 only) or[4.4.0;5.*].
Details: spip-migration-5 (references/config-metas.md, references/double-compat.md,
references/plugin-modele-5.md).
SPIP-specific terms (always kept in original form)
| Term | Meaning |
|---|---|
| boucle | <BOUCLE_xxx(TABLE){critères}>...</BOUCLE_xxx> — template loop |
| squelette | Template file (.html with SPIP syntax) |
| rubrique | Hierarchical content section (SPIP's primary tree node) |
| paquet.xml | Plugin manifest — declares metadata, dependencies, pipelines, menus |
| pipeline | Extension hook — a named chain of functions called in sequence |
| balise | #TAG — template marker compiled to PHP |
| critère | {criteria} inside a boucle — constrains the SQL query |
| filtre | |function — post-processor applied to a value in a squelette |
| formulaire CVT | Charger/Vérifier/Traiter — SPIP's form pattern (load/validate/process) |
| objet éditorial | First-class content type with CRUD, i18n, revisions (article, rubrique…) |
| genie | SPIP scheduled task (cron), declared via <genie> in paquet.xml |
| action | Secured script entry point (?action=nom&arg=...&hash=...) |
| exec | Private-space page handler (?exec=nom) |
| inc | Helper function file, lazy-loaded via charger_fonction('nom', 'inc') |
| prefix | Short plugin identifier — prefixes all PHP filenames and function names |
Quick-start: minimal plugin skeleton
A SPIP plugin needs at minimum these files:
monplugin/
├── paquet.xml
├── monplugin_pipelines.php ← pipeline handlers
└── lang/
└── monplugin_fr.php ← reference language strings
paquet.xml (no schema, no DB):
<paquet
prefix="monplugin"
version="1.0.0"
compatibilite="[4.2.0;4.*]"
etat="dev"
categorie="divers">
<nom>Mon Plugin</nom>
<auteur>Prénom Nom</auteur>
<licence>GNU/GPL</licence>
<pipeline nom="mon_pipeline" inclure="monplugin_pipelines.php" />
<traduire module="monplugin" reference="fr" />
</paquet>
paquet.xml (with a database table):
<paquet prefix="monplugin" version="1.0.0" schema="1.0.0" ...>
<nom>Mon Plugin</nom>
<!-- declare pipeline handlers for table declarations -->
<pipeline nom="declarer_tables_objets_sql" inclure="base/monplugin.php" />
<pipeline nom="declarer_tables_interfaces" inclure="base/monplugin.php" />
</paquet>
The schema= attribute alone activates installation/upgrade support. SPIP looks for
{prefix}_administrations.php at the plugin root by naming convention — it must
exist and contain {prefix}_upgrade() and {prefix}_vider_tables(). There is no
<install> tag in the paquet.xml DTD (it was removed when plugin.xml was deprecated).
monplugin_pipelines.php:
<?php
if (!defined('_ECRIRE_INC_VERSION')) { return; }
function monplugin_mon_pipeline($flux) {
// modify $flux['data'], return $flux
return $flux;
}
Decision tree — I want to…
| Goal | Read |
|---|---|
Create a plugin manifest (paquet.xml) |
references/paquet-xml.md |
| Understand the directory layout and file loading order | references/arborescence.md |
| Hook into SPIP events (content published, user logged in, etc.) | references/pipelines.md |
| Add a contact/subscription/edit form | ../spip-formulaires/references/cvt-formulaires.md |
Write a secured action script (action/) |
references/actions.md |
| Check or grant permissions in PHP or squelettes | references/autorisations.md |
| Read or write database rows | references/sql-api.md |
| Create a new database table (not an objet éditorial) | references/declarer-table.md |
| Create a new objet éditorial (with statut, edition form, search) | references/declarer-objet.md |
Create public object URLs / custom #URL_* tags |
references/urls-objets.md |
Define a custom #BALISE, |filtre, or {critère} |
references/balises-filtres-criteres.md |
| Add translated strings to a plugin | references/i18n.md |
Write install/upgrade/uninstall logic (_administrations.php) |
references/cycle-de-vie.md |
| Build or customise private-space templates (liste, infos, contenu, editer) | references/prive-objets.md |
Understand what ?exec=mon_objets / ?exec=mon_objet provides out-of-the-box |
references/exec-generique.md |
| Apply SPIP coding standards (SCS1) | references/coding-standards.md |
| Configure composer.json, ecs.php or rector.php | references/coding-standards.md |
| Control page cache duration, disable caching, or trigger invalidation | references/cache.md |
| Write PHPUnit tests for my plugin (filtres, autorisations, squelettes) | invoke the spip-testing skill |
Support content in multiple languages (lang, id_trad, translation groups) |
references/multilinguisme.md |
| Run work asynchronously (background jobs, scheduled tasks) | references/queue-jobs.md |
| Add chunked file upload (bigup) to a formulaire CVT | references/upload-bigup.md |
| Create a plugin configuration page and read/write settings | references/config.md |
Workflow index
Starting a new plugin
references/paquet-xml.md→ write the manifestreferences/arborescence.md→ choose the right file for each functionreferences/pipelines.md→ pick which pipeline(s) to plug intoreferences/coding-standards.md→ configure composer.json, ecs.php or rector.php (optional but recommended)
Adding a database table
references/declarer-table.md→ declare schema viadeclarer_tables_principalesordeclarer_tables_objets_sqlreferences/cycle-de-vie.md→ write_administrations.phpwithmaj_tables()references/sql-api.md→ read/write the table at runtime
Building a form
../spip-formulaires/references/cvt-formulaires.md→ implementcharger,verifier,traiterreferences/sql-api.md→ write data intraiter()references/pipelines.md→formulaire_charger/formulaire_verifier/formulaire_traiterif you need to hook into other plugins' forms
Creating an objet éditorial
references/declarer-objet.md→ fulldeclarer_tables_objets_sqldescriptorreferences/declarer-table.md→ adddeclarer_tables_interfacesfor compiler configreferences/cycle-de-vie.md→ install/uninstall the table../spip-formulaires/references/cvt-formulaires.md→ useformulaires_editer_objet_charger/verifier/traiter()references/pipelines.md→pre_edition,post_editionto react to changes
Creating public URLs for a custom object
references/urls-objets.md→#URL_<TYPE>,declarer_url_objets, routing expectationsreferences/declarer-objet.md→ descriptor keystype,page,titrereferences/declarer-table.md→declarer_tables_interfaces(table_titre) for meaningful URLs
Extending squelettes
references/balises-filtres-criteres.md→ add#BALISE,|filtre, or{critère}references/declarer-table.md→declarer_tables_interfacesfortable_des_traitements
Adding secured actions
references/actions.md→ writeaction_nom_dist(), usesecuriser_action, checkautoriserreferences/autorisations.md→ define customautoriser_functions
Restricting access
references/autorisations.md→autoriser()API, lookup chain, writingautoriser_X_Y_dist()
Building the private-space UI for an objet éditorial
references/exec-generique.md→ what?exec=mon_objets/?exec=mon_objetprovides for freereferences/prive-objets.md→ createliste/,infos/,contenu/fragments as needed
Adding file upload to a form
references/upload-bigup.md→ enable bigup via_bigup_rechercher_fichiersincharger()../spip-formulaires/references/cvt-formulaires.md→verifier()andtraiter()with$_FILES
Running background work
references/queue-jobs.md→queue_add_job(),spip_jobs, priority, deduplicationreferences/paquet-xml.md→<genie>for periodic tasks
Testing a plugin
→ invoke the spip-testing skill
Supporting multiple languages in an objet
references/multilinguisme.md→lang,id_trad,action_referencer_traduction_dist()references/declarer-objet.md→titreaccessor must includelang
Source of truth
All examples in this skill are extracted from the SPIP 4.4.15 sources:
- SPIP core:
ecrire/ - plugins-dist (the 20 reference plugins shipped with SPIP):
plugins-dist/ - coding standards reference:
references/coding-standards.md
Paths inside the references are given relative to the SPIP root,
e.g. plugins-dist/forum/paquet.xml.
When in doubt, read the source.