PHPackages                             pobo-builder/php-sdk - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. pobo-builder/php-sdk

ActiveLibrary[API Development](/categories/api)

pobo-builder/php-sdk
====================

Official PHP SDK for Pobo API V2 - product content management and webhooks

v1.5.1(1mo ago)0538MITPHPPHP ^8.1CI passing

Since Dec 27Pushed 1mo agoCompare

[ Source](https://github.com/pobo-builder/php-sdk)[ Packagist](https://packagist.org/packages/pobo-builder/php-sdk)[ Docs](https://github.com/pobo-builder/php-sdk)[ RSS](/packages/pobo-builder-php-sdk/feed)WikiDiscussions master Synced today

READMEChangelog (8)Dependencies (5)Versions (20)Used By (0)

Pobo PHP SDK
============

[](#pobo-php-sdk)

[![Tests](https://github.com/pobo-builder/php-sdk/actions/workflows/tests.yml/badge.svg)](https://github.com/pobo-builder/php-sdk/actions/workflows/tests.yml)[![Latest Stable Version](https://camo.githubusercontent.com/1082fc1c01fa86ab42ff78ae01f28df1c755dec037774d56f9470de6dd66466a/68747470733a2f2f706f7365722e707567782e6f72672f706f626f2d6275696c6465722f7068702d73646b2f762f737461626c65)](https://packagist.org/packages/pobo-builder/php-sdk)[![License](https://camo.githubusercontent.com/5507e0e186845d06d00939fe36999da1ff215e08e42f35f476292c762e46aac6/68747470733a2f2f706f7365722e707567782e6f72672f706f626f2d6275696c6465722f7068702d73646b2f6c6963656e7365)](https://packagist.org/packages/pobo-builder/php-sdk)

Official PHP SDK for [Pobo API V2](https://api.pobo.space) — product content management and webhooks.

Requirements
------------

[](#requirements)

- PHP 8.1+
- ext-curl, ext-json

Installation
------------

[](#installation)

```
composer require pobo-builder/php-sdk
```

Quick Start
-----------

[](#quick-start)

```
use Pobo\Sdk\PoboClient;

$client = new PoboClient(apiToken: 'your-api-token');
```

The token authenticates via `Authorization: Bearer ...` and is bound to a single e-shop. Get the API token and webhook secret in the Pobo administration under e-shop settings → REST API.

The examples below use only the **default** language. For multi-language imports and exports see [Multilang](#multilang) further down.

Import
------

[](#import)

Order: parameters → categories → brands → products → blogs.

### Parameters

[](#parameters)

```
use Pobo\Sdk\DTO\Parameter;
use Pobo\Sdk\DTO\ParameterValue;

$result = $client->importParameters([
    new Parameter(id: 1, name: 'Color', values: [
        new ParameterValue(id: 1, value: 'Red'),
        new ParameterValue(id: 2, value: 'Blue'),
    ]),
]);
```

### Categories

[](#categories)

```
use Pobo\Sdk\DTO\Category;
use Pobo\Sdk\DTO\LocalizedString;

$result = $client->importCategories([
    new Category(
        id: 'CAT-001',
        isVisible: true,
        name: LocalizedString::create('Electronics'),
        url: LocalizedString::create('https://example.com/electronics'),
    ),
]);
```

### Brands

[](#brands)

```
use Pobo\Sdk\DTO\Brand;
use Pobo\Sdk\DTO\LocalizedString;

$result = $client->importBrands([
    new Brand(
        id: 'BRAND-001',
        isVisible: true,
        name: LocalizedString::create('Apple'),
        url: LocalizedString::create('https://example.com/znacky/apple'),
        imagePreview: 'https://example.com/brands/apple-logo.png',
    ),
]);
```

`imagePreview` follows the same three-state semantics as `Product::brandId`:

Value of `imagePreview`Behavioromitted (default)`image_preview` is not sent — the server keeps the existing logo`null``image_preview` is **cleared** on the brand`'https://.../logo.png'` (string)`image_preview` is set to this value### Products

[](#products)

```
use Pobo\Sdk\DTO\Product;
use Pobo\Sdk\DTO\LocalizedString;

$result = $client->importProducts([
    new Product(
        id: 'PROD-001',
        isVisible: true,
        name: LocalizedString::create('iPhone 15'),
        url: LocalizedString::create('https://example.com/iphone-15'),
        categoriesIds: ['CAT-001'],
        parametersIds: [1, 2],
        brandId: 'BRAND-001',
    ),
]);

if ($result->hasErrors()) {
    foreach ($result->errors as $error) {
        echo implode(', ', $error['errors']);
    }
}
```

#### Pairing a product with a brand

[](#pairing-a-product-with-a-brand)

`brandId` has three-state semantics:

Value of `brandId`Behavioromitted (default)`brand_id` is not sent — the server keeps the existing assignment`null`brand is **cleared** on the product`'BRAND-001'` (string)paired with `brand.remote_id` in PoboThe brand referenced by `brandId` must already exist in Pobo (registered via `importBrands`); otherwise the product is skipped with `"Invalid brand id: ..."`.

> **Empty string `brandId: ''`:** the API treats `""` as a lookup, not a clear — the product is skipped with `"Invalid brand id: "`. To clear an existing brand pairing, pass an explicit `null`. The same applies to entries inside `categoriesIds` / `parametersIds` — the SDK defensively filters empty strings and `null` from those arrays before sending.

> **Known V2 limitation — clearing categories or parameters:** sending `categoriesIds: []` (or `parametersIds: []`) **does not** detach existing pivot rows; the server treats an empty/absent array as "no change". V2 currently has no clear-all endpoint for these relations — clearing has to happen via the Pobo admin UI. The 3-state semantic only applies to `brandId`.

### Blogs

[](#blogs)

```
use Pobo\Sdk\DTO\Blog;
use Pobo\Sdk\DTO\LocalizedString;

$result = $client->importBlogs([
    new Blog(
        id: 'BLOG-001',
        isVisible: true,
        name: LocalizedString::create('New Product Launch'),
        url: LocalizedString::create('https://example.com/blog/new-product'),
        category: 'news',
    ),
]);
```

> **Per-item errors:** validation failures during bulk import (invalid URL, missing language, …) **don't** raise an exception. They appear in `$result->errors[]` while the rest of the batch is processed. Always inspect `$result->hasErrors()`.

Delete
------

[](#delete)

Soft-delete by external ID (max 100 per request):

```
$client->deleteProducts(['PROD-001', 'PROD-002']);
$client->deleteCategories(['CAT-001']);
$client->deleteBrands(['BRAND-001']);
$client->deleteBlogs(['BLOG-001']);
```

> Soft-deleting a brand does **not** clear `brand_id` on products that reference it. Re-import the products with `brandId: null` if you also want to drop the assignment.

Export
------

[](#export)

```
$response = $client->getProducts(page: 1, perPage: 50);

foreach ($response->data as $product) {
    echo $product->id, ': ', $product->name->getDefault(), "\n";
}

// Or iterate everything (handles pagination automatically):
foreach ($client->iterateProducts() as $product) {
    // ...
}
```

The same shape works for `getCategories` / `getBrands` / `getBlogs` (and their `iterate*` variants).

> **`isEdited`:** when not provided, the server defaults to `true` and returns only items that have been edited in Pobo (`is_loaded = true`). To export everything, pass `isEdited: false` explicitly.

### Filter by last update

[](#filter-by-last-update)

```
$since = new DateTime('2024-01-01 00:00:00');
$response = $client->getProducts(lastUpdateFrom: $since);
```

### Generated content (HTML, marketplace, …)

[](#generated-content-html-marketplace-)

By default the export returns only `content.html`. Use `include` to request additional content:

```
use Pobo\Sdk\Enum\IncludeContent;

foreach ($client->iterateProducts(include: [IncludeContent::MARKETPLACE]) as $product) {
    $html            = $product->content?->getHtmlDefault();
    $marketplaceHtml = $product->content?->getMarketplaceDefault();
}
```

ValueDescriptionAvailable for`marketplace`HTML for marketplace (no custom CSS)product, category, brand, blog`nested`Raw widget JSONproduct, category, brand, blog`site_link`Anchor navigation on H2 headingsproduct, brand, blog`rich_snippet`JSON-LD structured data (FAQPage)product, category, brand, blog`site_link` requires `enable_site_link` and `rich_snippet` requires `enable_rich_snippet` to be enabled on the e-shop in Pobo administration.

Multilang
---------

[](#multilang)

Pobo supports 7 languages: `default`, `cs`, `sk`, `en`, `de`, `pl`, `hu`. `default` is a separate virtual locale used as fallback content — it is **not** an alias for `cs` or any other language.

CodeLanguage`default`Default (required)`cs`Czech`sk`Slovak`en`English`de`German`pl`Polish`hu`Hungarian### Importing translations

[](#importing-translations)

Use `LocalizedString::withTranslation()` to chain languages on any localized field:

```
use Pobo\Sdk\DTO\LocalizedString;
use Pobo\Sdk\Enum\Language;

$name = LocalizedString::create('iPhone 15')                    // default
    ->withTranslation(Language::CS, 'iPhone 15 CZ')
    ->withTranslation(Language::SK, 'iPhone 15 SK')
    ->withTranslation(Language::EN, 'iPhone 15');

$name->getDefault();        // 'iPhone 15'
$name->get(Language::CS);   // 'iPhone 15 CZ'
$name->toArray();           // ['default' => '...', 'cs' => '...', 'sk' => '...', 'en' => '...']
```

Pass localized strings to any DTO that accepts them — products, categories, brands, blogs:

```
use Pobo\Sdk\DTO\Product;
use Pobo\Sdk\DTO\LocalizedString;
use Pobo\Sdk\Enum\Language;

new Product(
    id: 'PROD-001',
    isVisible: true,
    name: LocalizedString::create('iPhone 15')
        ->withTranslation(Language::CS, 'iPhone 15 CZ')
        ->withTranslation(Language::SK, 'iPhone 15 SK'),
    url: LocalizedString::create('https://example.com/iphone-15')
        ->withTranslation(Language::CS, 'https://example.com/cs/iphone-15')
        ->withTranslation(Language::SK, 'https://example.com/sk/iphone-15'),
    shortDescription: LocalizedString::create('Latest iPhone model')
        ->withTranslation(Language::CS, 'Nejnovější model iPhone')
        ->withTranslation(Language::SK, 'Najnovší model iPhone'),
    description: LocalizedString::create('The best iPhone ever.')
        ->withTranslation(Language::CS, 'Nejlepší iPhone vůbec.')
        ->withTranslation(Language::SK, 'Najlepší iPhone vôbec.'),
);
```

### Validation rules

[](#validation-rules)

The API enforces a "language consistency" rule across multilang fields:

- `name.default` and `url.default` are **always required**.
- If any field on an item carries a translation key (e.g. `sk`), then `name.{lang}` and `url.{lang}` become required for that item.
- Other fields (`short_description`, `description`, `seo_title`, `seo_description`) may have `null` values, but the language key must be present if the language is used elsewhere on the item.
- All `url.*` values must start with `https://`.
- Records that fail validation are **skipped**, not aborted — the response reports them in `errors[]`. The rest of the batch is imported.
- These rules apply identically to products, categories, brands and blogs.

### Update behavior

[](#update-behavior)

When you re-import an item:

- Languages **present** in the request stay active (`*_meta.is_active = true`).
- Languages **missing** from the request are soft-deleted (`*_meta.is_active = false`).

Always re-send every language you want to keep visible.

### Filtering languages on export

[](#filtering-languages-on-export)

By default, only `default` is returned. Use the `lang` parameter to request specific languages:

```
use Pobo\Sdk\Enum\Language;

// All 7 languages
$response = $client->getProducts(lang: [Language::ALL]);

// Specific languages
$response = $client->getProducts(lang: [Language::DEFAULT, Language::CS, Language::SK]);

foreach ($client->iterateProducts(lang: [Language::ALL]) as $product) {
    echo $product->name->get(Language::CS);
    echo $product->content?->getHtml(Language::CS);
}
```

The `lang` parameter filters every multilang field in the response — `name`, `description`, `url`, …, plus `content.html`, `content.marketplace`, `site_link.*`, `rich_snippet.*`. Invalid language values are silently ignored.

### Reading translations

[](#reading-translations)

```
use Pobo\Sdk\Enum\Language;

foreach ($client->iterateProducts(lang: [Language::ALL]) as $product) {
    $product->name->getDefault();           // default value
    $product->name->get(Language::CS);      // Czech variant (or null)
    $product->name->toArray();              // ['default' => '...', 'cs' => '...', ...]

    // Same accessors on content / site_link / rich_snippet:
    $product->content?->getHtml(Language::CS);
    $product->content?->getMarketplace(Language::CS);
    $product->siteLink?->getList(Language::CS);
    $product->richSnippet?->getJson(Language::CS);
}
```

Webhook Handler
---------------

[](#webhook-handler)

Pobo can notify your application when content changes in the administration. Register a webhook URL in the Pobo e-shop settings; Pobo POSTs to it with HMAC-SHA256 signed requests.

### Supported events

[](#supported-events)

EventConstantFired when`Products.create``WebhookEvent::PRODUCTS_CREATE`a product was created`Products.update``WebhookEvent::PRODUCTS_UPDATE`a product was edited`Products.delete``WebhookEvent::PRODUCTS_DELETE`a product was soft-deleted`Categories.create``WebhookEvent::CATEGORIES_CREATE`a category was created`Categories.update``WebhookEvent::CATEGORIES_UPDATE`a category was edited`Categories.delete``WebhookEvent::CATEGORIES_DELETE`a category was soft-deleted`Brands.create``WebhookEvent::BRANDS_CREATE`a brand was created`Brands.update``WebhookEvent::BRANDS_UPDATE`a brand was edited`Brands.delete``WebhookEvent::BRANDS_DELETE`a brand was soft-deleted`Blogs.create``WebhookEvent::BLOGS_CREATE`a blog was created`Blogs.update``WebhookEvent::BLOGS_UPDATE`a blog was edited`Blogs.delete``WebhookEvent::BLOGS_DELETE`a blog was soft-deletedThe enum exposes `isCreate()` / `isUpdate()` / `isDelete()` helpers if you only need to branch on the lifecycle action regardless of entity type.

> **Note:** the API also defines `Languages.create/update/delete` events. The SDK does not expose them yet — payloads carrying these events will throw `WebhookException::unknownEvent()`.

The webhook is a notification only — it does not carry the changed entities. Use it as a trigger to re-sync via the export endpoints (typically combined with `lastUpdateFrom`).

### Basic usage

[](#basic-usage)

```
use Pobo\Sdk\WebhookHandler;
use Pobo\Sdk\Enum\WebhookEvent;
use Pobo\Sdk\Exception\WebhookException;

$handler = new WebhookHandler(webhookSecret: 'your-webhook-secret');

try {
    $payload = $handler->handleFromGlobals();

    match ($payload->event) {
        WebhookEvent::PRODUCTS_CREATE,
        WebhookEvent::PRODUCTS_UPDATE,
        WebhookEvent::PRODUCTS_DELETE   => syncProducts($client),
        WebhookEvent::CATEGORIES_CREATE,
        WebhookEvent::CATEGORIES_UPDATE,
        WebhookEvent::CATEGORIES_DELETE => syncCategories($client),
        WebhookEvent::BRANDS_CREATE,
        WebhookEvent::BRANDS_UPDATE,
        WebhookEvent::BRANDS_DELETE     => syncBrands($client),
        WebhookEvent::BLOGS_CREATE,
        WebhookEvent::BLOGS_UPDATE,
        WebhookEvent::BLOGS_DELETE      => syncBlogs($client),
    };

    http_response_code(200);
} catch (WebhookException $e) {
    http_response_code(401);
    echo json_encode(['error' => $e->getMessage()]);
}
```

`$payload` exposes `event` (`WebhookEvent` enum), `timestamp` (`DateTimeInterface`), and `eshopId` (int).

### Branching only on lifecycle action

[](#branching-only-on-lifecycle-action)

```
$payload = $handler->handleFromGlobals();

if ($payload->event->isDelete()) {
    invalidateCache($payload->event, $payload->eshopId);
}

if ($payload->event->isCreate() || $payload->event->isUpdate()) {
    triggerResync($payload->event, $payload->eshopId);
}
```

Error Handling
--------------

[](#error-handling)

```
use Pobo\Sdk\Exception\ApiException;
use Pobo\Sdk\Exception\ValidationException;

try {
    $result = $client->importProducts($products);
} catch (ValidationException $e) {
    print_r($e->errors);
} catch (ApiException $e) {
    echo $e->httpCode, ': ', $e->getMessage();
    print_r($e->responseBody);
}
```

ExceptionThrown when`ValidationException`Local pre-flight check fails — empty payload or **more than 100 items** in a bulk request. The HTTP request is not sent.`ApiException`The HTTP request fails or the API returns `>= 400`. Exposes `httpCode` and parsed `responseBody`.`WebhookException`The webhook signature is missing/invalid, the body cannot be parsed, or the event name is unknown.> Per-item validation failures during bulk import (invalid URL, missing language, …) **don't** raise `ApiException` — they appear in `$result->errors[]` while the rest of the batch is processed.

API Methods
-----------

[](#api-methods)

MethodDescription`importProducts(array $products)`Bulk import products (max 100)`importCategories(array $categories)`Bulk import categories (max 100)`importBrands(array $brands)`Bulk import brands (max 100)`importParameters(array $parameters)`Bulk import parameters (max 100)`importBlogs(array $blogs)`Bulk import blogs (max 100)`deleteProducts(array $ids)`Bulk delete products (max 100)`deleteCategories(array $ids)`Bulk delete categories (max 100)`deleteBrands(array $ids)`Bulk delete brands (max 100)`deleteBlogs(array $ids)`Bulk delete blogs (max 100)`getProducts(?page, ?perPage, ?lastUpdateFrom, ?isEdited, ?include, ?lang)`Get products page`getCategories(?page, ?perPage, ?lastUpdateFrom, ?isEdited, ?include, ?lang)`Get categories page`getBrands(?page, ?perPage, ?lastUpdateFrom, ?isEdited, ?include, ?lang)`Get brands page`getBlogs(?page, ?perPage, ?lastUpdateFrom, ?isEdited, ?include, ?lang)`Get blogs page`iterateProducts(?lastUpdateFrom, ?isEdited, ?include, ?lang)`Iterate all products`iterateCategories(?lastUpdateFrom, ?isEdited, ?include, ?lang)`Iterate all categories`iterateBrands(?lastUpdateFrom, ?isEdited, ?include, ?lang)`Iterate all brands`iterateBlogs(?lastUpdateFrom, ?isEdited, ?include, ?lang)`Iterate all blogsLimits
------

[](#limits)

LimitValueMax items per import request100Max items per delete request100Max items per export page100Product/Category ID length255 charsName length250 charsURL length255 charsImage URL length650 charsDescription length500,000 charsSEO description length500 charsTesting
-------

[](#testing)

### Unit tests

[](#unit-tests)

```
vendor/bin/phpunit --testsuite=Unit
```

Unit tests are pure (no network) and run on every CI build across PHP 8.1–8.5.

### Integration tests

[](#integration-tests)

Integration tests hit the real `api.pobo.space` API and verify that the SDK's wire format is in sync with the server. They require a dedicated test e-shop and a valid REST API token.

```
POBO_API_TOKEN="your-test-eshop-token" vendor/bin/phpunit --testsuite=Integration
```

If `POBO_API_TOKEN` is not set, all integration tests are marked **skipped** (not failed). The CI workflow runs integration tests on pushes to `master`, PRs from the same repository (forks are skipped — secrets are not exposed there), and manual dispatch. Each run uses a unique ID prefix and `tearDown()` removes anything the test created.

License
-------

[](#license)

MIT License

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance88

Actively maintained with recent releases

Popularity17

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 81.3% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~13 days

Total

11

Last Release

58d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/22744053?v=4)[Tomáš Smetka](/maintainers/smety)[@smety](https://github.com/smety)

---

Top Contributors

[![smety-cz](https://avatars.githubusercontent.com/u/80474481?v=4)](https://github.com/smety-cz "smety-cz (26 commits)")[![smety](https://avatars.githubusercontent.com/u/22744053?v=4)](https://github.com/smety "smety (6 commits)")

---

Tags

buildercontentcontent-based-recommendationcontent-managementcontent-management-systemcontent-management-system-for-studentdrag-and-droppagerich-textrich-text-editorwysiwygwysiwyg-editorwysiwyg-editorswysiwyg-html-editorwysiwyg-js-editorapisdkecommercepoboproduct-content

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/pobo-builder-php-sdk/health.svg)

```
[![Health](https://phpackages.com/badges/pobo-builder-php-sdk/health.svg)](https://phpackages.com/packages/pobo-builder-php-sdk)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
