A developer edits a core template to fix a client request by Friday. Three months later a core update overwrites that file and the fix is gone. Prefer hooks for behavior, child themes for markup, and plugins for billing and spam: each lives under oc-content and can be diffed without touching core. Log which plugin owns each shared hook after every staging pass. File layout: architecture notes, the production build guide, and the official introduction.
Discover hook names by grepping core for osc_run_hook( and osc_apply_filter( under oc-includes/osclass. Do not invent names from memory. Document which plugin owns each shared hook in the release notes.
init / init_item / init_user / init_main: early request setup per controller.header / footer / before_html / after_html: assets and late HTML wrappers.before_search / search_conditions / after_search: filter params and result sets on search routes.item_form / item_form_price / item_contact_form: publish, edit, and contact form regions.posted_item / edited_item: after successful save in ItemActions.hook_email_item_inquiry and other hook_email_*: mail side effects after core templates load.add_admin_toolbar_menus / plugin admin menu hooks: backoffice navigation and assets.Lower priority numbers run earlier (default 5). Example conflict: plugin A and plugin B both attach to item_form_price; if B runs later and echoes a full replacement block, A's note disappears. Fix by agreeing ownership, using different form hooks, or calling osc_remove_hook() on uninstall for callbacks you registered. Prefer named functions so remove and reorder stay possible.
Three layers. Mixing them without a clear owner is how the same business rule ends up twice and breaks after one plugin update. Prefer core helpers (row loaders, cookie/session, search utilities) before inventing parallel queries in a theme.
| Layer | Purpose | Example |
|---|---|---|
| Hooks | Change runtime behavior without editing core controllers | item_form_price, posted_item |
| Themes / child themes | Control UI and layout while preserving upgrade path | Override a template; keep extras in functions_child.php |
| Plugins | Reusable modules such as billing, messaging, anti-spam | Osclass Pay; Cloudflare Turnstile |
Hooks register with osc_add_hook(). Filters register with osc_add_filter() and must return the transformed value through osc_apply_filter(). Themes and plugins fire side effects with osc_run_hook(). Prefer named plugin functions over anonymous closures.
osc_add_hook('item_form_price', 'myplugin_render_price_note');
function myplugin_render_price_note() {
echo '<p class="price-note">Price shown excludes delivery.</p>';
}
osc_add_filter('email_title', 'myplugin_prefix_email_title');
function myplugin_prefix_email_title($title) {
return '[Site] ' . $title;
}
osc_add_hook('search_conditions', 'myplugin_log_search_params');
function myplugin_log_search_params($params) {
// inspect Params array; do not invent SQL here unless you own indexes
}
Plugins that ship custom mail should create and remove templates through core helpers:
osc_email_template_create(
'xyz_my_custom_template',
'{WEB_TITLE} - New info #{KEYWORD0}',
'<p>Hi {CONTACT_NAME}!</p><p>{KEYWORD1}</p>'
);
// on uninstall or schema cleanup:
osc_email_template_delete('xyz_my_custom_template');
Disable does not uninstall: orphan email templates and preferences stay until you delete them. Use osc_static_page_url_from_page($page, $locale) for static page links so rewrite patterns and locale prefixes match Settings > Permalinks. Hardcoded ?page=page&id= URLs break when friendly URLs turn on.
Theme routes can resolve to a theme file or to that theme's custom or plugins subfolder. Keep business rules out of those templates when a hook can own them.
Child theme load order: parent template → child override of the same path → functions_child.php for hooks and osc_enqueue_style(). Copy only templates you change. Osclass-initiated files (main.php, search.php, item.php, account templates) and theme-initiated includes (header.php, loop-single.php) resolve differently; re-diff overrides after every parent theme update.
function gam_child_custom_css() {
osc_enqueue_style('style-child', osc_current_web_theme_url('css/style-child.css'));
}
osc_add_hook('header', 'gam_child_custom_css');
Plugin layout: folder under oc-content/plugins/, index registers hooks on activation, admin assets only on plugin admin pages. Loading a payment admin stylesheet on every public page is a common leak that looks like a slow theme. Prefer a three-letter plugin prefix on function names (xyz_) so uninstall and greps stay unambiguous across themes.
Prefer model helpers over ad-hoc SQL against oc_t_item from a theme. Osclass 8.3 session-backed loaders cut repetitive queries. Prefer osc_get_countries() / osc_get_country_row('US') (and matching region, city, category, user helpers) over looping Country::newInstance()->listAll(). Category preload is gated by OPTIMIZE_CATEGORIES and OPTIMIZE_CATEGORIES_LIMIT (roughly 1,000-2,000). Past that limit, themes that assume a full in-memory tree will fail. Flush object cache after taxonomy or preference changes that those helpers cache in session.
Put business rules in a plugin under oc-content/plugins/, not in parent functions.php. Parent theme updates can overwrite that file; child functions_child.php is for presentation hooks and CSS enqueue only. If a rule must survive theme swaps (fee labels, form validation, mail), it belongs in a plugin with install/uninstall cleanup.
Plugin install should create email templates and preferences you need; uninstall should delete those templates and prefs. Disable leaves files on disk and usually keeps settings, so a "disabled" payment plugin can still confuse operators who expect a clean slate. Document which tables or preference keys your plugin owns. After activate, run publish and search once before enabling the plugin on production.
Paste an OsclassPoint API key into Settings > General > Software Updates to install themes and plugins from Market. Language and location packs work without a key. Treat the key as a credential; rotate it when staff with Market access leave; still stage Market installs like a ZIP upload against your PHP minor and active plugin set. Market delivery does not prove payment callbacks or cron ownership.
Since Osclass 8.0 you can rename oc-admin on disk and set OC_ADMIN_FOLDER in config.php. Plugin admin links must use osc_admin_base_url(), not a hardcoded /oc-admin/ path. Do not put the new folder name in robots.txt. After rename, click every plugin admin menu once on staging; broken links usually mean a plugin still concatenates a literal path. Update operator bookmarks and staging deploy runbooks to the new backoffice folder name in the same release. Docs: change backoffice directory name.
Deploy one high-impact change at a time and verify publish, pay, search, message, and moderate. Keep a runbook that names the payment plugin, cache plugin, and hooks that touch publish.
OSC_MEMORY_LIMIT in config.php (for example 128M) within host PHP max; default core floor is 64M.Fork core only if hooks and plugins cannot do the job and someone will merge every upstream release. Stay on supported core; treat upload and plugin paths as high-risk in review (older Osclass 3.x had documented RCE via admin XSS and crafted uploads). Footer script moves from old speed tutorials repeatedly broke item-post uploads and city autosuggest; retest those flows after asset-order changes. Email customization needs a working SMTP path, not only a template edit. Generated hook names, table names, and preference keys are guesses until you grep core or read docs; keep config.php and Market keys out of chat logs.
Translations use .po/.mo files. Never edit the en_US source catalogs; work on a copy of the target language in oc-admin (International > Translations) or Poedit, then compile to .mo. Themes and plugins ship their own catalogs under their folders; leave core catalogs alone unless you maintain a language pack. Market language packs install without an OsclassPoint API key.
RTL locales need a theme that flips layout, not only translated strings. After enabling an RTL language, check listing forms, filters, and oc-admin labels on a phone-width viewport. Incomplete packs often leave mixed English admin labels next to translated front-office strings; decide whether that is acceptable before launch. Keep permalink and language-in-URL settings stable before marketing spend so translated routes do not fork.
Keep custom plugins and child themes in version control. Each release names the hook or template touched, the reason, and how to roll back. Before production: staging on a recent DB dump, verified backup restore, PHP compatibility for every active plugin, dry run of publish, search, payment callback, and moderation. After rewrite changes, confirm canonical tags on listing URLs.
Osclass 8.3.1 added PHP 8.5 support, an updated PHP mailer path, utf8mb4 (emoji and full Unicode in titles, messages, and category names), and session/cookie updates. Older utf8 collations can truncate emoji on write. Core PHP support does not certify every theme or plugin on that minor; check each product's Requires-PHP note. Keep web and CLI PHP identical for cron. After upgrade, sign in as user and admin, publish a listing with emoji in the title, open Settings > Permalinks and Save once, and open a session-dependent dashboard widget. Session loops after a domain move often mean cookie domain or reverse-proxy Set-Cookie rewrites, not a theme bug.
Release gate: named owner; changelog and rollback note; no core edits without sign-off; hook priorities written down when plugins share a flow; PHP compatibility matrix updated. Monthly: review plugin changelogs against your PHP minor; re-test payment callbacks after proxy or SSL changes; confirm cron CLI PHP matches the web pool; sample customized templates after a parent theme update. Keep a compatibility matrix (PHP minor, Osclass, theme, payment plugin, last staging pass). If the matrix is stale, force a full staging pass even for a "small" plugin bump. After parent theme updates, diff every child override file against the new parent before calling the release done; silent template renames are a common source of blank listing cards.
This article was last updated on 25. August 2026.