You added a filter. It runs. You can see it run. And the value on the page is not yours, because something else ran after you and put its own value back.
Priority is the argument that decides that, and almost every misunderstanding of it comes from one wrong assumption: priority does not order hooks against each other. It orders callbacks inside a single hook. A callback on init at priority 1 still runs long after every callback on plugins_loaded at priority 999, because the stage comes first and the number only breaks ties within it.
Get that separation clear and the rest is small.
The number
add_action( 'init', 'first_one', 5 );
add_action( 'init', 'second_one' ); // default 10
add_action( 'init', 'third_one', 20 );
Lower runs earlier. The default is 10. Any integer is legal, including negative ones, and there is nothing special about 10 other than that everything unlabelled sits there.
For filters the ordering matters more than it does for actions, because a filter chain passes each callback’s output into the next one. At priority 20 you receive whatever priority 10 returned, not the original value. If you assumed you were getting the raw title and you are getting somebody else’s decorated title, priority is why.
The tie, which is the part that catches people
Two callbacks registered on the same hook at the same priority run in the order they were added. Not alphabetically, not by plugin name. Registration order.
Registration order is a function of load order, and load order on a WordPress site is: must-use plugins, then active plugins in the order the option lists them, then the child theme’s functions.php, then the parent theme’s. So two plugins that both filter the_content at 10 resolve their conflict by whichever one the site owner happened to activate in a particular order, which is not a design you want to depend on.
If two things must not tie, do not let them tie. Give yours an explicit number that says what you mean.
Why 999 is not the fix it looks like
The reflex when something overwrites your value is to raise your priority until you win:
add_filter( 'the_content', 'my_content', 999 );
This works right up until the moment somebody else does the same thing, and now the site has two callbacks racing to be last. PHP_INT_MAX is the end of that road and it is not a good place to arrive.
There are three better moves, in order of preference.
Ask whether you are on the right hook at all. Being overwritten frequently means you hooked something early and general when a later, more specific hook exists. That is the same class of mistake as reaching for init when the answer was a stage that runs after the query, and the stage-by-stage map in the WordPress execution order is the thing to read before you touch the number.
Set a priority that expresses a relationship, not a competition. If you must run after a known callback registered at 10, use 11. The intent is legible to whoever reads it next, and it leaves room.
Remove the other callback, if it is genuinely wrong. That is the honest fix when a plugin is doing something you must undo, and it needs the exact signature it was added with:
remove_filter( 'the_content', 'their_function', 10 );
The priority argument must match the one used in add_filter, and the callback identity must match too. That is where most remove_ calls fail.
Removing a callback that is a method or a closure
A plain function name is easy. An object method is not, because you need the same instance:
remove_action( 'init', array( $their_object, 'their_method' ), 10 );
If the plugin created the object internally and never exposed it, you cannot construct a matching handle, and remove_action will silently do nothing. A static method is addressable, an instance method on a private object usually is not.
A closure is worse: it has no name, so there is nothing to pass. If the code you want to remove was registered as an anonymous function, removal is not available and you need a different approach, usually running later and correcting the result.
remove_action returns a boolean. Check it. A silent false is the difference between “I removed it” and “I thought I removed it”, and it is worth a line in the log while you are working:
$removed = remove_filter( 'the_content', 'their_function', 10 );
error_log( 'remove_filter the_content: ' . var_export( $removed, true ) );
That line, and what to do with the file it writes to, is covered in reading a WordPress debug log and naming the guilty code.
Removing at the right moment
A callback cannot be removed before it has been added. If plugin B removes plugin A’s filter from the top level of its own file, and A loads after B, the removal runs against an empty registry and does nothing.
Do the removal on a hook that fires after everything is loaded:
add_action( 'wp_loaded', function () {
remove_filter( 'the_content', 'their_function', 10 );
} );
Removing from inside a callback on the very hook you are editing works too, but be careful about doing it while that hook is running: you are modifying the list that is currently being iterated.
Seeing who is actually registered
When you need facts rather than guesses, the registry is a global array keyed by hook name and then by priority:
add_action( 'wp_loaded', function () {
global $wp_filter;
if ( isset( $wp_filter['the_content'] ) ) {
error_log( print_r( array_keys( $wp_filter['the_content']->callbacks ), true ) );
}
} );
That prints the priorities in use on that hook. Drop the array_keys and you get the callbacks themselves, which is noisy but tells you the names. Use it as a diagnostic and take it back out; it is not something to leave in shipped code.
The four rules
- Priority orders callbacks within one hook. The stage decides everything before that.
- Lower runs earlier, default
10, ties resolve by registration order, which resolves by load order. - A filter at a higher number receives the previous callback’s output, not the original value.
- Reach for
remove_and a correct hook choice before you reach for a bigger number.

