Skip to content

Image block: Validate attachment ID exists before treating image as local#77178

Merged
ramonjd merged 3 commits intoWordPress:trunkfrom
samvaidya:fix/validate-attachment-id-before-treating-as-local
Apr 15, 2026
Merged

Image block: Validate attachment ID exists before treating image as local#77178
ramonjd merged 3 commits intoWordPress:trunkfrom
samvaidya:fix/validate-attachment-id-before-treating-as-local

Conversation

@samvaidya
Copy link
Copy Markdown
Contributor

Description

When block markup is copied from one WordPress site to another, image blocks carry attachment IDs from the source site. The editor assumes these IDs reference local attachments and suppresses the "Upload to Media Library" button.

This change piggybacks on the existing getEntityRecord call (which already fires when an image block is selected) and adds a hasFinishedResolution check. Once resolution confirms the attachment doesn't exist locally, the invalid id is cleared from block attributes, allowing the existing external image upload flow to activate correctly.

How it works

  1. Block arrives with id: 107491 from Site A
  2. Existing useSelect fires getEntityRecord('postType', 'attachment', 107491) - returns 404
  3. New hasFinishedResolution check confirms resolution is complete (not still loading)
  4. New useEffect detects: id exists, resolution finished, but no record found - clears the id
  5. isExternalImage(undefined, url) now returns true
  6. Existing external image upload flow activates - "Upload to Media Library" button appears

What this doesn't change

  • Zero new API calls. The REST call was already happening.
  • hasFinishedResolution is a local state check (zero network cost)
  • Only runs when isSingleSelected is true (user clicked on the specific image block)
  • Valid local images are unaffected

Future scope

  • ID collision detection: If Site B has a different attachment with the same ID as Site A, the current fix won't detect the mismatch. This was discussed in Image block: Add actual attachment post checking when selecting a single image block #74156 and agreed to be out of scope for the MVP. URL matching could address this in a follow-up.
  • Other attachment blocks: As noted by @ramonjd in the issue discussion, this pattern should eventually extend to video, audio, and file blocks. Those blocks don't currently have external upload logic, so extending to them would require building that flow from scratch.

Testing Instructions

  1. Start a local WordPress dev environment
  2. Create a new post, switch to Code Editor, paste:
<!-- wp:image {"id":999999,"sizeSlug":"large"} -->
<figure class="wp-block-image size-large"><img src="https://picsum.photos/id/237/800/600" alt="" class="wp-image-999999"/></figure>
<!-- /wp:image -->
  1. Exit code editor, click the image block
  2. Expected: "Upload to Media Library" button appears in the toolbar
  3. Click the button - image uploads successfully
  4. Verify no regression: Upload a real image via the image block, click it - no upload button appears (correct)
  5. Multiple images: Paste multiple image blocks with different fake IDs, click each - upload button appears on each

Fixes #74156

…ocal

When block markup is copied from one WordPress site to another, image
blocks carry attachment IDs from the source site. The editor assumes
these IDs reference local attachments and suppresses the Upload to
Media Library button.

This change adds a check: after the entity record resolution completes,
if the attachment ID does not match a local record, the ID is cleared
from the block attributes. This allows the existing external image
upload flow to activate correctly.

Fixes WordPress#74156

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions bot added the [Package] Block library /packages/block-library label Apr 9, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: samvaidya <samvaidya@git.wordpress.org>
Co-authored-by: ramonjd <ramonopoly@git.wordpress.org>
Co-authored-by: andrewserong <andrewserong@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions github-actions bot added the First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository label Apr 9, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @samvaidya! In case you missed it, we'd love to have you join us in our Slack community.

If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information.

Comment on lines +329 to +340
const hasResolved =
id && isSingleSelected
? select( coreStore ).hasFinishedResolution(
'getEntityRecord',
[
'postType',
'attachment',
id,
{ context: 'view' },
]
)
: false;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR!

Curious, are we only interested in 404s?

I'm just wondering if getResolutionError would be more useful.

hasFinishedResolution returns true for other failed resolutions, e.g., 403, 500

			const resolutionError =
				id && isSingleSelected
					? select( coreStore ).getResolutionError(
							'getEntityRecord',
							[
								'postType',
								'attachment',
								id,
								{ context: 'view' },
							]
					  )
					: null;
// Later...

	useEffect( () => {
		if ( ! id || ! isSingleSelected ) {
			return;
		}
		// Only clear for confirmed 404s. apiFetch throws the Response object
		// for HTTP errors (parse: false), so checking .status === 404 avoids
		// incorrectly clearing the id on 403, 500, or network failures, which
		// would cause data loss for valid local attachments.
		if ( attachmentResolutionError?.status === 404 ) {
			setAttributes( { id: undefined } );
		}
	}, [ id, isSingleSelected, attachmentResolutionError, setAttributes ] );

What that satisfy the requirements?

Copy link
Copy Markdown
Contributor Author

@samvaidya samvaidya Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch, thanks @ramonjd! You're right -- hasFinishedResolution is too broad. A 500 or 403 would also "finish" but shouldn't trigger clearing the ID, since the attachment may exist fine and just be temporarily unreachable or permission-restricted. Clearing it in those cases would be data loss.

Switched to getResolutionError and added a ?.status === 404 check so we only clear on confirmed not-found responses. Updated in 1066e9f.

Thanks too for the learning opportunity -- this is a great example of "be specific about what you're catching." 🙏

@andrewserong
Copy link
Copy Markdown
Contributor

+1 thanks for the PR! I noticed something while testing which isn't introduced in this PR, but if your environment supports client-side media processing, then when you click the "Upload to Media Library" button in the block toolbar, it'll fire off more notices than expected when the image uploads:

image

I can look into a fix for this separately, so don't let it stop you proceeding with this PR! It's otherwise testing well for me, but @ramonjd raises a valid point about explicitly checking for 404s.

Other than that, I think this is a good pragmatic approach, and as you mention in the description this works well for an MVP that should hopefully capture the majority of cases, with the caveat that it's possible for an id to exist in both environments that both match a valid id but with different images. It could be worth mentioning that limitation in the code comment just in case it helps our future selves remember the limitation 🙂

@andrewserong
Copy link
Copy Markdown
Contributor

I noticed something while testing which isn't introduced in this PR, but if your environment supports client-side media processing, then when you click the "Upload to Media Library" button in the block toolbar, it'll fire off more notices than expected when the image uploads:

Fix here: #77218

samvaidya and others added 2 commits April 14, 2026 21:38
Addresses @ramonjd's review: replace hasFinishedResolution with
getResolutionError so we can specifically check for 404 status.
This prevents the id from being cleared on transient errors
(500, 403, network failures), which would cause data loss for
valid local attachments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per @andrewserong's review: note the limitation that if a different
attachment with the same id exists on the destination site, the lookup
will succeed and use the wrong local image. URL matching could address
this in a follow-up.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@samvaidya
Copy link
Copy Markdown
Contributor Author

Thanks for testing and the feedback @andrewserong! 🙏

And thanks for taking care of the extra notices issue separately in #77218!

Great suggestion on documenting the ID-collision limitation in the code comment itself. Added in 8a0c693.

//
// Known limitation: if a different attachment with the same id happens to
// exist on the destination site, the lookup will succeed and the wrong
// local image will be used. URL matching could address this in a follow-up.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like a good follow up. Not bullet-proof, but even a filename check could be helpful to alert the user (and they can manually resolve if necessary).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I forgot to mention, I did test this path and the original attachment is loaded:

Image

When converting to a gallery, the original image is reloaded

Kapture.2026-04-15.at.16.14.23.mp4

Copy link
Copy Markdown
Member

@ramonjd ramonjd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is testing well for me. Cheers!

The 404 response, and the upload button are present. Upload works.

Image

When the id isn't present in the image attributes, e.g.,

<!-- wp:image {"sizeSlug":"large"} -->
<figure class="wp-block-image size-large"><img src="https://picsum.photos/id/237/800/600" alt="" /></figure>
<!-- /wp:image -->

the upload button appears as expected.

@ramonjd ramonjd merged commit 433d058 into WordPress:trunk Apr 15, 2026
42 of 44 checks passed
@github-actions github-actions bot added this to the Gutenberg 23.0 milestone Apr 15, 2026
// clearing the id on 403, 500, or network failures, which would
// cause data loss for valid local attachments.
if ( attachmentResolutionError?.status === 404 ) {
setAttributes( { id: undefined } );
Copy link
Copy Markdown
Member

@Mamaduka Mamaduka Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can create an undo trap. A user undoes id removal, the whole effect runs again, etc. It's good practice to mark similar changes in effect as non-persistent.

I'll create a follow-up.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for spotting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Block] Image Affects the Image Block First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository [Package] Block library /packages/block-library [Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Image block: Add actual attachment post checking when selecting a single image block

5 participants