WordPress · 5 min read · March 2025

Plugin vs functions.php — What to Use and When

This is the question WordPress developers ask most often — and get wrong most often. The answer isn't about file size or personal preference. It's about coupling: does this functionality belong to the theme, or does it belong to the site?

The Core Question

Both functions.php and a plugin execute PHP on every WordPress request. Mechanically, they're almost identical. The difference is what happens when you switch themes.

If you add a custom post type in functions.php and then switch themes, those post types vanish. The content still exists in the database, but WordPress no longer knows about it. That's a data integrity problem — and it's entirely avoidable.

The rule of thumb: If the feature would still be needed after switching to a completely different theme, it belongs in a plugin, not in functions.php.

What Belongs in functions.php

These are things that are genuinely about the theme — they make no sense without it:

  • Theme support declarationsadd_theme_support('post-thumbnails'), title tag, HTML5 markup
  • Menu registration — menu locations are tied to theme layout
  • Asset enqueueing — loading theme-specific CSS and JS
  • Sidebar registration — widget areas defined by the theme's layout
  • Template-level display logic — helpers that manipulate output only within theme templates
  • Customizer settings — options that control theme appearance (colors, fonts)

What Belongs in a Plugin

These features survive a theme change — they belong to the site, not the theme:

  • Custom post types and taxonomies — always a plugin
  • Shortcodes — content uses them; a theme switch would break all content that contains them
  • Custom database tables or meta
  • Third-party API integrations — payment gateways, CRM connections, email services
  • WP-CLI commands
  • User roles and capabilities
  • Cron jobs and background processing
  • Any feature you'd want to enable/disable independently

The Must-Use Plugin Option

There's a third option that many developers overlook: must-use plugins (wp-content/mu-plugins/). Files in this directory:

  • Load automatically — no activation step required
  • Cannot be deactivated from the admin panel
  • Load before regular plugins
  • Are invisible to clients who shouldn't be disabling core functionality
// wp-content/mu-plugins/site-post-types.php
<?php
/**
 * Plugin Name: Site Post Types
 * Description: Registers custom post types for this installation.
 */

add_action( 'init', function() {
    register_post_type( 'project', [
        'labels'      => [ 'name' => 'Projects', 'singular_name' => 'Project' ],
        'public'      => true,
        'has_archive' => true,
        'supports'    => [ 'title', 'editor', 'thumbnail', 'custom-fields' ],
        'show_in_rest' => true,
    ] );
} );

Must-use plugins are the right home for business-critical, always-on code that should never be deactivated accidentally.

The Site-Specific Plugin Pattern

On client projects, a common professional pattern is the site-specific plugin: a single plugin that contains all the custom post types, taxonomies, shortcodes, and integrations for one site. It's named after the client (e.g., acme-core/acme-core.php) and lives in the plugins directory.

This gives you:

  • Theme-independence for all data structures
  • A clear boundary between "theme" work and "site" work
  • Version control isolation — you can update the theme without touching site functionality
  • Easy handoff: a new developer immediately understands where business logic lives

Decision Flowchart

Run through these questions in order:

  1. Would this feature still be needed if the theme changed? → Yes: plugin. No: functions.php.
  2. Does it store or register data (post types, taxonomies, options)? → Always a plugin.
  3. Does it need to be activated/deactivated independently? → Plugin.
  4. Must it be always on and impossible to deactivate? → Must-use plugin.
  5. Is it purely about layout, appearance, or template output? → functions.php.

When in doubt, err toward a plugin. The overhead of creating one is minimal and the decoupling benefit is permanent.