Mastering MediaWiki Cargo Extension for Structured Data
The working Cargo pattern: declare tables, store from templates, query with #cargo_query — types, formats, `where` clauses and performance habits.
Cargo gives MediaWiki database-style tables with querying, stored and managed entirely from wiki pages. It is the straightforward choice for records — inventories, items, people — where Semantic MediaWiki would be overkill. This is the working pattern: declare, store, query, with the details the quickstarts skip. (Installation is one Composer line; the extension page covers it.)
1. Declare the table
A template page carries the declaration — #cargo_declare runs when the template is saved and (re)creates the table:
Template:Books
<includeonly>{{#cargo_declare:_table=Books
|Title=String
|Author=String
|PublicationYear=Date
|Pages=Integer
|Cover=File
}}{{#cargo_store:_table=Books
|Title={{{title|{{PAGENAME}}}}}
|Author={{{author|}}}
|PublicationYear={{{year|}}}
|Pages={{{pages|}}}
|Cover={{{cover|}}}
}}</includeonly>
Field types include String, Integer, Float, Date, Datetime, Boolean, Page, File, Coordinates, Wikitext, Wikitext string, Searchtext, URL and Email. #cargo_store pushes the values into the table on every save. Field names keep type consistency: changing an Integer to String requires re-running the maintenance script for table alteration.
2. Query with #cargo_query
{{#cargo_query:
|tables=Books
|fields=Title,Author,PublicationYear,Pages
|where=PublicationYear>=2000 AND Pages>300
|order by=PublicationYear DESC
|format=table
|limit=20
}}
The SQL-flavored where accepts =, !=, >=, LIKE, IN (...), HOLDS (list fields) and boolean combinators. Key parameters:
tables=— multiple tables with join conditions:tables=Books,Reviews|Books._ID=Reviews.Bookgroup by=,having=— aggregates (fields=COUNT(*),SUM(Pages))format=table|list|template|count|dynamic table|timeline|calendar— output shape;templaterenders rows through a templatedefault=— message when the query finds nothing
3. Data entry: forms and templates
Cargo tables are populated through the templates that contain #cargo_store. Pair it with Page Forms so editors never touch the store call: a form with fields bound to the template parameters (autocomplete on Author keeps names consistent). Page Forms' field types map onto Cargo types, and validation happens at the form level (e.g. input type=date for Date fields).
4. Presentation and navigation
format=dynamic table— client-side filterable/sortable table from a small result setformat=templatewithintro=/outro=— full layout control (cards, grids)format=count— dashboard numbers- Geodata:
Coordinatesfields +format=map(Leaflet/OpenLayers) for location records
5. Performance habits
- Index what you filter — Cargo creates an index on the template's declared fields; heavy
where/order bycolumns beyond that can be indexed via the extension's maintenance functions — but usually, restraint in field count keeps tables fast - Limit the rows —
limit=on every user-facing query; full-table scans in#cargo_queryare a page-load killer - Store what you query — avoid query-time computation; prestore derived columns (e.g. normalized dates) at
#cargo_storetime - Delete discipline — deleting a page does not delete its Cargo rows automatically unless the deletion hook runs; re-run the relevant maintenance for orphaned rows if records linger
6. The pattern in practice
One template per record type (declaration + store), forms for entry, list pages with #cargo_query, and templates for rich output. Cargo's strength is that the whole data layer lives on the wiki: schema, data and reports are all pages, versionable like everything else. The extension documentation documents every field type and parameter.