diff --git a/packages/core-data/src/hooks/test/use-resource-permissions.js b/packages/core-data/src/hooks/test/use-resource-permissions.js
new file mode 100644
index 00000000000000..d1d41db3ddf360
--- /dev/null
+++ b/packages/core-data/src/hooks/test/use-resource-permissions.js
@@ -0,0 +1,117 @@
+/**
+ * WordPress dependencies
+ */
+import triggerFetch from '@wordpress/api-fetch';
+import { createRegistry, RegistryProvider } from '@wordpress/data';
+
+jest.mock( '@wordpress/api-fetch' );
+
+/**
+ * External dependencies
+ */
+import { act, render } from '@testing-library/react';
+
+/**
+ * Internal dependencies
+ */
+import { store as coreDataStore } from '../../index';
+import useResourcePermissions from '../use-resource-permissions';
+
+describe( 'useResourcePermissions', () => {
+ let registry;
+ beforeEach( () => {
+ jest.useFakeTimers();
+
+ registry = createRegistry();
+ registry.register( coreDataStore );
+ } );
+
+ afterEach( () => {
+ jest.runOnlyPendingTimers();
+ jest.useRealTimers();
+ } );
+
+ it( 'retrieves the relevant permissions for a key-less resource', async () => {
+ triggerFetch.mockImplementation( () => ( {
+ headers: {
+ Allow: 'POST',
+ },
+ } ) );
+ let data;
+ const TestComponent = () => {
+ data = useResourcePermissions( 'widgets' );
+ return
;
+ };
+ render(
+
+
+
+ );
+ expect( data ).toEqual( [
+ false,
+ {
+ status: 'IDLE',
+ isResolving: false,
+ canCreate: false,
+ },
+ ] );
+
+ // Required to make sure no updates happen outside of act()
+ await act( async () => {
+ jest.advanceTimersByTime( 1 );
+ } );
+
+ expect( data ).toEqual( [
+ true,
+ {
+ status: 'SUCCESS',
+ isResolving: false,
+ canCreate: true,
+ },
+ ] );
+ } );
+
+ it( 'retrieves the relevant permissions for a resource with a key', async () => {
+ triggerFetch.mockImplementation( () => ( {
+ headers: {
+ Allow: 'POST',
+ },
+ } ) );
+ let data;
+ const TestComponent = () => {
+ data = useResourcePermissions( 'widgets', 1 );
+ return ;
+ };
+ render(
+
+
+
+ );
+ expect( data ).toEqual( [
+ false,
+ {
+ status: 'IDLE',
+ isResolving: false,
+ canCreate: false,
+ canUpdate: false,
+ canDelete: false,
+ },
+ ] );
+
+ // Required to make sure no updates happen outside of act()
+ await act( async () => {
+ jest.advanceTimersByTime( 1 );
+ } );
+
+ expect( data ).toEqual( [
+ true,
+ {
+ status: 'SUCCESS',
+ isResolving: false,
+ canCreate: true,
+ canUpdate: false,
+ canDelete: false,
+ },
+ ] );
+ } );
+} );
diff --git a/packages/core-data/src/hooks/use-resource-permissions.ts b/packages/core-data/src/hooks/use-resource-permissions.ts
new file mode 100644
index 00000000000000..122eadc5759805
--- /dev/null
+++ b/packages/core-data/src/hooks/use-resource-permissions.ts
@@ -0,0 +1,120 @@
+/**
+ * Internal dependencies
+ */
+import { store as coreStore } from '../';
+import { Status } from './constants';
+import useQuerySelect from './use-query-select';
+
+interface GlobalResourcePermissionsResolution {
+ /** Can the current user create new resources of this type? */
+ canCreate: boolean;
+}
+interface SpecificResourcePermissionsResolution {
+ /** Can the current user update resources of this type? */
+ canUpdate: boolean;
+ /** Can the current user delete resources of this type? */
+ canDelete: boolean;
+}
+interface ResolutionDetails {
+ /** Resolution status */
+ status: Status;
+ /**
+ * Is the data still being resolved?
+ */
+ isResolving: boolean;
+}
+
+/**
+ * Is the data resolved by now?
+ */
+type HasResolved = boolean;
+
+type ResourcePermissionsResolution< IdType > = [
+ HasResolved,
+ ResolutionDetails &
+ GlobalResourcePermissionsResolution &
+ ( IdType extends void ? SpecificResourcePermissionsResolution : {} )
+];
+
+/**
+ * Resolves resource permissions.
+ *
+ * @param resource The resource in question, e.g. media.
+ * @param id ID of a specific resource entry, if needed, e.g. 10.
+ *
+ * @example
+ * ```js
+ * import { useResourcePermissions } from '@wordpress/core-data';
+ *
+ * function PagesList() {
+ * const { canCreate, isResolving } = useResourcePermissions( 'pages' );
+ *
+ * if ( isResolving ) {
+ * return 'Loading ...';
+ * }
+ *
+ * return (
+ *
+ * {canCreate ? () : false}
+ * // ...
+ *
+ * );
+ * }
+ *
+ * // Rendered in the application:
+ * //
+ * ```
+ *
+ * In the above example, when `PagesList` is rendered into an
+ * application, the appropriate permissions and the resolution details will be retrieved from
+ * the store state using `canUser()`, or resolved if missing.
+ *
+ * @return Entity records data.
+ * @template IdType
+ */
+export default function __experimentalUseResourcePermissions< IdType = void >(
+ resource: string,
+ id: IdType
+): ResourcePermissionsResolution< IdType > {
+ return useQuerySelect(
+ ( resolve ) => {
+ const { canUser } = resolve( coreStore );
+ const create = canUser( 'create', resource );
+ if ( ! id ) {
+ return [
+ create.hasResolved,
+ {
+ status: create.status,
+ isResolving: create.isResolving,
+ canCreate: create.hasResolved && create.data,
+ },
+ ];
+ }
+
+ const update = canUser( 'update', resource, id );
+ const _delete = canUser( 'delete', resource, id );
+ const isResolving =
+ create.isResolving || update.isResolving || _delete.isResolving;
+ const hasResolved =
+ create.hasResolved && update.hasResolved && _delete.hasResolved;
+
+ let status = Status.Idle;
+ if ( isResolving ) {
+ status = Status.Resolving;
+ } else if ( hasResolved ) {
+ status = Status.Success;
+ }
+ return [
+ hasResolved,
+ {
+ status,
+ isResolving,
+ canCreate: hasResolved && create.data,
+ canUpdate: hasResolved && update.data,
+ canDelete: hasResolved && _delete.data,
+ },
+ ];
+ },
+ [ resource, id ]
+ );
+}
diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js
index 05cace07c992b4..6914f376d72e43 100644
--- a/packages/core-data/src/index.js
+++ b/packages/core-data/src/index.js
@@ -68,6 +68,9 @@ export const store = createReduxStore( STORE_NAME, storeConfig() );
register( store );
export { default as EntityProvider } from './entity-provider';
+export { default as useEntityRecord } from './hooks/use-entity-record';
+export { default as useEntityRecords } from './hooks/use-entity-records';
+export { default as useResourcePermissions } from './hooks/use-resource-permissions';
export * from './entity-provider';
export * from './entity-types';
export * from './fetch';