diff --git a/packages/block-editor/src/components/off-canvas-editor/block-edit-button.js b/packages/block-editor/src/components/off-canvas-editor/block-edit-button.js
new file mode 100644
index 0000000000000..4d44bc658c74d
--- /dev/null
+++ b/packages/block-editor/src/components/off-canvas-editor/block-edit-button.js
@@ -0,0 +1,215 @@
+/**
+ * WordPress dependencies
+ */
+import { edit } from '@wordpress/icons';
+import { __ } from '@wordpress/i18n';
+import { useMemo, useState } from '@wordpress/element';
+import { Button, Modal } from '@wordpress/components';
+import { useSelect, useDispatch } from '@wordpress/data';
+import { createBlock as create } from '@wordpress/blocks';
+
+/**
+ * Internal dependencies
+ */
+import { store as blockEditorStore } from '../../store';
+/**
+ * External dependencies
+ */
+// import InspectorControls from '../../components/inspector-controls';
+
+// copied from packages/block-library/src/page-list/edit.js
+
+// We only show the edit option when page count is <= MAX_PAGE_COUNT
+// Performance of Navigation Links is not good past this value.
+const MAX_PAGE_COUNT = 100;
+
+const usePageData = () => {
+	// 1. Grab editor settings
+	// 2. Call the selector when we need it
+	const { pages } = useSelect( ( select ) => {
+		const { getSettings } = select( blockEditorStore );
+
+		return {
+			pages: getSettings().__experimentalFetchPageEntities( {
+				orderby: 'menu_order',
+				order: 'asc',
+				_fields: [ 'id', 'link', 'parent', 'title', 'menu_order' ],
+				per_page: -1,
+				context: 'view',
+			} ),
+		};
+	}, [] );
+
+	return useMemo( () => {
+		// TODO: Once the REST API supports passing multiple values to
+		// 'orderby', this can be removed.
+		// https://core.trac.wordpress.org/ticket/39037
+		const sortedPages = [ ...( pages ?? [] ) ].sort( ( a, b ) => {
+			if ( a.menu_order === b.menu_order ) {
+				return a.title.rendered.localeCompare( b.title.rendered );
+			}
+			return a.menu_order - b.menu_order;
+		} );
+		const pagesByParentId = sortedPages.reduce( ( accumulator, page ) => {
+			const { parent } = page;
+			if ( accumulator.has( parent ) ) {
+				accumulator.get( parent ).push( page );
+			} else {
+				accumulator.set( parent, [ page ] );
+			}
+			return accumulator;
+		}, new Map() );
+
+		return {
+			pages, // necessary for access outside the hook
+			pagesByParentId,
+			totalPages: pages?.length ?? null,
+		};
+	}, [ pages ] );
+};
+
+// copied from convert-to-links-modal.js
+/**
+ * WordPress dependencies
+ */
+
+const convertSelectedBlockToNavigationLinks =
+	( { pages, clientId, replaceBlock, createBlock } ) =>
+	() => {
+		if ( ! pages?.length ) {
+			return;
+		}
+
+		const linkMap = {};
+		const navigationLinks = [];
+		pages.forEach( ( { id, title, link: url, type, parent } ) => {
+			// See if a placeholder exists. This is created if children appear before parents in list.
+			const innerBlocks = linkMap[ id ]?.innerBlocks ?? [];
+			linkMap[ id ] = createBlock(
+				'core/navigation-link',
+				{
+					id,
+					label: title.rendered,
+					url,
+					type,
+					kind: 'post-type',
+				},
+				innerBlocks
+			);
+
+			if ( ! parent ) {
+				navigationLinks.push( linkMap[ id ] );
+			} else {
+				if ( ! linkMap[ parent ] ) {
+					// Use a placeholder if the child appears before parent in list.
+					linkMap[ parent ] = { innerBlocks: [] };
+				}
+				const parentLinkInnerBlocks = linkMap[ parent ].innerBlocks;
+				parentLinkInnerBlocks.push( linkMap[ id ] );
+			}
+		} );
+
+		// Transform all links with innerBlocks into Submenus. This can't be done
+		// sooner because page objects have no information on their children.
+
+		const transformSubmenus = ( listOfLinks ) => {
+			listOfLinks.forEach( ( block, index, listOfLinksArray ) => {
+				const { attributes, innerBlocks } = block;
+				if ( innerBlocks.length !== 0 ) {
+					transformSubmenus( innerBlocks );
+					const transformedBlock = createBlock(
+						'core/navigation-submenu',
+						attributes,
+						innerBlocks
+					);
+					listOfLinksArray[ index ] = transformedBlock;
+				}
+			} );
+		};
+
+		transformSubmenus( navigationLinks );
+
+		replaceBlock( clientId, navigationLinks );
+	};
+
+const ConvertToLinksModal = ( { onClose, clientId, pages } ) => {
+	const hasPages = !! pages?.length;
+
+	const { replaceBlock } = useDispatch( blockEditorStore );
+
+	return (
+		<Modal
+			closeLabel={ __( 'Close' ) }
+			onRequestClose={ onClose }
+			title={ __( 'Convert to links' ) }
+			className={ 'wp-block-page-list-modal' }
+			aria={ { describedby: 'wp-block-page-list-modal__description' } }
+		>
+			<p id={ 'wp-block-page-list-modal__description' }>
+				{ __(
+					'To edit this navigation menu, convert it to single page links. This allows you to add, re-order, remove items, or edit their labels.'
+				) }
+			</p>
+			<p>
+				{ __(
+					"Note: if you add new pages to your site, you'll need to add them to your navigation menu."
+				) }
+			</p>
+			<div className="wp-block-page-list-modal-buttons">
+				<Button variant="tertiary" onClick={ onClose }>
+					{ __( 'Cancel' ) }
+				</Button>
+				<Button
+					variant="primary"
+					disabled={ ! hasPages }
+					onClick={ convertSelectedBlockToNavigationLinks( {
+						pages,
+						replaceBlock,
+						clientId,
+						createBlock: create,
+					} ) }
+				>
+					{ __( 'Convert' ) }
+				</Button>
+			</div>
+		</Modal>
+	);
+};
+
+const BlockEditButton = ( { label, clientId } ) => {
+	const { toggleBlockHighlight } = useDispatch( blockEditorStore );
+	const [ convertModalOpen, setConvertModalOpen ] = useState( false );
+	const { pages, totalPages } = usePageData();
+
+	const block = useSelect(
+		( select ) => {
+			return select( blockEditorStore ).getBlock( clientId );
+		},
+		[ clientId ]
+	);
+
+	const onClick = () => {
+		toggleBlockHighlight( clientId, true );
+		setConvertModalOpen( ! convertModalOpen );
+	};
+
+	const allowConvertToLinks =
+		'core/page-list' === block.name && totalPages <= MAX_PAGE_COUNT;
+
+	return (
+		<>
+			{ convertModalOpen && (
+				<ConvertToLinksModal
+					onClose={ () => setConvertModalOpen( false ) }
+					clientId={ clientId }
+					pages={ pages }
+				/>
+			) }
+			{ allowConvertToLinks && (
+				<Button icon={ edit } label={ label } onClick={ onClick } />
+			) }
+		</>
+	);
+};
+
+export default BlockEditButton;
diff --git a/packages/block-editor/src/components/off-canvas-editor/block.js b/packages/block-editor/src/components/off-canvas-editor/block.js
index e338c4c35c957..50119ebddfb57 100644
--- a/packages/block-editor/src/components/off-canvas-editor/block.js
+++ b/packages/block-editor/src/components/off-canvas-editor/block.js
@@ -33,6 +33,7 @@ import {
 } from '../block-mover/button';
 import ListViewBlockContents from './block-contents';
 import BlockSettingsDropdown from '../block-settings-menu/block-settings-dropdown';
+import BlockEditButton from './block-edit-button';
 import { useListViewContext } from './context';
 import { getBlockPositionDescription } from './utils';
 import { store as blockEditorStore } from '../../store';
@@ -132,6 +133,14 @@ function ListViewBlock( {
 		  )
 		: __( 'Options' );
 
+	const editAriaLabel = blockInformation
+		? sprintf(
+				// translators: %s: The title of the block.
+				__( 'Edit %s block' ),
+				blockInformation.title
+		  )
+		: __( 'Edit' );
+
 	const { isTreeGridMounted, expand, collapse } = useListViewContext();
 
 	const hasSiblings = siblingBlockCount > 0;
@@ -146,6 +155,11 @@ function ListViewBlock( {
 		{ 'is-visible': isHovered || isFirstSelectedBlock }
 	);
 
+	const listViewBlockEditClassName = classnames(
+		'block-editor-list-view-block__menu-cell',
+		{ 'is-visible': isHovered || isFirstSelectedBlock }
+	);
+
 	// If ListView has experimental features related to the Persistent List View,
 	// only focus the selected list item on mount; otherwise the list would always
 	// try to steal the focus from the editor canvas.
@@ -307,26 +321,44 @@ function ListViewBlock( {
 			) }
 
 			{ showBlockActions && (
-				<TreeGridCell
-					className={ listViewBlockSettingsClassName }
-					aria-selected={ !! isSelected || forceSelectionContentLock }
-				>
-					{ ( { ref, tabIndex, onFocus } ) => (
-						<BlockSettingsDropdown
-							clientIds={ dropdownClientIds }
-							icon={ moreVertical }
-							label={ settingsAriaLabel }
-							toggleProps={ {
-								ref,
-								className: 'block-editor-list-view-block__menu',
-								tabIndex,
-								onFocus,
-							} }
-							disableOpenOnArrowDown
-							__experimentalSelectBlock={ updateSelection }
-						/>
-					) }
-				</TreeGridCell>
+				<>
+					<TreeGridCell
+						className={ listViewBlockEditClassName }
+						aria-selected={
+							!! isSelected || forceSelectionContentLock
+						}
+					>
+						{ () => (
+							<BlockEditButton
+								label={ editAriaLabel }
+								clientId={ clientId }
+							/>
+						) }
+					</TreeGridCell>
+					<TreeGridCell
+						className={ listViewBlockSettingsClassName }
+						aria-selected={
+							!! isSelected || forceSelectionContentLock
+						}
+					>
+						{ ( { ref, tabIndex, onFocus } ) => (
+							<BlockSettingsDropdown
+								clientIds={ dropdownClientIds }
+								icon={ moreVertical }
+								label={ settingsAriaLabel }
+								toggleProps={ {
+									ref,
+									className:
+										'block-editor-list-view-block__menu',
+									tabIndex,
+									onFocus,
+								} }
+								disableOpenOnArrowDown
+								__experimentalSelectBlock={ updateSelection }
+							/>
+						) }
+					</TreeGridCell>
+				</>
 			) }
 		</ListViewLeaf>
 	);
diff --git a/packages/edit-site/src/components/block-editor/index.js b/packages/edit-site/src/components/block-editor/index.js
index 0f42214b45442..62e79ac14cefa 100644
--- a/packages/edit-site/src/components/block-editor/index.js
+++ b/packages/edit-site/src/components/block-editor/index.js
@@ -122,6 +122,16 @@ export default function BlockEditor( { setIsInserterOpen } ) {
 		[ settingsBlockPatternCategories, restBlockPatternCategories ]
 	);
 
+	const { fetchPagesEntities } = useSelect( ( select ) => {
+		const { getEntityRecords } = select( coreStore );
+
+		return {
+			fetchPagesEntities: ( options = {} ) => {
+				return getEntityRecords( 'postType', 'page', options );
+			},
+		};
+	}, [] );
+
 	const settings = useMemo( () => {
 		const {
 			__experimentalAdditionalBlockPatterns,
@@ -133,6 +143,7 @@ export default function BlockEditor( { setIsInserterOpen } ) {
 			...restStoredSettings,
 			__experimentalBlockPatterns: blockPatterns,
 			__experimentalBlockPatternCategories: blockPatternCategories,
+			__experimentalFetchPageEntities: fetchPagesEntities,
 		};
 	}, [ storedSettings, blockPatterns, blockPatternCategories ] );
 
diff --git a/packages/edit-site/src/components/sidebar-edit-mode/navigation-item-editor/index.js b/packages/edit-site/src/components/sidebar-edit-mode/navigation-item-editor/index.js
new file mode 100644
index 0000000000000..bb289e7b31983
--- /dev/null
+++ b/packages/edit-site/src/components/sidebar-edit-mode/navigation-item-editor/index.js
@@ -0,0 +1,36 @@
+/**
+ * WordPress dependencies
+ */
+import { FlexBlock, Flex } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+import { navigation } from '@wordpress/icons';
+
+/**
+ * Internal dependencies
+ */
+import DefaultSidebar from '../default-sidebar';
+
+export default function NavigationItemSidebar() {
+	return (
+		<DefaultSidebar
+			className="edit-site-navigation-item-sidebar"
+			identifier="edit-site/navigation-item"
+			title={ __( 'Navigation Item' ) }
+			icon={ navigation }
+			closeLabel={ __( 'Close navigation menu sidebar' ) }
+			panelClassName="edit-site-navigation-menu-sidebar__panel"
+			header={
+				<Flex>
+					<FlexBlock>
+						<strong>{ __( 'Navigation Menus' ) }</strong>
+						<span className="edit-site-navigation-sidebar__beta">
+							{ __( 'Beta' ) }
+						</span>
+					</FlexBlock>
+				</Flex>
+			}
+		>
+			<p>Hello, world</p>
+		</DefaultSidebar>
+	);
+}