Architecture
Database schema
Extension data models are declared in JSON files. The core schema updater reads these definitions and automatically creates database tables.
File location and naming
Schema files live in src/config/objects/ inside each extension. Use singular names:
article.json— notarticles.jsoncampaign.json— notcampaigns.jsonrecipient.json— notrecipients.json
Schema format (infoKeys / metaKeys)
Every schema uses two key arrays: infoKeys for database columns and
metaKeys for flexible schemaless data stored in a separate meta table.
{
"infoKeys": [
{
"slug": "id",
"type": "int(11)",
"permissions": {
"read": true,
"update": ["PAPI_is_super_admin"]
}
},
{
"slug": "organization_ID",
"type": "int(11)",
"index": true,
"permissions": {
"read": true,
"update": ["PAPI_is_super_admin"]
}
},
{
"slug": "label",
"type": "varchar(250)",
"permissions": {
"read": true,
"update": ["PAPI_is_super_admin"]
}
},
{
"slug": "slug",
"type": "varchar(150)",
"index": true,
"permissions": {
"read": true,
"update": ["PAPI_is_super_admin"]
}
},
{
"slug": "status",
"type": "varchar(50)",
"permissions": {
"read": true,
"update": ["PAPI_is_super_admin"]
}
}
],
"metaKeys": [],
"createForm": {
"fields": [
{
"type": "text",
"label": "Name",
"slug": "label",
"placeholder": "Enter name...",
"value": "",
"required": true
}
]
},
"updateForm": false,
"permissions": {
"create": ["PAPI_is_super_admin"],
"read": true,
"update": ["PAPI_is_super_admin"],
"delete": ["PAPI_is_super_admin"]
}
}
SQL field types
| SQL Type | Use For |
|---|---|
int(11) |
IDs, foreign keys, counts, integer values |
varchar(N) |
Short strings (names, slugs, statuses). N = max length |
text |
Medium text, JSON data, descriptions |
longtext |
Large content (HTML, rich text, large JSON) |
date |
Date only (YYYY-MM-DD) |
datetime |
Date and time |
time |
Time only (HH:MM:SS) |
json |
Structured JSON data (typically in metaKeys) |
decimal(10,2) |
Currency, precise numbers |
tinyint(1) |
Boolean flags |
Common field patterns
Every object should have:
id—int(11), always first.status—varchar(50), lifecycle tracking (active, draft, archived, deleted).
If organization-scoped:
organization_ID—int(11), indexed.
If user-owned:
user_ID—int(11), indexed.
Other common fields:
label/name—varchar(250), human-readable name.slug—varchar(150), indexed, URL-friendly identifier.description—text, longer description.config—text, JSON configuration (auto-decoded by PAPI helpers).sort_order—int(11), for manual ordering.created_at/updated_at—datetime.
Foreign keys to other objects use the <parent_object>_ID pattern with int(11) and "index": true.
Permissions
Permissions control access at the field and object level:
true— public access, no restrictions.["PAPI_is_super_admin"]— restricted to super admins.- An array of permission function names for custom access control.
Object-level permissions use the same format in the top-level permissions key with create, read, update, and delete operations.
MetaKeys
MetaKeys store flexible, schemaless data in a separate _meta table. Use them for data
that does not need direct SQL queries.
"metaKeys": [
{
"slug": "settings",
"type": "json",
"permissions": {
"read": true,
"update": ["PAPI_is_super_admin"]
}
}
]
Use "slug": "*" to allow arbitrary meta keys:
"metaKeys": [
{
"slug": "*",
"type": "text",
"permissions": {
"read": true,
"update": ["PAPI_is_super_admin"]
}
}
]
Table naming
Tables are auto-created as {DB_PREFIX}_{extension_slug}_{object_slug}.
Meta tables use {DB_PREFIX}_{extension_slug}_{object_slug}_meta.
Access data via PAPI_get_object('{ext}:{object}', ...) and
PAPI_get_objects('{ext}:{object}', ...). See
PHP conventions for full query syntax.