wppaste
WordPress

wp_get_attachment_url( int $attachment_id = 0 ): string|false

Since
2.1.0
Source
wp-includes/post.php:6970

Builds the public URL for an attachment post by reading its `_wp_attached_file` post meta and combining it with the uploads directory. Falls back to the attachment's GUID when the meta is missing or the uploads path can't be resolved, so the returned string isn't always a guaranteed-current path. Pair it with `wp_get_attachment_image_src()` when you also need width and height, since this function only ever returns the URL.

Retrieves the URL for an attachment.

Compatibility

WordPress
since 2.1.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

$attachment_idintoptional
Attachment post ID. Defaults to global $post.Default: 0

Return value

string|false
Attachment URL, 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.

Get the direct URL for an attachment by ID

Insert an attachment post with attached-file meta so the sandbox has something to resolve a URL for.

$attachment_id = get_option( 'wppaste_example_attachment_id' );

$url = wp_get_attachment_url( $attachment_id );

if ( $url ) {
	echo esc_html( 'Attachment URL: ' . $url );
} else {
	echo esc_html( 'No URL could be resolved for this attachment.' );
}

In a real upload the file would already exist on disk; here only the meta is set, which is enough for the URL-building logic to run.

Understand why the function returns false for a non-attachment post

Pass an ordinary post ID from the baseline fixtures to see the false return path in action.

$url = wp_get_attachment_url( 1 );

if ( false === $url ) {
	echo esc_html( 'Post 1 is not an attachment, so wp_get_attachment_url() returned false.' );
} else {
	echo esc_html( $url );
}

Common problems and fixes · 4

Why does wp_get_attachment_url() return false when I know the ID exists?

The source explicitly checks $post->post_type and returns false unless it's exactly 'attachment'. Passing the ID of a regular post, page, or any other post type always fails this check even though the post itself is valid.

Why does wp_get_attachment_url() give me the GUID instead of a proper uploads URL?

The function only builds a URL from _wp_attached_file post meta and the uploads directory. If that meta is empty or wp_get_upload_dir() reports an error, it silently falls back to get_the_guid(), which can be stale if the site URL ever changed.

Why is the URL still http on the front end when my site is over SSL?

set_url_scheme() is only applied when is_ssl() is true and the request is neither admin nor wp-login.php. On admin screens or admin-ajax requests the URL keeps whatever scheme was stored, which can mismatch a front end served over https.

Why does calling wp_get_attachment_url() with no argument return false outside The Loop?

Leaving $attachment_id at its default 0 makes the function rely on get_post( 0 ), which in turn falls back to the global $post. Outside a post context (an admin page, a cron job, a shortcode called from a widget) that global may be unset, so get_post() returns null and the function bails out.

Alternatives and related functions

wp_get_attachment_image_src
When you also need the image width and height alongside the URL, not just the URL string.
wp_get_attachment_image
When you want a ready-made `` tag with srcset and sizes instead of building markup around a bare URL.
get_attached_file
When you need the local filesystem path to the attachment rather than its public URL.
wp_get_original_image_url
When the attachment may have been edited or scaled and you specifically need the URL of the original, unmodified file.

Performance profile

How much work a call to wp_get_attachment_url() 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
Heavy

Reaches the database via get_post().

Scaling
Constant

No loop in the body: the same number of instructions runs whatever you pass in.

Instructions
10–78

Executed per call on PHP 8.5, depending on the branch taken. The body compiles to 91.

Plugin surface
1 hook

Third-party callbacks on 'wp_get_attachment_url' run inside this call, and their cost is not bounded by anything here.

Called by
42

42 places in core call this, so the cost is paid more often than your own code shows.

What it touches

  • querycontent queryget_post()called directly
  • hookthird-party callbacksapply_filters()called directly

Further down the call graph this can also reach cache, serialize, option and transient. Those are the worst case, several calls deep and usually down an error path, not what a normal call pays.

What one call costs · 19 distinct outcomes

One number would be a lie: the work depends on which branch runs. These are every distinct cost wp_get_attachment_url() can have, taken from its control-flow graph on PHP 8.5.

WhenInstructionsCalls it makes
always10–13get_post()
!is_ssl()36get_post(), get_post_meta(), is_ssl(), apply_filters()
is_ssl()39–41get_post(), get_post_meta(), is_ssl(), is_admin(), apply_filters()
!is_ssl()40–52get_post(), get_post_meta(), wp_get_upload_dir(), is_ssl(), apply_filters()
!is_ssl()42get_post(), get_post_meta(), get_the_guid(), is_ssl(), apply_filters()
is_ssl()43–57get_post(), get_post_meta(), wp_get_upload_dir(), is_ssl(), is_admin(), apply_filters()
is_ssl() && !is_admin() && $pagenow !== "wp-login.php"45get_post(), get_post_meta(), is_ssl(), is_admin(), set_url_scheme(), apply_filters()
is_ssl()45–47get_post(), get_post_meta(), get_the_guid(), is_ssl(), is_admin(), apply_filters()
!is_ssl()46–58get_post(), get_post_meta(), wp_get_upload_dir(), get_the_guid(), is_ssl(), apply_filters()
is_ssl() && !is_admin() && $pagenow !== "wp-login.php"49–61get_post(), get_post_meta(), wp_get_upload_dir(), is_ssl(), is_admin(), set_url_scheme(), apply_filters()
is_ssl()49–63get_post(), get_post_meta(), wp_get_upload_dir(), get_the_guid(), is_ssl(), is_admin(), apply_filters()
is_ssl() && !is_admin() && $pagenow !== "wp-login.php"51get_post(), get_post_meta(), get_the_guid(), is_ssl(), is_admin(), set_url_scheme(), apply_filters()
7 further outcomes, up to 78 instructions
is_ssl() && !is_admin() && $pagenow !== "wp-login.php"55–67get_post(), get_post_meta(), wp_get_upload_dir(), get_the_guid(), is_ssl(), is_admin(), set_url_scheme(), apply_filters()
!is_ssl()63get_post(), get_post_meta(), wp_get_upload_dir(), _wp_get_attachment_relative_path(), trailingslashit(), wp_basename(), is_ssl(), apply_filters()
is_ssl()66–68get_post(), get_post_meta(), wp_get_upload_dir(), _wp_get_attachment_relative_path(), trailingslashit(), wp_basename(), is_ssl(), is_admin(), apply_filters()
!is_ssl()69get_post(), get_post_meta(), wp_get_upload_dir(), _wp_get_attachment_relative_path(), trailingslashit(), wp_basename(), get_the_guid(), is_ssl(), apply_filters()
is_ssl() && !is_admin() && $pagenow !== "wp-login.php"72get_post(), get_post_meta(), wp_get_upload_dir(), _wp_get_attachment_relative_path(), trailingslashit(), wp_basename(), is_ssl(), is_admin(), set_url_scheme(), apply_filters()
is_ssl()72–74get_post(), get_post_meta(), wp_get_upload_dir(), _wp_get_attachment_relative_path(), trailingslashit(), wp_basename(), get_the_guid(), is_ssl(), is_admin(), apply_filters()
is_ssl() && !is_admin() && $pagenow !== "wp-login.php"78get_post(), get_post_meta(), wp_get_upload_dir(), _wp_get_attachment_relative_path(), trailingslashit(), wp_basename(), get_the_guid(), is_ssl(), is_admin(), set_url_scheme(), apply_filters()

Across PHP versions

PHPCompiledExecutedBranchesNotes
8.6-dev9110–7812
8.59110–7812
8.49110–78129 fewer instructions than PHP 8.3
8.310010–8412
8.210010–8412
8.110010–84121 fewer instruction than PHP 7.4
7.410110–8512

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.

Hooks and filters fired · 1

One hook fires while wp_get_attachment_url() runs, in this order:

  1. apply_filters( wp_get_attachment_url )filterline 7027 (+57 into the body)

    Filters the attachment URL.

Uses · 13

Show all 13
  • apply_filters()Calls the callback functions that have been added to a filter hook.

Used by · 42

Show all 42

Source code

function wp_get_attachment_url( $attachment_id = 0 ) {	global $pagenow; 	$attachment_id = (int) $attachment_id; 	$post = get_post( $attachment_id ); 	if ( ! $post ) {		return false;	} 	if ( 'attachment' !== $post->post_type ) {		return false;	} 	$url = '';	// Get attached file.	$file = get_post_meta( $post->ID, '_wp_attached_file', true );	if ( $file ) {		// Get upload directory.		$uploads = wp_get_upload_dir();		if ( $uploads && false === $uploads['error'] ) {			// Check that the upload base exists in the file location.			if ( str_starts_with( $file, $uploads['basedir'] ) ) {				// Replace file location with url location.				$url = str_replace( $uploads['basedir'], $uploads['baseurl'], $file );			} elseif ( str_contains( $file, 'wp-content/uploads' ) ) {				// Get the directory name relative to the basedir (back compat for pre-2.7 uploads).				$url = trailingslashit( $uploads['baseurl'] . '/' . _wp_get_attachment_relative_path( $file ) ) . wp_basename( $file );			} else {				// It's a newly-uploaded file, therefore $file is relative to the basedir.				$url = $uploads['baseurl'] . "/$file";			}		}	} 	/*	 * If any of the above options failed, Fallback on the GUID as used pre-2.7,	 * not recommended to rely upon this.	 */	if ( ! $url ) {		$url = get_the_guid( $post->ID );	} 	// On SSL front end, URLs should be HTTPS.	if ( is_ssl() && ! is_admin() && 'wp-login.php' !== $pagenow ) {		$url = set_url_scheme( $url );	} 	/**	 * Filters the attachment URL.	 *	 * @since 2.1.0	 *	 * @param string $url           URL for the given attachment.	 * @param int    $attachment_id Attachment post ID.	 */	$url = apply_filters( 'wp_get_attachment_url', $url, $post->ID ); 	if ( ! $url ) {		return false;	} 	return $url;}

Changelog

Introduced in 2.1.0. Unchanged from 6.7.7 through 7.1.0.

  1. 6.7.7
  2. 6.8.8
  3. 6.9.7
  4. 7.0.4
  5. 7.1.0

Signature, return type and hooks compared across 5 parsed releases.

About this page

Parsed data
Generated from the wordpress-develop 6.9.7 tag, from src/wp-includes/post.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.