Guides
Media and inline editing
Configure rich content and manage files and image presentation.
Rich content fields: end-to-end
This section shows the full flow for an editable HTML field: resource definition → template output → inline admin editing.
Step 1: Define the field in .meta
"intro": {
"name": "Intro",
"type": "html",
"buttons": "bold,italic",
"admin_col": false,
"i18n": true
},
"body": {
"name": "Body",
"type": "html",
"buttons": "h2,h3,h4,bold,italic,orderedlist,unorderedlist,quote,anchor",
"media": true,
"media_sizes": "sm-90,md-70,lg-60,xl-50,xxl-40",
"admin_col": false,
"i18n": true
}
buttonsis mandatory - always specify the exact toolbar buttons neededmedia: trueenables the media browser; requiresmedia_sizesadmin_col: falseis mandatory for html fields (too large for table view)- Add
i18n: trueif the site is multi-language
Step 2: Load the record in route.inc
<?php
router_deny();
$parts = router_match(__FILE__);
if ($parts === false) return;
load_library('data');
$record = data_read('articles', $parts[0]);
if (!$record) return;
set_variable('article', $record);
router_accept();
Step 3: Output in the template
Use [#get-html#] to output an editable html field. When a logged-in admin views the page, the field becomes inline-editable directly in the frontend.
<div class="prose">
[#get-html article.intro#]
</div>
<div class="prose prose-lg">
[#get-html article.body#]
</div>
For multi-language content, [#get-html#] resolves i18n field objects automatically. Add a language suffix when you explicitly need one language:
[#get-html article.body#]
[#get-html article.body.nl#]
Step 4: Enable inline editing
Add [#html#] to the route and ensure the user module is active (auto-loaded). Logged-in editors (edit-inline-content) then get the page actions pill: Edit switches inline editing on and off (asking to save or discard unsaved changes), and the settings button opens page settings or the record's admin page (page-settings-link). The pill can be dragged anywhere and docks in a corner when dropped near one. While editing, the focused field shows its field bar with Save.
Inline editing attributes
For finer control over the inline editor (e.g. different toolbar than the .meta default, or to enable media on a field that doesn't have it in .meta), add data-nb-edit directly to a wrapper element:
<div data-nb-edit="event.[#record.uuid#].main_text" data-nb-edit-options='{
"buttons":"h2,h3,bold,italic,anchor",
"media": true,
"media_sizes":"md-70,lg-60"}'>
[#get-html record.main_text#]
</div>
For inline image replacement, use data-nb-edit-img on the image wrapper:
<div data-nb-edit-img="event.[#record.uuid#].main_img">
[#get-img-html [#record.main_img#] sizes="xs-100,md-50"#]
</div>
Both attributes only activate for logged-in admins. The value format is resource.uuid.field.
File upload
Files are uploaded to the special .files resource as multipart/form-data:
curl -X POST "/api/v1/.files" \
-H "Authorization: Bearer TOKEN" \
-F "file=@photo.jpg"
Response includes the file UUID, which is the MD5 checksum of the file content:
{
"files": {
"uuid": "44556fd2a0d9463b6506e6e101e20bfe",
"name": "photo.jpg",
"type": "image/jpeg",
"size": 2048
},
"message": "RESOURCE_CREATED",
"code": 201
}
Re-uploading an identical file returns RESOURCE_CREATED with the same UUID - the upload is idempotent by checksum.
File metadata: .files_meta
Every uploaded file has a corresponding metadata record in .files_meta/{uuid}. This is a separate resource from .files (which stores the raw binary). The metadata record contains:
| Field | Description |
|---|---|
uuid |
File UUID - MD5 checksum of the file content |
name |
Original filename (e.g. KH-Menukaart.pdf) |
title |
Admin-set display title (e.g. KetelHuis Menukaart) - may be empty |
type |
MIME type (e.g. application/pdf, image/jpeg) |
size |
File size in bytes |
When displaying a file reference stored as a UUID (e.g. in page settings or a resource field), always read the display name and type from .files_meta, not from the UUID itself. In templates, nb.media_library.unfiltered contains the full .files_meta records loaded at page init - use this for client-side display without extra API calls.
Prefer title over name as the user-facing display label; fall back to name when title is empty.
Image serving
Uploaded images are served at /img/{uuid}/{spec}:
| Spec | Example | Description |
|---|---|---|
{W}x{H}f |
300x300f |
Fit into box, aspect ratio preserved |
{W}x{H}c |
300x200c |
Crop from center to exact dimensions |
{W}w |
300w |
Resize to width |
{H}h |
300h |
Resize to height |
/img/44556fd2a0d9463b6506e6e101e20bfe/500x500c
Images are served as WebP. Upscaling is not allowed.