WordPress Plugin vs functions.php: Where Code Belongs

WordPress Plugin vs functions.php: Where Code Belongs — WP Block Suite

Keep design-specific theme setup in functions.php.

Put functionality that must survive a theme switch inside a plugin.

The correct boundary follows ownership, not whichever file feels easier.

WordPress plugin vs functions.php: the rule

Theme-bound presentation belongs with the active theme.

Portable site capability belongs in a plugin.

What is functions.php?

It is an optional PHP file automatically loaded for the active theme.

Both classic and block themes can use it.

The official theme guidance is explicit

The Theme Handbook says design-independent features belong in plugins.

It describes themes as responsible for site design.

functions.php behaves like plugin code

It can call WordPress functions, define code, and register hooks.

That technical power does not give it universal ownership.

The active theme controls whether it loads

WordPress loads only the currently active theme’s functions file.

Switching themes changes which theme functionality executes.

Use the theme-switch test

Ask whether the behaviour should remain after replacing the visual design.

If yes, the behaviour normally belongs in a plugin.

Compare the ownership boundaries

QuestionPluginfunctions.php
OwnerSite capabilityActive design
Survives theme switchYesNo
Activation controlPlugin statusTheme activation
Update boundaryPlugin packageTheme package
Best fitPortable behaviourTheme setup
Removal signalPlugin deactivationTheme switch

Good functions.php use: theme support

The active design can declare supported WordPress presentation features.

Those declarations naturally change when another theme becomes active.

Good functions.php use: theme assets

Theme styles and scripts can be enqueued from the theme.

Load them only where the design actually requires them.

Good functions.php use: presentation hooks

A theme can adjust design-specific markup through stable hooks.

The behaviour should lose relevance with that theme.

Good functions.php use: template helpers

Helpers used only by that theme’s templates can remain theme-owned.

Prefix or namespace them to avoid global collisions.

Good plugin use: custom post types

Content structure should usually survive a visual redesign.

Registering it inside a plugin preserves administration after theme changes. We cover the method in telling a theme conflict from a plugin conflict.

Good plugin use: taxonomies

Classification reflects information architecture rather than visual presentation.

Keep its registration available across supported themes.

Good plugin use: shortcodes

Stored content may contain shortcode references for many years.

A theme switch should not expose raw shortcode text.

Good plugin use: blocks storing content

Custom content blocks can remain embedded beyond one theme’s lifespan.

Keep their registration and migrations independent from presentation packages.

Good plugin use: roles and permissions

Business access rules should not disappear with a design change.

Own their setup, checks, migration, and removal inside functionality code.

Good plugin use: external integrations

Payments, email, CRM, search, and storage serve site operations.

Their connections normally outlive the active theme.

Good plugin use: scheduled work

Imports, exports, renewals, and clean-up are not presentation concerns.

Theme changes should not stop business processing.

Good plugin use: REST endpoints

Applications may depend on stable routes, permissions, and response shapes.

Those contracts should remain independent from visual design.

Child themes protect theme customisations

A child theme separates custom changes from an updated parent theme.

This prevents parent updates overwriting owned theme code.

Never edit a vendor parent theme directly

Updates can replace changed parent files and erase custom work.

The official editing guide recommends child themes for protected modifications.

Child and parent functions both load

WordPress loads child functions before parent functions.

The child file does not replace the parent file wholesale.

A child function cannot redeclare blindly

Duplicate global names can create fatal errors or unexpected behaviour.

Understand the parent extension contract before changing callbacks.

Use hooks instead of copied parent functions

Remove and replace callbacks where the parent exposes stable hooks.

Copying an internal function creates drift after parent updates.

Block themes still have functions.php

HTML templates and theme.json do not eliminate PHP custom functionality.

The same ownership boundary still applies.

Prefer theme.json for supported design settings

Declarative design configuration can be clearer than procedural PHP.

Use code only when the supported configuration cannot express the requirement.

Do not make content depend on theme markup

Theme-specific shortcodes or blocks can lock stored content to one design.

Keep durable content semantics in functionality plugins.

The same hook works from either location

Technical equivalence does not decide architectural ownership.

Choose where the behaviour belongs across future changes.

Plugin packaging creates separate enablement

Administrators can activate functionality without replacing the active design.

That separation improves testing, diagnosis, handoff, and retirement.

Theme activation bundles many changes

A theme switch changes templates, styling, parts, and theme-owned functions together.

Portable business behaviour should not share that broad transition.

Separate release cadences

Design work and business functionality rarely change for identical reasons.

Independent packages let each follow suitable testing and deployment.

Separate support ownership

A theme vendor should not silently own bespoke operational code.

Name the maintainer responsible for every plugin capability.

Separate security review

Functionality handling users, input, files, or remote calls needs focused review.

Its location inside a theme does not reduce risk.

Separate data lifecycle ownership

Options, tables, posts, and files can outlive the active design.

A plugin can define activation, migration, deactivation, and uninstall behaviour.

Theme switching does not run plugin uninstall

Theme-bound business data can remain without its producing code.

That orphaned state complicates restoration and future migration.

Avoid direct database work during theme load

Repeated setup can run on every request and under concurrent traffic.

Use explicit, versioned, idempotent functionality migrations.

Inventory existing functions.php behaviour

List callbacks, features, assets, data, jobs, integrations, and permissions.

Assign each item to presentation or durable functionality.

Trace included theme files

A short functions file may require many hidden modules.

Follow every include before declaring the theme boundary clean.

Map stored content dependencies

Search for shortcodes, block names, post types, and metadata.

These references reveal functionality that a theme switch could strand.

Map external consumers

Applications, feeds, webhooks, jobs, and reports may call theme-owned endpoints.

Move their contracts before changing the active theme.

Create a small site functionality plugin

Related portable behaviour can share one clearly named private package.

The Plugin Handbook introduction shows how simple a plugin can be.

Do not create one plugin per callback

Excessive fragmentation creates noisy activation and ownership surfaces.

Group cohesive functionality under one maintained purpose.

Move code without changing behaviour first

Separate relocation from redesign whenever practical.

A smaller change preserves diagnosis when output differs.

Prevent duplicate hook registration

Running old and new copies can duplicate emails, jobs, or output.

Activate the new owner through a controlled transition.

Preserve callback priorities

Changing hook priority can alter interactions with themes and plugins.

Record existing priorities and test the full callback chain.

Preserve option names and schemas

A new container need not rename established data immediately.

Plan schema changes as separate reversible migrations.

Test with both themes during migration

Verify behaviour under the existing and intended replacement designs.

Portable functionality should pass the same contract tests.

Test deactivation deliberately

Disabling the new plugin should produce understood, bounded feature loss.

Stored content and administration must retain a recovery route.

Test parent and child updates

Confirm parent releases cannot overwrite the remaining theme customisations.

Recheck hooks whose internal implementation changed.

Keep production out of the editor

Browser file edits are immediate and lack professional development controls.

Edit reviewed source offline, test it, and deploy deliberately.

Use version control for both containers

Preserve source, history, review, tests, and release evidence.

The production filesystem should not become the authority.

Monitor theme switches as production changes

Check content, permissions, jobs, endpoints, integrations, and public journeys.

A visual review cannot detect every lost function.

functions.php loads beyond public templates

The active theme’s code also runs during many administration requests.

Do not assume theme PHP affects only rendered pages.

Admin-only code still needs the right owner

An administration screen can serve design configuration or site operations.

Classify its purpose before choosing theme or plugin placement.

Theme previews need isolated behaviour

Previewing another theme can execute parts of its setup.

Theme code must not perform destructive business transitions during previews.

Fatal theme code can block administration

A syntax error may affect frontend and dashboard requests together.

Maintain tested filesystem or hosting recovery access.

A plugin does not remove fatal risk

Bad PHP can fail regardless of its directory.

Packaging improves ownership and isolation, not code correctness.

Avoid plugin dependence on one theme

A functionality plugin should expose behaviour through stable WordPress interfaces.

Theme-specific adapters can remain separate and optional.

Avoid theme dependence on plugin internals

Templates should use documented functions, hooks, blocks, or query interfaces.

Private plugin classes can change without a stable promise.

Design graceful missing-plugin states

A theme should not fatally call an absent plugin function.

Check capability and provide bounded fallback output.

Theme patterns should avoid unavailable blocks

Bundled patterns can reference blocks supplied by optional plugins.

State requirements clearly or provide core-block alternatives.

Navigation behaviour crosses visible boundaries

Menu styling belongs with design, but access rules are functionality.

Separate appearance from permission decisions.

Image sizes need lifecycle thought

A theme may register sizes required by its layouts.

Generated files can remain after switching themes.

Rewrite rules expose ownership mistakes

Business routes should not vanish when the visual theme changes.

Register them in a plugin and manage flushing deliberately.

Widgets can contain durable behaviour

Presentation wrappers may be theme-bound while data services remain portable.

Split rendering from the underlying capability where useful.

Email formatting is not email delivery

A design may style messages, but delivery workflows are operational.

Keep queues, recipients, and triggers outside theme ownership.

Analytics presentation differs from collection

Dashboard charts can follow design, but tracking affects privacy and data.

Place consent-aware collection inside owned functionality.

Multisite can activate different themes

Theme-owned behaviour may exist on only part of a network.

Network-wide capabilities need an appropriate plugin activation model.

Caching can mask the ownership change

Old output may survive after code moves or themes switch.

Purge relevant layers and verify uncached behaviour.

Asset versions need independent evidence

Moved frontend functionality may ship JavaScript and CSS from a plugin.

Confirm URLs, dependency handles, versions, and cache invalidation.

Test accessibility after separating layers

Markup ownership changes can alter labels, focus, semantics, and keyboard behaviour.

Validate the combined plugin and theme output.

Test localisation after moving strings

A plugin requires its own text domain and translation loading.

Do not keep relying on the former theme domain.

Benchmark performance after relocation

Moving code can change load conditions, queries, assets, and cache keys.

Compare representative requests against the recorded baseline.

Document what intentionally remains

List theme-bound hooks with their design purpose and owner.

Future audits should not reopen settled boundaries without new evidence.

Document the new dependency boundary

State supported themes, public interfaces, fallback behaviour, and tests.

Keep theme and plugin release notes separate.

Retire obsolete theme code completely

Remove old callbacks, includes, configuration, and unreachable helpers.

Confirm monitoring shows only the plugin-owned behaviour.

Know the honest weak case

A small presentation hook can belong inside a maintained child theme.

Do not create packaging overhead without a durable functional boundary.

Use the code-placement checklist

  1. Define the exact behaviour.
  2. Apply the theme-switch test.
  3. Identify stored content references.
  4. Map data and external consumers.
  5. Separate presentation from functionality.
  6. Protect parent-theme updates.
  7. Trace every included theme file.
  8. Choose one cohesive plugin boundary.
  9. Preserve existing hooks and priorities.
  10. Avoid duplicate execution.
  11. Test old and replacement themes.
  12. Test activation and deactivation.
  13. Deploy versioned reviewed source.
  14. Monitor non-visual behaviour.
  15. Document ownership and retirement.

Frequently asked questions

Should custom code go in a plugin or functions.php?

Use a plugin unless the code exists only for that theme’s presentation.

Does functions.php work with block themes?

Yes. Block and classic themes can both load a functions file.

Will child functions.php replace the parent file?

No. WordPress loads both, with the child file loading first.

Should post types be registered in a theme?

Usually no. Durable content structure should survive a theme switch.

Can a theme update erase functions.php changes?

Yes. Direct parent-theme edits can be overwritten by updates.

The verdict

The theme-switch test exposes the correct owner quickly. Review WP Block Suite’s $299 lifetime licence.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *