// Expert Omeka S developer assistant for Docker-based installations. Covers theme development (SCSS/CSS, templates), module creation (PHP, events, database), configuration management, and troubleshooting. Use for Omeka S customization from simple CSS tweaks to advanced module development.
| name | omeka-s |
| description | Expert Omeka S developer assistant for Docker-based installations. Covers theme development (SCSS/CSS, templates), module creation (PHP, events, database), configuration management, and troubleshooting. Use for Omeka S customization from simple CSS tweaks to advanced module development. |
| license | MIT |
| category | api-integration |
Expert guidance for Docker-based Omeka S development - from simple CSS tweaks to advanced module development.
Docker-based installation - All operations use Docker commands:
omeka-s-app (application), omeka-s-db (database)/var/www/html/ inside containerdocker exec omeka-s-app cat /var/www/html/application/Module.php | grep VERSION# Read files
docker exec omeka-s-app cat /var/www/html/path/to/file
# List directories
docker exec omeka-s-app ls -la /var/www/html/themes/
# Copy files into container
docker cp local-file omeka-s-app:/var/www/html/path/
# Fix permissions after copying
docker exec omeka-s-app chown -R www-data:www-data /var/www/html/themes/
# Database access
docker exec -it omeka-s-db mysql -u omeka -p omeka
User Request → What type of work?
├─ Theme Customization
│ ├─ Simple CSS change?
│ │ └─ Edit theme/asset/css/style.css directly
│ │ (see references/theme-development.md → Quick CSS Editing)
│ │
│ ├─ Need SCSS/variables?
│ │ └─ Use SCSS build process
│ │ 1. Edit theme/asset/sass/*.scss
│ │ 2. Run: docker exec omeka-s-app bash -c "cd /var/www/html/themes/THEME && npm run build"
│ │ (see references/theme-development.md → SCSS Build Process)
│ │
│ └─ Template override?
│ └─ Copy from application/view/ to theme/view/
│ (see references/theme-development.md → Template Structure)
│
├─ Module Development
│ ├─ Creating new module?
│ │ └─ Follow module structure pattern
│ │ (see references/module-development.md → Module Structure)
│ │
│ ├─ Adding event listener?
│ │ └─ Register in Module.php attachListeners()
│ │ (see references/module-development.md → Event System)
│ │
│ └─ Database entities?
│ └─ Create Entity, update module.ini
│ (see references/module-development.md → Database Entities)
│
├─ Content Visibility Issues
│ └─ Items not showing?
│ 1. Check database relationships
│ 2. Verify is_public flags
│ 3. Confirm site assignments
│ (see references/database.md → Content Visibility)
│
└─ Configuration/Setup
└─ See references/troubleshooting.md
/var/www/html/
├── application/ # Core (DON'T MODIFY)
│ ├── view/ # Default templates (COPY to theme to override)
│ └── src/ # PHP source
├── config/
│ ├── database.ini
│ └── local.config.php
├── modules/ # Custom modules here
│ └── MyModule/
│ ├── Module.php
│ ├── module.ini
│ ├── src/
│ └── view/
├── themes/ # Custom themes here
│ └── MyTheme/
│ ├── theme.ini
│ ├── asset/
│ │ ├── css/
│ │ └── sass/
│ └── view/
└── volume/
└── files/ # Uploaded media
# Read theme file
docker exec omeka-s-app cat /var/www/html/themes/THEME/theme.ini
# Copy theme into container
docker cp ./my-theme omeka-s-app:/var/www/html/themes/
docker exec omeka-s-app chown -R www-data:www-data /var/www/html/themes/my-theme
# View logs
docker logs omeka-s-app
docker logs omeka-s-db
# Restart services
docker restart omeka-s-app
-- Make all items public
UPDATE resource SET is_public = 1 WHERE resource_type = 'Omeka\\Entity\\Item';
-- Assign all items to site 1
INSERT INTO item_site (item_id, site_id)
SELECT id, 1 FROM item
ON DUPLICATE KEY UPDATE site_id = VALUES(site_id);
-- Check item visibility
SELECT r.id, r.title, r.is_public, COUNT(is_i.item_id) as in_site
FROM resource r
LEFT JOIN item_site is_i ON r.id = is_i.item_id
WHERE r.resource_type = 'Omeka\\Entity\\Item'
GROUP BY r.id;
For complete database operations, see references/database.md
mkdir -p my-theme/{asset/{css,js,sass},view/common}
[info]
name = "My Theme"
version = "1.0"
author = "Your Name"
theme_link = ""
author_link = ""
description = "Custom theme"
application/view/ to theme/view/For complete theme guide, see references/theme-development.md
<?php
namespace MyModule;
use Omeka\Module\AbstractModule;
use Laminas\EventManager\SharedEventManagerInterface;
class Module extends AbstractModule
{
public function attachListeners(SharedEventManagerInterface $sharedEventManager)
{
// Add event listeners here
}
}
[info]
name = "My Module"
version = "1.0.0"
author = "Your Name"
description = "Module description"
For complete module guide, see references/module-development.md
theme/asset/sass/_base.scss variablesdocker exec omeka-s-app bash -c "cd /var/www/html/themes/THEME && npm run build"application/view/omeka/site/item/show.phtmltheme/view/omeka/site/item/show.phtml/var/www/html/modules/chown -R www-data:www-dataapplication/ directlyapplication/→ See references/database.md (Content Visibility section)
→ See references/theme-development.md (Troubleshooting Build Issues)
→ See references/troubleshooting.md
volume/files/→ See references/database.md (Media & Thumbnails)
Detailed documentation for each area:
Items need 4 conditions to be visible:
is_public = 1)item_site table)is_open = 1)site_item_set table)See references/database.md for complete details
Themes inherit from default theme:
application/view/asset/sass/See references/theme-development.md for template patterns
Modules extend Omeka via events:
api.search.query - Modify searchview.show.after - Add to page displayform.add_elements - Add form fieldsSee references/module-development.md for event list
For new Omeka S projects:
For detailed guidance on any topic, see the appropriate reference file above.
Newer MariaDB Docker images use different command names:
# OLD (deprecated)
docker exec omeka-s-db mysqladmin ping
docker exec omeka-s-db mysql -u user -p database
# NEW (current)
docker exec omeka-s-db mariadb-admin ping
docker exec omeka-s-db mariadb -u user -p database
Always use mariadb and mariadb-admin commands for compatibility.
Omeka S has two distinct types of block layouts that are registered differently:
block_layouts)Used for site pages (like Time Periods, About, etc.)
// In module.config.php
'block_layouts' => [
'invokables' => [
'timePeriodsGrid' => MyModule\Site\BlockLayout\TimePeriodsGrid::class,
],
],
resource_page_block_layouts)Used for item/media/item-set display pages
// In module.config.php
'resource_page_block_layouts' => [
'invokables' => [
'relatedItems' => MyModule\Site\ResourcePageBlockLayout\RelatedItems::class,
],
],
Key Difference: Site page blocks go in regular pages; resource page blocks appear when viewing an item.
NEVER use direct database connections in blocks. Use the API instead:
// ❌ WRONG - Will fail
$connection = $view->getHelperPluginManager()->get('api')->getManager()->getConnection();
// ✅ CORRECT - Use the API
$view->api()->search('items', [
'site_id' => $site->id(),
'property' => [[
'joiner' => 'and',
'property' => 'dcterms:coverage',
'type' => 'eq',
'text' => 'some value',
]],
]);
Getting unique property values dynamically:
// Get property first
$coverageProperty = $view->api()->searchOne('properties', [
'term' => 'dcterms:coverage',
])->getContent();
// Get all items with that property
$allItems = $view->api()->search('items', [
'site_id' => $site->id(),
'has_property' => [$coverageProperty->id()],
])->getContent();
// Extract unique values
$uniqueValues = [];
foreach ($allItems as $item) {
$values = $item->value('dcterms:coverage', ['all' => true]);
foreach ($values as $val) {
$textValue = (string)$val;
if (!in_array($textValue, $uniqueValues)) {
$uniqueValues[] = $textValue;
}
}
}
Symptoms:
Diagnosis:
# Check file version
docker exec omeka-s-app cat /var/www/html/modules/ModuleName/config/module.ini | grep version
# Check database version
docker exec omeka-s-db mariadb -u omekas -pomekas omekas -e "SELECT id, version FROM module WHERE id = 'ModuleName';"
Solution: Download the correct version from GitHub releases to match database expectations.
Full working example of a site page block:
<?php
namespace MyModule\Site\BlockLayout;
use Omeka\Api\Representation\SiteRepresentation;
use Omeka\Api\Representation\SitePageRepresentation;
use Omeka\Api\Representation\SitePageBlockRepresentation;
use Omeka\Site\BlockLayout\AbstractBlockLayout;
use Laminas\View\Renderer\PhpRenderer;
class MyCustomBlock extends AbstractBlockLayout
{
public function getLabel()
{
return 'My Custom Block'; // @translate
}
public function form(
PhpRenderer $view,
SiteRepresentation $site,
SitePageRepresentation $page = null,
SitePageBlockRepresentation $block = null
) {
// Get saved data
$data = $block ? $block->data() : [];
$myValue = $data['myValue'] ?? '';
// Return HTML form for admin
return sprintf(
'<div class="field"><label>My Setting</label><input type="text" name="o:block[__blockIndex__][o:data][myValue]" value="%s"></div>',
htmlspecialchars($myValue, ENT_QUOTES)
);
}
public function render(PhpRenderer $view, SitePageBlockRepresentation $block)
{
$site = $view->currentSite();
$data = $block->data();
// Query items dynamically
$items = $view->api()->search('items', [
'site_id' => $site->id(),
'limit' => 10,
])->getContent();
// Render template
return $view->partial('common/block-layout/my-custom-block', [
'items' => $items,
'data' => $data,
]);
}
}
Block configuration is stored as JSON in site_page_block.data:
-- View block data
SELECT id, layout, data FROM site_page_block WHERE page_id = 10;
-- Update block data (be careful with escaping!)
UPDATE site_page_block
SET data = '{"key": "value"}'
WHERE id = 54;
Important: Data must be valid JSON. Use proper escaping when updating via SQL.
// Search items by property value
$response = $view->api()->search('items', [
'site_id' => $site->id(),
'property' => [[
'joiner' => 'and', // 'and' or 'or'
'property' => 'dcterms:title', // property term
'type' => 'eq', // 'eq', 'neq', 'in', 'nin', 'ex', 'nex'
'text' => 'search term',
]],
]);
$count = $response->getTotalResults();
$items = $response->getContent();
Omeka S uses the CSSEditor module for custom CSS. It provides two methods:
css_editor_css) - Written directly in admin interfacecss_editor_external_css) - URLs to external CSS filesDatabase Storage:
-- Check current CSS configuration
SELECT id, value FROM site_setting WHERE id LIKE '%css%';
-- Example output:
css_editor_css (empty or contains CSS code)
css_editor_external_css ["/themes/foundation/asset/css/cdha-custom.css"]
CSS Loading Order (Cascade):
1. Core CSS (iconfonts.css, resource-page-blocks.css)
2. Module CSS (from installed modules)
3. Theme base CSS (default.css, inkwell.css, etc.)
4. Inline CSS from css_editor_css (via /s/SITE/css-editor endpoint)
5. External CSS from css_editor_external_css
6. Additional module CSS
Key Points:
Common Issue: CSS Conflicts
If CSS changes don't appear:
-- Clear conflicting inline CSS
UPDATE site_setting SET value = '' WHERE id = 'css_editor_css';
-- Check what external files are referenced
SELECT value FROM site_setting WHERE id = 'css_editor_external_css';
Best Practice:
Real-World Example:
css_editor_external_css: ["/themes/foundation/asset/css/cdha-custom.css"]
This tells Omeka to load cdha-custom.css after the base theme CSS but load it as a separate file (not copied into the inline editor).
Omeka S loads templates in this order (highest to lowest priority):
themes/THEME/view/) - HIGHEST PRIORITYmodules/MODULE/view/)application/view/) - LOWEST PRIORITY⚠️ This means theme templates override EVERYTHING, including module templates.
Real-World Example:
# You edit this module template:
modules/CdhaBlocks/view/common/resource-page-block-layout/filtered-values-main.phtml
# But Omeka actually uses this theme template (if it exists):
themes/foundation/view/common/resource-page-block-layout/filtered-values-main.phtml
# Result: Your module changes don't appear, causing confusion!
Debugging Template Issues:
When template changes don't appear:
# 1. Check if theme has override
find themes/ACTIVE_THEME -name "template-name.phtml"
# 2. If found, edit the THEME template, not the module template
# 3. Use error logging to confirm which file loads:
# Add to top of both templates:
<?php error_log('TEMPLATE: ' . __FILE__); ?>
Best Practice:
grep -r to find ALL instances of a template nameCommon Symptoms:
chown -R www-data:www-data after file changesdocker-compose restart omeka-s after code changes