npm package: @structdk/extension-sdk
Description: Framework agnostic SDK for building Struct extensions
Latest version: 1.0.6

# @structdk/extension-sdk

SDK for building extensions for [Struct PIM](https://struct.com). Extensions run as iframes embedded inside the Struct PIM UI and communicate with the host via a postMessage-based API. This SDK abstracts that messaging layer into a simple, typed API.

## Installation

```bash
npm install @structdk/extension-sdk
```

## Setup

Create an SDK instance using `createStructSDK`. Optionally pass the `hostOrigin` of your Struct PIM instance to restrict message acceptance to that origin only.

```ts
import { createStructSDK } from '@structdk/extension-sdk';

const struct = createStructSDK({ hostOrigin: 'https://your-struct-instance.struct.com' });
```

> If `hostOrigin` is not provided, all origins are accepted and a warning is printed to the console.

The returned object exposes `actions`, `events`, and a `destroy()` cleanup function.

---

## Actions (`struct.actions`)

Actions are messages sent **from your extension to Struct PIM**.

### `getContext`

Requests the current context from Struct PIM. Returns a promise that resolves with the context payload for the current extension point. This is the primary way to retrieve the session context (slug, language, user, entity info, etc.).

```ts
import { createStructSDK } from '@structdk/extension-sdk';
import type { TabContextPayload } from '@structdk/extension-sdk';

const struct = createStructSDK();
const ctx = await struct.actions.getContext<TabContextPayload>();
console.log('Entity:', ctx.entityType, ctx.entityId);
console.log('Slug:', ctx.slug);
```

An optional `GetContextOptions` object can be passed to override the default timeout (10 000 ms):

```ts
const ctx = await struct.actions.getContext<TabContextPayload>({ timeoutMs: 5000 });
```

The context payload type depends on the extension point. Use the appropriate typed payload for full type safety:

| Extension point   | Payload type                  | Extra fields                                  |
|-------------------|-------------------------------|-----------------------------------------------|
| Tab               | `TabContextPayload`           | `entityId`, `entityType`                      |
| Section           | `SectionContextPayload`       | `entityId`, `entityType`                      |
| Sidebar widget    | `SidebarWidgetContextPayload` | `entityId`, `entityType`                      |
| Property          | `PropertyContextPayload`      | `entityId`, `entityType`                      |
| Exporter          | `ExporterContextPayload`      | `selectedEntityIds`, `selectedEntityType`      |
| Search action     | `SearchActionContextPayload`  | `selectedEntityIds`, `selectedEntityType`      |
| Page              | `PageContextPayload`          | _(none)_                                      |
| Widget            | `WidgetContextPayload`        | _(none)_                                      |

All payload types extend `BaseContextPayload`, which includes: `messagingVersion`, `slug`, `currentLanguage?`, `currentUser?`, `currentSegments?`.

---

### `openModal`

Opens a modal inside the Struct PIM UI, rendering a URL in an iframe.

```ts
struct.actions.openModal({
  id: 'my-modal',
  url: 'https://your-extension.example.com/modal',
  placement: 'center', // 'center' | 'left' | 'right'
  size: 'medium',      // 'small' | 'medium' | 'large'
});
```

---

### `closeModal`

Closes an open modal by its ID.

```ts
struct.actions.closeModal({ id: 'my-modal' });
```

---

### `closeHostContainer`

Asks the Struct PIM host to close the dialog that is currently hosting your extension iframe (e.g. the export-entities dialog or a search-action dialog). Use this when your extension has finished its work and the surrounding host dialog should be dismissed.

```ts
struct.actions.closeHostContainer();
```

Only supported on the **Exporter** and **SearchAction** extension points — these are the only extension points whose iframes are embedded inside a dismissible host dialog. On any other extension point the host fires `onActionRejectedEvent` with reason `Action "CLOSE_HOST_CONTAINER" is not supported for extension point type "<type>"`.

> Not to be confused with `closeModal`, which only closes a modal that the *same* extension previously opened via `openModal`. `closeHostContainer` closes the host dialog that contains the extension iframe itself.

---

### `showSnackbarMessage`

Displays a snackbar notification in the Struct PIM UI.

```ts
struct.actions.showSnackbarMessage({
  message: 'Changes saved successfully',
  placement: 'bottom', // 'top' | 'bottom'
  isError: false,
  durationMs: 3000,    // optional
});
```

---

### `resizeContainer`

Manually sets the iframe container height in pixels. Supported on Tab, Section, Property, and Sidebar Widget extension points.

```ts
struct.actions.resizeContainer({ height: 400 });
```

---

### `enableAutoResize`

Automatically adjusts the iframe height based on content using a `ResizeObserver`. Only one auto-resize observer can be active at a time. Supported on Tab, Section, Property, and Sidebar Widget extension points.

```ts
struct.actions.enableAutoResize({
  maxHeight: 800,    // optional — cap the height
  debounceMs: 200,   // optional — debounce delay (default 100ms)
});
```

---

## Events (`struct.events`)

Events are messages sent **from Struct PIM to your extension**. Each listener returns an unsubscribe function.

### `onEntityChangedEvent`

Fired whenever the entity the extension is associated with is saved/updated. Relevant for tab, section, sidebar widget, and property extension points.

```ts
const unsubscribe = struct.events.onEntityChangedEvent((payload) => {
  console.log(payload.entityType, 'with id', payload.entityId, 'changed');
});
```

---

### `onLanguageChangedEvent`

Fired whenever the user switches the active language in Struct PIM. Use this to re-fetch localized data or update the UI.

```ts
struct.events.onLanguageChangedEvent((payload) => {
  console.log('Language changed to:', payload.currentLanguage);
});
```

---

### `onSegmentChangedEvent`

Fired whenever the active segment selection changes in Struct PIM.

```ts
struct.events.onSegmentChangedEvent((payload) => {
  console.log('Segments changed:', payload.currentSegments);
});
```

---

### `onActionRejectedEvent`

Fired when the host rejects an action (e.g., a resize on an unsupported extension point).

```ts
struct.events.onActionRejectedEvent((payload) => {
  console.warn(`Action rejected: ${payload.rejectedAction} — ${payload.reason}`);
});
```

---

## Cleanup

Call `destroy()` to unsubscribe all event listeners and stop any active auto-resize observer.

```ts
struct.destroy();
```

---

## Quick start

A minimal extension that retrieves its context and listens for changes:

```ts
import { createStructSDK } from '@structdk/extension-sdk';
import type { TabContextPayload } from '@structdk/extension-sdk';

// 1. Create SDK instance (optionally lock to a specific origin)
const struct = createStructSDK({ hostOrigin: 'https://your-struct-instance.struct.com' });

// 2. Request context from the host
const ctx = await struct.actions.getContext<TabContextPayload>();
console.log('Entity:', ctx.entityType, ctx.entityId);
console.log('Slug:', ctx.slug);

// 3. Auto-resize the iframe to fit content
struct.actions.enableAutoResize({ maxHeight: 800 });

// 4. Register event listeners for ongoing changes
struct.events.onEntityChangedEvent((payload) => {
  console.log('Entity updated:', payload.entityId);
});

struct.events.onLanguageChangedEvent((payload) => {
  console.log('Language:', payload.currentLanguage);
});

// 5. Clean up when done
struct.destroy();
```

---

## Types

All payload types are exported from the package root:

```ts
import type {
  // SDK options
  StructSDKOptions,
  // Context payloads
  BaseContextPayload,
  TabContextPayload,
  SectionContextPayload,
  SidebarWidgetContextPayload,
  PropertyContextPayload,
  ExporterContextPayload,
  SearchActionContextPayload,
  PageContextPayload,
  WidgetContextPayload,
  // Event payloads
  EntityChangedPayload,
  LanguageChangedPayload,
  SegmentChangedPayload,
  ActionRejectedPayload,
  // Action payloads
  OpenModalPayload,
  CloseModalPayload,
  CloseHostContainerPayload,
  ShowSnackbarMessagePayload,
  ResizeContainerPayload,
  // Options
  AutoResizeOptions,
  GetContextOptions,
} from '@structdk/extension-sdk';
```

The `StructEntityType` enum is also exported:

```ts
import { StructEntityType } from '@structdk/extension-sdk';
// StructEntityType.Product | .Category | .Variant | .VariantGroup | .Asset
```

---

## Versioning

The SDK version is available at runtime via `ExtensionSdkVersion`:

```ts
import { ExtensionSdkVersion } from '@structdk/extension-sdk';

console.log('SDK version:', ExtensionSdkVersion);
```

---

## License

MIT