omeka-s/SKILL.md
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.
npx skillsauth add szweibel/claude-skills omeka-sInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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 changesdevelopment
Comprehensive spreadsheet creation, editing, and analysis with support for formulas, formatting, data analysis, and visualization. When Claude needs to work with spreadsheets (.xlsx, .xlsm, .csv, .tsv, etc) for: (1) Creating new spreadsheets with formulas and formatting, (2) Reading or analyzing data, (3) Modify existing spreadsheets while preserving formulas, (4) Data analysis and visualization in spreadsheets, or (5) Recalculating formulas
development
Use OCLC WorldCat APIs to search for books and scholarly materials, retrieve bibliographic metadata, check library holdings worldwide, and get classification data. Use when working with ISBNs, DOIs, OCLC numbers, library catalogs, or institutional holdings.
tools
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
development
Complete guide for Svelte 5 runes ($state, $derived, $effect, $props, $bindable). Use for any Svelte 5 project or when code contains $ prefixed runes. Essential for reactive state management, computed values, side effects, and component props. Covers migration from Svelte 4 reactive statements.