str_starts_with( string $haystack, string $needle ): bool
- Since
- 5.9.0
- Source
wp-includes/compat.php:521
Determines whether a string begins with a given substring, using a case-sensitive strpos() comparison under the hood. WordPress loads this as a polyfill so the same str_starts_with() call works whether the site runs on PHP 7 or PHP 8, without any version checks in your code. An empty needle always returns true, matching PHP's own native function.
str_starts_with() function added in PHP 8.0.Description
Performs a case-sensitive check indicating if the haystack begins with needle.
Compatibility
- WordPress
- since 5.9.0
- PHP
- 7.4–8.6-dev
- 6.7.7
- 6.8.8
- 6.9.7
- 7.0.4
- 7.1.0
Present in every tracked release (6.7.7 to 7.1.0), and compiles on PHP 7.4 through 8.6-dev.
Parameters
$haystackstring- The string to search in.
$needlestring- The substring to search for in the
$haystack.
Return value
bool- True if
$haystackstarts with$needle, otherwise false.
Code examples
Every example is editable and runs in a real WordPress booted in your browser by WordPress Playground. Press Run, then edit the code: clicking away re-runs it. Nothing is sent anywhere until you do.
Check if a post title starts with a given word
Fetch the first sample post and test its title against a prefix.
$post = get_post( 1 );
$title = $post->post_title;
$prefix = 'Hello';
if ( str_starts_with( $title, $prefix ) ) {
printf( 'The title "%s" starts with "%s".', esc_html( $title ), esc_html( $prefix ) );
} else {
printf( 'The title "%s" does not start with "%s".', esc_html( $title ), esc_html( $prefix ) );
}The comparison is case-sensitive, so 'hello' would not match 'Hello world!'.
Filter category slugs by prefix
Loop over the site's categories and flag the ones whose slug starts with "new".
$categories = get_terms( array(
'taxonomy' => 'category',
'hide_empty' => false,
) );
foreach ( $categories as $category ) {
if ( str_starts_with( $category->slug, 'new' ) ) {
printf( 'Category "%s" (slug: %s) matches the "new" prefix.<br>', esc_html( $category->name ), esc_html( $category->slug ) );
} else {
printf( 'Category "%s" (slug: %s) does not match.<br>', esc_html( $category->name ), esc_html( $category->slug ) );
}
}Common problems and fixes · 3
- Why does str_starts_with return true when my needle is an empty string?
- Why doesn't str_starts_with match when the casing is different?
- Should I use str_starts_with or str_contains to check a prefix?
Why does str_starts_with return true when my needle is an empty string?
if ( '' === $needle ) return true;. An empty needle is treated as matching the start of every string, which mirrors PHP's own native behavior rather than being a bug.Why doesn't str_starts_with match when the casing is different?
Should I use str_starts_with or str_contains to check a prefix?
Alternatives and related functions
str_ends_with- When you need to check the end of a string instead of the beginning, such as a file extension.
str_contains- When the needle can appear anywhere in the haystack, not only at the start.
strpos- When you also need the numeric position of a match, not just a true or false result.
substr- When you need to extract or compare a fixed-length slice of the string rather than test a prefix.
Performance profile
How much work a call to str_starts_with() does, and what it touches: the algorithmic scaling, the Zend instruction count per call across PHP versions, the hooks it hands control to, and the core code that calls it. Measured from the compiled opcodes, not a stopwatch, so every number is identical on any machine running the same PHP version, and every function in core is ranked by cost.
- Cost class
- Trivial
- Scaling
- Constant
- Instructions
- 5–7
- Plugin surface
- None
- Called by
- 50
Touches nothing outside its own arguments.
No loop in the body: the same number of instructions runs whatever you pass in.
Executed per call on PHP 8.5, depending on the branch taken. The body compiles to 8.
Nothing here hands control to plugin code.
50 places in core call this, so the cost is paid more often than your own code shows.
What one call costs · 1 distinct outcome
One number would be a lie: the work depends on which branch runs. These are every distinct cost str_starts_with() can have, taken from its control-flow graph on PHP 8.5.
| When | Instructions | Calls it makes |
|---|---|---|
| always | 5–7 | none |
Across PHP versions
| PHP | Compiled | Executed | Branches | Notes |
|---|---|---|---|---|
| 8.6-dev | 8 | 5–7 | 1 | |
| 8.5 | 8 | 5–7 | 1 | |
| 8.4 | 8 | 5–7 | 1 | 3 fewer instructions than PHP 8.3 |
| 8.3 | 11 | 5–10 | 1 | |
| 8.2 | 11 | 5–10 | 1 | |
| 8.1 | 11 | 5–10 | 1 | |
| 7.4 | 11 | 5–10 | 1 |
An instruction is not a fixed amount of time, so a matching count is not necessarily the same speed; what it rules out is a difference in the work itself.
Used by · 50
- AbstractEnum::__call()Handles dynamic method calls for enum checking.
- Core_Upgrader::check_files()Compares the disk file checksums against the expected checksums.
- File_Upload_Upgrader::__construct()Construct the upgrader for a form.
- MimeType::isType()Checks if this MIME type is a specific type.
- OptionEnum::determineClassEnumerations()Determines the class enumerations by reflecting on class constants.
- PO::trim_quotes()
- WP::parse_request()Parses the request to find the correct WordPress query.
- WP_AI_Client_Ability_Function_Resolver::is_ability_call()Checks if a function call is an ability call.
- WP_Application_Passwords::check_password()Checks a plaintext application password against a hashed password.
- WP_Automatic_Updater::is_allowed_dir()Checks whether access to a given directory is allowed.
- WP_Block_Metadata_Registry::find_collection_path()Finds the collection path for a given file or folder.
- WP_Block_Metadata_Registry::is_valid_collection_path()Checks whether the given block metadata collection path is valid against the list of collection roots.
Show all 50
- WP_Customize_Manager::add_state_query_params()Adds customize state query params to a given URL if preview is allowed.
- WP_Customize_Nav_Menus::filter_wp_nav_menu_args()Keeps track of the arguments that are being passed to wp_nav_menu().
- WP_Customize_Widgets::is_option_capture_ignored()Determines whether the captured option update should be ignored.
- WP_Duotone::restore_image_outer_container()Fixes the issue with our generated class name not being added to the block's outer container in classic themes due to gutenberg_restore_image_outer_container from layout block supports.
- WP_Embed::delete_oembed_caches()Deletes all oEmbed caches. Unused by core as of 4.0.0.
- WP_Font_Face::order_src()Orders `src` items to optimize for browser support.
- WP_Font_Face_Resolver::to_theme_file_uri()Converts each 'file:./' placeholder into a URI to the font file in the theme.
- WP_HTML_Doctype_Info::__construct()Constructor.
- WP_HTML_Tag_Processor::get_attribute_names_with_prefix()Gets lowercase names of all attributes matching a given prefix in the current tag.
- WP_Http_Cookie::test()Confirms that it's OK to send this cookie to the URL checked against.
- WP_Http_Encoding::compatible_gzinflate()Decompression of deflated string while staying compatible with the majority of servers.
- WP_Image_Editor_Imagick::load()Loads image from $this->file into new Imagick Object.
- WP_Interactivity_API::data_wp_bind_processor()Processes the `data-wp-bind` directive.
- WP_Interactivity_API::data_wp_each_processor()Processes the `data-wp-each` directive.
- WP_Media_List_Table::column_default()Handles output for the default column.
- WP_Media_List_Table::get_views()Gets an array of links for the available views on this table.
- WP_Network::_set_cookie_domain()Sets the cookie domain based on the network domain if one has not been populated.
- WP_Posts_List_Table::column_default()Handles the default column output.
- WP_Privacy_Policy_Content::get_default_content()Returns the default suggested privacy policy content.
- WP_Query::generate_postdata()Generates post data.
- WP_Query::parse_search()Generates SQL for the WHERE clause based on passed search terms.
- WP_REST_Attachments_Controller::create_item_from_url()Sideloads an external image from a URL into the media library.
- WP_REST_Attachments_Controller::create_item_permissions_check()Checks if a given request has access to create an attachment.
- WP_REST_Attachments_Controller::get_attachment_upload_subdir()Returns the uploads subdirectory an attachment is stored in.
- WP_REST_Attachments_Controller::get_filename_from_disposition()Parses filename from a Content-Disposition header value.
- WP_REST_Font_Faces_Controller::relative_fonts_path()Returns relative path to an uploaded font file.
- WP_REST_Request::from_url()Retrieves a WP_REST_Request object from a full URL.
- WP_REST_Server::get_compact_response_links()Retrieves the CURIEs (compact URIs) used for relations.
- WP_REST_Server::get_headers()Extracts headers from a PHP-style $_SERVER array.
- WP_REST_Server::match_request_to_handler()Matches a request object to its handler.
- WP_REST_Server::serve_request()Handles serving a REST API request.
- WP_Recovery_Mode::get_extension_for_error()Gets the extension that the error occurred in.
- WP_Recovery_Mode::is_network_plugin()Checks whether the given extension a network activated plugin.
- WP_Recovery_Mode_Email_Service::get_plugin()Return the details for a single plugin based on the extension data from an error.
- WP_Screen::get()Fetches a screen object.
- WP_Scripts::do_item()Processes a script dependency.
- WP_Scripts::in_default_dir()Whether a handle's source is in a default directory.
- WP_Site_Health::get_test_is_in_debug_mode()Tests if debug information is enabled.
Source code
function str_starts_with( $haystack, $needle ) { if ( '' === $needle ) { return true; } return 0 === strpos( $haystack, $needle ); }Changelog
Introduced in 5.9.0. Unchanged from 6.7.7 through 7.1.0.
Signature, return type and hooks compared across 5 parsed releases.
About this page
- Parsed data
- Generated from the wordpress-develop 7.1.0 tag, from
src/wp-includes/compat.php, and regenerated for each WordPress release so it tracks the code rather than a snapshot of it. - Corrections
- Something wrong on this page? Report it and it gets fixed in the next regeneration.