AdminUIExtensionError: No extension API found

Hi there,

Starting today, we’ve been seeing issues with the Admin Extensions.

Specifically, there’s a fatal error when trying to leverage the useApi(target) hook.


import { useApi } from "@shopify/ui-extensions-react/admin";

const target = "admin.customer-details.block.render";
const { data } = useApi(TARGET);

// results in thrown error:
// AdminUIExtensionError: No extension api found.

Admin API version 2024-07

@shopify/ui-extensions: “2024.4.x”
@shopify/ui-extensions-react": “2024.4.x”

Hey Dylan. Thanks for reaching out.

Would you mind adding a bit more to the code snippet to show how and where you’re using the hook?

Thanks for the quick reply @Olavo

So more context, I’m using a monorepo to share code between separate admin block extensions.

This has never been officially supported, but it worked just fine for some time. That is until today.

Basically I had a common package used to pass the target dynamically:


// order admin block

export const TARGET = "admin.order-details.block.render";

import { reactExtension } from "@shopify/ui-extensions-react/admin";
import { BlockApp } from '@/package/shared';

export default reactExtension(TARGET, () => {
  return <WorkspaceAppBlock target={TARGET} />;
});


// customer admin block

export const TARGET = "admin.customer-details.block.render";

import { reactExtension } from "@shopify/ui-extensions-react/admin";
import { BlockApp } from '@/package/shared';

export default reactExtension(TARGET, () => {
  return <WorkspaceAppBlock target={TARGET} />;
});

// package/shared
import { AdminBlock } from "@shopify/ui-extensions-react/admin";
import TargetProvider from "./providers/TargetProvider";


// this BlockApp can be used in different contexts, and the target prop allows the implementations to switch APIs, queries, mutations, etc based on the target
export function BlockApp({ target }) {
  return (
    <AdminBlock title="My Extension">
      <TargetProvider target={target}>
         {/* rest of my components code */}
      </TargetProvider>
    </AdminBlock>
  )
}

Then I had a useTarget hook to leverage the target dynamically across these different admin extensions.

But it looks like that no longer works. Probably not a supported use case, but it was really nice to be able to provide similar functionality across different extensions without having to manage copy and pasted code.

The only solution I’ve found so far is to copy and paste the shared code directly into the individual extensions now.

Definitely a nice setup. More scalable for sure. I’d expect it to be possible, so hopefully it’s a simple fix.

For now, I’ll try to reproduce it on my side. If you have a minimal reproducible version of the issue on a repo, it’d make it easier for me to debug your use-case.

Thanks @Olavo - it would be really nice if workspace packages could be supported :folded_hands:

Here’s a few more snippets for context.

// TargetProvider.jsx
import { createContext } from "react";
// Create a context for the check state
export const TargetContext = createContext(null);

// Create a provider component
export default ({ target, children }) => {
  console.log(`TARGET :: ${target}`);

  return (
    <TargetContext.Provider
      value={{
        target,
      }}
    >
      {children}
    </TargetContext.Provider>
  );
};


Example implementation where the bug occurs:


import { useApi, Text } from "@shopify/ui-extensions-react/admin";
import { useTarget } from "../hooks/useTarget";

export default function SharedComponent() {
  const { data } = useApi(target);
  return (
    <Text>{ JSON.stringify(data) }</Text>
  )
}

// results in thrown error, even when `TARGET: ${target name}` is logged to console
// AdminUIExtensionError: No extension api found.

You can skip the provider implementation and try to use useApi(TARGET) in the root component of the shared package and the error still happens.

So it’s just a general incompatibility at this time I think.


// package/shared
import { AdminBlock, useApi } from "@shopify/ui-extensions-react/admin";
import TargetProvider from "./providers/TargetProvider";

// this BlockApp can be used in different contexts, and the target prop allows the implementations to switch APIs, queries, mutations, etc based on the target
export function BlockApp({ target }) {

   // results in the error
  const { data } = useApi(target);
  // also hardcoding the target results in the same error:
  const { data } = useApi("admin.order-details.block.render");

  return (
    <AdminBlock title="My Extension">
         {/* rest of my components code */}
    </AdminBlock>
  )
}

So it looks like shared packages just aren’t feasible anymore from what I can tell.

Where you export your block extension implementation, could you try wrapping it with the reactExtension function?

import {reactExtension} from "@shopify/ui-extensions-react/admin";

import {BlockApp} from '@/package/shared';

export const TARGET = "admin.order-details.block.render";

// Export the `reactExtension` call instead of the component here
export default reactExtension(TARGET, () => <BlockApp target={TARGET} />);

Sorry about that, yes I did use reactExtension in my own copy, but my examples were missing that import.

I updated my example code in the post to accurately show the reactExtension import and usage.

So that isn’t the root of it.

I’m not sure how you’ve set this up on your repo, but, seeing that I didn’t have the issue, what comes to mind here it that maybe you have multiple versions of react active. Are you building the shared packages as a library? Does it have have react as a dependency?


Here’s my attempt to reproduce the issue. Its seems to be working, though it’s a simple implementation. If you want to try it out, clone the repo, run:

pnpm i && pnpm shopify dev --reset

You should see the extensions on the product and order details pages.

Nice, thanks for the repo. You took a slightly different approach.

I’ve explicitly defined a package.json in the packages/shared so that way react is pinned to the same version as the extensions.

And that means it’s possible to accidentally have different @shopify/ui-extensions-react and @shopify/ui-extensions versions from your consuming extensions by accident.

Switching the shared package’s ui-extension to the same version (2024-07) as the order/customer extension packages seems to have fixed the issue.

Ah! Yea, that’s likely the case then. Monorepos are tricky with this kinda thing and it’s easy to fall into this situation. You can probably set those packages as peer dependencies on the shared package and make sure they’re treated as external by the bundler.

1 Like

Good call, I’ll take a look into that.

Thanks so much for building that PoC for me to compare against. That’s what ultimately lead me to realize the versions were out of sync.

Turns out it was user error after all, although this is definitely not an official way of doing things :laughing:

2 Likes

No worries! Glad it worked out.
It’s great to explore these types of patterns. Don’t get discouraged and keep on building! We’re here if you need help.