To find a WordPress page ID, go to Pages, All Pages, open the page, and read the number after post= in the address bar: in wp-admin/post.php?post=42&action=edit, the ID is 42. Posts, media and custom post types work the same way, because they all live in one table and share one numbering.
That covers most people. The rest of this post is for the cases where it does not: you have no login, you need fifty IDs at once, you are writing PHP, or an ID you hardcoded stopped working after the site was moved.
That last one is the reason to read on.
Every guide on the first page of results explains where the number is. None of the ones we read explains when it stops being the same number.
How do I find a page ID in the WordPress dashboard?
Two ways, and both read the same URL.
- Hover. In Pages, All Pages, hover over the page title. The browser shows the link target in the bottom corner, ending in
post=42&action=edit. - Open it. Click the title. The block editor loads at
post.php?post=42&action=edit, and the number stays in the address bar while you edit.
For posts, start from Posts, All Posts instead. Categories and tags use tag_ID= in their edit URL, users use user_id=, and media items opened from the grid use item=.
Is there an ID column in Screen Options?
Not in core WordPress. Several popular guides tell you to tick an "ID" box under Screen Options, but the core pages list only has a checkbox, Title, Author, Comments and Date (plus Categories and Tags on posts).
If you see an ID column, a plugin or your theme added it. That is fine, just know it will vanish on a site without that plugin.
Without a plugin, the ID is still on the list screen. Each table row is rendered as <tr id="post-42">, so right-click a row, choose Inspect, and read it off the row element.
How do I find a page ID without logging in?
Three places on the public page carry it, and you can check them from a terminal.
The body class. Themes that call body_class() (almost all of them) print page-id-42 on a page and postid-42 on a post. View the source and search for page-id-.
Child pages also get parent-pageid- with the parent's ID.
The HTTP headers. WordPress sends two Link headers on a single page or post, and both contain the ID:
curl -sI https://example.com/about/ | grep -i '^link'
link: <https://example.com/wp-json/>; rel="https://api.w.org/"
link: <https://example.com/wp-json/wp/v2/pages/42>; rel="alternate"; title="JSON"; type="application/json"
link: <https://example.com/?p=42>; rel=shortlink
The ?p=42 shortlink is the one most people recognise.
One exception: on the page set as your static front page, core makes the shortlink the home URL itself, with no ID in it. The REST alternate link still carries the ID there.
The REST API. If you know the slug, ask for the ID directly:
curl -s 'https://example.com/wp-json/wp/v2/pages?slug=about&_fields=id,link'
Unauthenticated, this only returns published pages. Some security plugins strip the headers or block the REST API for visitors, so if all three come back empty, that is the likely reason rather than a missing ID.
How do I get every page ID at once?
Use WP-CLI. One command lists every page with its ID, title, slug and status:
wp post list --post_type=page --fields=ID,post_title,post_name,post_status
Add --format=csv to paste it into a spreadsheet. Swap page for post, product or any custom post type.
If you have a URL and want its ID, there is a command for exactly that:
wp post url-to-id https://example.com/about/
It calls core's url_to_postid(), so it follows your permalink structure.
Two IDs are worth knowing by name, because they live in options rather than in the page itself:
wp option get page_on_front # the static front page
wp option get page_for_posts # the page that lists your posts
If you do not have WP-CLI yet, our install guide covers it. On a SandyWP sandbox it is already there over SSH.
How do I get the page ID in PHP?
It depends on where your code runs.
| Situation | Use | Why |
|---|---|---|
| Inside The Loop | get_the_ID() |
Reads the current global $post. |
| Header, footer, sidebar, a hook | get_queried_object_id() |
Reads what the URL asked for, not what the loop is on. |
| You have a URL | url_to_postid( $url ) |
Resolves it through the rewrite rules. |
| You have a slug | get_page_by_path( 'about' ) |
Returns the page object; ->ID from there. |
The difference between the first two catches people on the posts page.
When a page is set as Posts page under Settings, Reading, WordPress fills the global $post with the first blog post in the list, not with that page. So get_the_ID() in header.php returns the newest post's ID.
get_queried_object_id() returns the page's.
Why are my page IDs not in order?
Because pages are not the only thing in the table. Pages, posts, media, revisions, autosaves, menu items, reusable blocks, block theme templates and global styles all take their ID from the same auto-increment counter in wp_posts.
Create a page, save it four times, add a menu item and upload two images, and the next page might be 51 when the previous one was 42.
Nothing is wrong. Gaps are normal and the numbers carry no meaning beyond identity.
Revisions are usually the biggest consumer. Our post on WordPress revisions shows how to cap them.
Does the page ID change when you migrate or clone a site?
It depends entirely on how the content moves.
A full database copy keeps every ID. A migration plugin that exports SQL, wp db export followed by wp db import, a host's staging copy, or our Cloner all move the wp_posts table as rows. Page 42 is still page 42 on the other side, and the URL replacement they run does not touch IDs.
A WXR import (Tools, Export, then Tools, Import) does not promise to. We read the WordPress Importer plugin (0.9.7) and core's wp_insert_post() to see what actually happens:
- The importer asks for the original ID by passing it as
import_id. - Core uses that ID only if no row in
wp_postsalready has it. If a revision, an image or another page already took 42 on the target site, the page gets the next free number instead. - On a fresh install, most IDs survive. On a site that already has content, expect collisions.
The importer keeps a map of old to new IDs and fixes what it knows about: page parents, menu items pointing at pages, featured images, and (when you tick the option) the old site's URLs inside content.
Here is what it does not fix:
- Numbers inside post content. Block attributes such as
"id":42, a reusable block's"ref":42, and shortcodes like[gallery ids="12,13"]or[contact-form-7 id="88"]keep the old numbers. - Plugin settings in other post meta. Page builders, field plugins and form plugins that store an ID in their own meta keys are not on the importer's list.
- Options. WXR files contain no options at all, so
page_on_front,page_for_posts, a WooCommerce shop page or a privacy policy page have to be set again by hand. - Your code.
is_page( 42 )in a theme,.page-id-42in custom CSS, or a redirect rule keyed on?p=42.
The importer also skips a page it thinks already exists (same title, date and post type) and points everything at the existing one. That is helpful on a re-run and confusing when two sites happen to share a page.
Our guide to exporting a WordPress site covers which method moves what.
How do I stop hardcoding page IDs?
Use the slug, which travels with the page and is the same on every copy:
// Instead of is_page( 42 )
if ( is_page( 'about' ) ) { ... }
// Instead of get_permalink( 42 )
$page = get_page_by_path( 'about' );
if ( $page ) {
$url = get_permalink( $page );
}
is_page() accepts an ID, a title, a slug, or an array of them, so the change is usually one argument.
For CSS, target a class you control instead of .page-id-42. Most themes let you add a CSS class to a block or page, and it survives an import untouched.
Slugs are not perfect either. They change when someone renames a page and edits its permalink, so treat them as editable content.
They still beat an auto-increment number for anything that has to work on more than one site.
How do I check IDs before moving content to live?
Do the move on a copy first, then compare the IDs the code depends on. With WP-CLI on both sides it takes a minute:
wp post list --post_type=page --fields=ID,post_name --format=csv > before.csv
# run the import on the copy
wp post list --post_type=page --fields=ID,post_name --format=csv > after.csv
diff before.csv after.csv
Then grep your theme and custom plugins for is_page(, get_permalink( and page-id- with a number after them. Each hit is something that will break if its row in the diff changed.
A sandbox is a good place for this because the import is the risky step and you want to throw away the result. Clone the live site into one, run the WXR import there, and diff.
If something breaks, delete the sandbox and nothing else noticed.
To be honest about the limits: if your plan is a full database copy, there is nothing to test here, because IDs do not change. This check is for imports, content merges between two live sites, and anyone moving pages with WXR or a plugin that works the same way.
The short version
- Editor URL: the number after
post=is the ID. Posts, pages and media share one sequence. - There is no ID column in core Screen Options. A plugin adds it.
- Without a login:
page-id-42in the body class, orcurl -sIand read theLinkheaders. - Bulk:
wp post list --post_type=page --fields=ID,post_title,post_name. - PHP:
get_queried_object_id()outside the loop, especially on the posts page. - A full database copy keeps IDs. A WXR import keeps them only where the number is free, and leaves IDs inside content, plugin meta and options alone.
- Use slugs in code that has to work on more than one site.
