HR Hanse Holding manages properties in Hamburg and maintains their portfolio in onOffice enterprise. The listings were supposed to appear on their WordPress website – with custom design, filters, and working detail pages. Multiple agencies had tried. None succeeded. On the website: zero listings.
In this article, I show what went wrong and what I did differently on the technical side: from API configuration to custom templates in templates.dist/, to the onOffice Template Guard that automatically restores customisations after plugin updates.
Starting Point: Zero Listings Online
Hanse Holding had already hired multiple service providers for the onOffice integration. The symptoms were always the same: The onOffice plugin for WordPress was installed, the API key entered – but no properties appeared on the website. Zero results in the list view. Clicking on a detail page returned a 404 error. The plugin's default layout didn't match the corporate design anyway.
The typical reaction: "The plugin doesn't work." In reality, the plugin works fine – the configuration around it needs to be correct.
onOffice enterprise is the market leader among real estate CRMs in the German-speaking region: over 30,000 users across more than 6,000 companies. The WordPress plugin is the official bridge between CRM and website – but the integration is anything but plug-and-play.
Debugging: API Permissions and Permalink Routing
The diagnosis was quicker than expected. Two causes, both classic:
Problem 1: Wrong API permissions. The API user in onOffice had the permission "Read properties" set to "own only" instead of "all". Sounds like a small toggle, but the impact is massive: the API only returns properties that are personally assigned to the API user. A technical API account typically has zero properties assigned – so the API returns an empty list. The plugin dutifully displays what it receives: nothing.
// API response with wrong permissions ("own only"):
{
"status": { "code": 200 },
"data": {
"records": [], // ← empty, not an error
"countabsolute": 0
}
}
// API response with correct permissions ("all"):
{
"status": { "code": 200 },
"data": {
"records": [ ... ], // ← 22 properties
"countabsolute": 22
}
}
The tricky part: the API doesn't return an error. Status 200, empty list. If you don't know where the toggle is, you end up searching for the bug in the plugin instead of in the onOffice admin panel.
Problem 2: Permalink structure breaks routing. The onOffice plugin registers its own rewrite rules for detail pages. These rules only work when the WordPress permalink structure is set to "Post name" (/%postname%/). With any other setting – "Plain", "Day and name", "Numeric" – the plugin's routing logic doesn't kick in, and detail page links lead to a 404.
If the list view works but clicking on individual properties returns a 404: check permalinks. Under Settings → Permalinks, "Post name" must be selected. Then click "Save Changes" once so WordPress regenerates the rewrite rules – even if nothing appears to have changed.
Once both problems were fixed, synchronisation was working. 22 properties appeared in the list view, detail pages were reachable. But the plugin's default design didn't match Hanse Holding's corporate design – and that was the actually time-consuming part of the project.
Custom Templates in templates.dist
The onOffice plugin loads its templates from the templates.dist/ directory inside the plugin folder. Custom template files are placed there alongside the default templates and selected as active templates via the onOffice plugin settings. The problem: during a plugin update, the entire plugin folder is replaced – including templates.dist/. Without safeguards, the custom templates are gone after the update.
That's exactly what had happened with the previous service providers. Many developers alternatively work with CSS overrides that depend on the plugin's HTML structure and break when the layout changes. The clean approach: develop custom templates and secure them with a recovery mechanism (more on that shortly).
For Hanse Holding, I developed custom templates:
- List view (
cardgrid.php): 3-column card grid with location filter (TomSelect multiselect), pagination, and integrated card rendering - Detail page (
custom_detail.php): Lightbox gallery with arrow navigation, dynamic highlight badges, key data, OpenStreetMap map, sticky contact person box - CSS (
onoffice-style.css): Complete custom stylesheet intemplates.dist/overriding the plugin styles
wp-content/plugins/
├── onoffice-for-wp-websites/ ← main plugin (don't touch)
│ └── templates.dist/
│ ├── estate/
│ │ ├── default_list.php
│ │ ├── default_detail.php
│ │ ├── cardgrid.php ← custom list view + cards
│ │ └── custom_detail.php ← custom detail page
│ └── onoffice-style.css ← custom stylesheet
│
└── onoffice-template-guard/ ← recovery plugin (HafenPixel)
└── onoffice-template-guard.php ← restores templates after updates
The custom templates are found by the plugin's internal template loader and configured as active templates in the onOffice settings. The Template Guard (see next section) ensures they are automatically restored after plugin updates.
List View: Card Grid with Location Filter
The onOffice plugin's default list view shows properties as a simple text list. For Hanse Holding, I built a 3-column card grid (cardgrid.php) that responsively collapses to 2 columns (tablet) and 1 column (mobile).
Each card shows: main image with hover zoom, marketing type badge (e.g. orange "KAUF" pill), property title, meta line with property type and location, fact boxes for room count and living area, the price (orange, bold), and a "View details" button.
Above the grid sits a location filter as a multiselect dropdown (via TomSelect). The filter uses the onOffice plugin's built-in filter logic – no custom JavaScript for data queries, no custom database queries. Results are paginated with classic page numbers via paginate_links().
Detail Page: Gallery, Key Data, Map
The detail page is the most complex part of the integration. Default: a page with free-text description and a few fields. Custom: a complete exposé presentation with multiple sections.
The custom detail page structure:
- Image gallery with lightbox: Click on the main image opens a fullscreen overlay with arrow navigation, keyboard support (ESC, arrow keys), image counter, and background click to close
- Dynamic highlight badges: All highlight fields maintained in onOffice (balcony, elevator, parking, etc.) are automatically read via
getFieldLabel()and displayed as visual tags - Key data: Dynamically sourced from the onOffice field configuration – standard fields like price, living area, plot size, room count, and all activated additional fields
- Property description: Free text from onOffice with clean typography styling
- Location map: OpenStreetMap via Leaflet.js (GDPR-compliant) – coordinates directly from the onOffice dataset. Plus an "Open in Google Maps" button for navigation
- Contact person box: Round avatar photo, name, phone and email with icons, "Get in touch" button – sticky in the sidebar on desktop, below content on mobile
- Contact CTA: Button redirects to the contact page (no embedded form on the detail page)
Recovery Plugin Against Update Loss
Custom templates in templates.dist/ work – until the next plugin update. Then the entire plugin folder is replaced, and the customisations are gone. What also happens if someone accidentally deletes files? Or a hosting migration script doesn't copy the folder?
For Hanse Holding, I built a custom WordPress plugin: the onOffice Template Guard. The plugin hooks into upgrader_process_complete – so it only fires when WordPress performs a plugin update. After every onOffice plugin update, it checks whether the custom template files are still present and automatically restores them from a protected backup if needed.
// Simplified recovery logic (onOffice Template Guard)
add_action('upgrader_process_complete', function($upgrader, $options) {
if ($options['type'] !== 'plugin') return;
$templates = [
'templates.dist/estate/cardgrid.php',
'templates.dist/estate/custom_detail.php',
'templates.dist/onoffice-style.css',
];
$plugin_dir = WP_PLUGIN_DIR . '/onoffice-for-wp-websites/';
$backup_dir = WP_PLUGIN_DIR . '/onoffice-template-guard/backup/';
foreach ($templates as $tpl) {
if (!file_exists($plugin_dir . $tpl)) {
// Template missing → restore from backup
wp_mkdir_p(dirname($plugin_dir . $tpl));
@copy($backup_dir . $tpl, $plugin_dir . $tpl);
}
}
}, 10, 2);
In the WordPress admin, there's a status page under Tools → onOffice Template Guard: it shows which templates are currently present, whether the backup is intact, and offers manual restoration with one click. The plugin doesn't run on every page load – only after plugin updates. No performance overhead during normal operation.
The Template Guard isn't a technical highlight – and that's the point. A team maintaining the website two years from now will understand what it does in thirty seconds. The admin page shows green or red, the hook only fires after updates, the backup lives in a fixed location. Sometimes the best solution is the most boring one.
GDPR: OpenStreetMap Instead of Google Maps
Property detail pages without a map are like exposés without an address – possible, but not useful. Google Maps is the standard solution but has GDPR implications: on load, IP addresses and browser data are transmitted to Google. This requires cookie consent, and many users decline.
The onOffice plugin already comes with a map solution: Leaflet.js with OpenStreetMap tiles. Leaflet is an open-source map library (42 KB gzipped) that loads map tiles from OpenStreetMap servers. These servers do not process personal data under GDPR – the map is immediately visible, without consent banners, without "Load external content" overlays.
The coordinates come directly from the onOffice dataset – the CRM provides them as part of each property record. No additional geocoding in the plugin needed. For users who need directions, there's an "Open in Google Maps" button that passes the coordinates as an external link – without embedding Google Maps on the page itself.
Numbers & Results
The result after integration:
- 22 properties live on the website, fully synchronised from onOffice
- List view in a 3-column card grid with location filter and pagination
- Detail pages with gallery, key data, OpenStreetMap map, and contact person
- Update-safe: Custom templates in
templates.dist/+ onOffice Template Guard as recovery mechanism - GDPR-compliant: No Google Maps, no external trackers on the property pages
- Responsive: Mobile-optimised for all devices
The live result can be seen at verwaltung-hh.com.
- When "the plugin doesn't work," check the API configuration first – not the plugin
- Permalink structure isn't a detail, it's a prerequisite for plugin routing
- Custom templates in
templates.dist/are the way to build custom designs – yet few know about it - A Template Guard with an
upgrader_process_completehook sounds like overkill until the next update overwrites your customisations - OpenStreetMap + Leaflet.js is a full-featured Google Maps alternative that eliminates GDPR headaches
Frequently Asked Questions about onOffice WordPress Integration
Why does the onOffice plugin show no listings?
The most common cause is a misconfigured API user in onOffice. If the permission is set to "Read properties: own only" instead of "all", the API only returns properties assigned to the API user personally – usually zero. Second most common cause: The WordPress permalink structure is not set to "Post name", which breaks the plugin's built-in routing logic for detail pages.
Where do custom templates go in the onOffice plugin?
Custom templates are placed in the templates.dist/estate/ directory inside the onOffice plugin folder and configured as active templates via the plugin settings. The problem: during plugin updates, the entire folder is replaced. Without safeguards – such as a Template Guard – customisations are lost. Many developers don't know this and lose their templates on the next update.
How do you prevent plugin updates from overwriting custom templates?
A custom recovery plugin – like the "onOffice Template Guard" built for this project – hooks into WordPress' upgrader_process_complete. After every onOffice plugin update, it automatically checks whether the custom template files are still present and restores them from a protected backup if needed. In the admin area under Tools, there's also a status page with a manual restore option.
Can onOffice listings be displayed with a GDPR-compliant map?
Yes. Instead of Google Maps, you can use OpenStreetMap with Leaflet.js. Leaflet loads map tiles from OpenStreetMap servers, which do not process personal data. This eliminates the need for cookie consent for map embedding – the detail page is GDPR-clean without restrictions.
Planning your own onOffice integration?
Need a custom onOffice-WordPress integration – with your own design, reliable synchronisation, and update-safe templates? Get in touch.
View Service Page