init
Fires once WordPress has loaded and the current user is known, before any headers are sent. The usual place to register post types.
When it fires
init runs on every request (front end, admin, AJAX, REST API and cron) once WordPress, your plugins and your theme have loaded. The current user has been set up by now, so is_user_logged_in() and current_user_can() work.
Nothing has been sent to the browser yet, but the main query hasn't run either, so conditional tags such as is_page() or is_single() don't work here. Use template_redirect for anything that depends on the page being viewed.
Parameters
None. Your callback takes no arguments.
Example: Register a custom post type
add_action( 'init', function () {
register_post_type(
'testimonial',
array(
'labels' => array(
'name' => __( 'Testimonials', 'my-snippets' ),
'singular_name' => __( 'Testimonial', 'my-snippets' ),
),
'public' => true,
'has_archive' => true,
'show_in_rest' => true,
'menu_icon' => 'dashicons-format-quote',
'supports' => array( 'title', 'editor', 'thumbnail' ),
)
);
} );
Good to know
- After registering a new post type, go to Settings > Permalinks and click Save once, so its URLs start working.
- init runs on every request, so keep the callback light. Move slow work to a more specific hook or a scheduled event.
- Core uses init with its own priorities too (widgets are set up at priority 1), so stick to the default 10 unless you need to run before or after something specific.