npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@scope01gmbh/n8n-nodes-pimcore-datahub

v2.0.1

Published

n8n community node for the Pimcore Datahub GraphQL API - read and write any data object without writing GraphQL, with upsert and batching

Downloads

590

Readme

n8n-nodes-pimcore-datahub

An n8n community node for the Pimcore Datahub GraphQL API.

Datahub already exposes every data object over GraphQL. What it does not give you is a way to use that from n8n without writing GraphQL by hand — Pimcore's own Workflow Automation blueprints are demos built on generic HTTP Request nodes, and their docs state that nested structures are excluded from export.

This node reads the endpoint's schema and turns it into dropdowns. Pick a class, pick fields, run. It also adds the two things Datahub's API does not have:

  • Create or Update — Datahub has create and update but no upsert. This node resolves identity first (by ID, full path, or any field such as an article number), then dispatches the right mutation.
  • Batching — Datahub writes one object per mutation. This node packs up to Batch Size items into a single GraphQL document under aliases, which the GraphQL spec executes serially. 500 updates is 20 requests, not 500.

Installation

Settings → Community Nodes → Install, then enter:

@scope01gmbh/n8n-nodes-pimcore-datahub

Credentials

Create a GraphQL endpoint in Pimcore under Datahub → Configurations, give it an API key in the Security tab, and grant workspaces for the classes you want to reach.

| Field | Example | |---|---| | Pimcore URL | https://pimcore.example.com (no webservice path) | | Endpoint Name | my_endpoint — the Datahub configuration name | | API Key | the key from the configuration's Security tab | | Send API Key As | Header (default) or Query Parameter |

The key is sent as X-API-Key. Use Query Parameter only when a proxy strips custom headers; it puts the key in the URL, where access logs will keep it.

An API key belongs to one Datahub configuration, which is why the endpoint name lives in the credential rather than on the node. Point several credentials at the same Pimcore to reach several endpoints.

Operations

Data Object

Pick the class from From List, populated by introspecting the endpoint, or switch the locator to By Name and type it — which also takes an expression, so one node can walk several classes, and works on endpoints that have introspection switched off.

| Operation | Notes | |---|---| | Get | By ID or full path. Reads are batched too — 200 IDs is one request. | | Get Many | Filter, sort, pagination. Return All pages through automatically. | | Create | Needs a key and a parent. Field values come from the mapper. | | Update | By ID or full path. | | Create or Update | Upsert. See below. | | Delete | By ID or full path. |

Asset

| Operation | Notes | |---|---| | Get | By ID or full path, optionally with the file as binary data. | | Get Many | Filter, sort, pagination. Folders come back too — the listing is a tree. | | Upload | Takes binary data from the item. The Pimcore asset type is detected from the mime type unless you set it. | | Update | Replace the file, write metadata, or both. | | Delete | By ID or full path. |

Datahub moves files base64 encoded inside the GraphQL response, so downloading a long listing is memory hungry — asset operations default to a batch size of 1 for that reason. asset.data is a plain String in the schema with nothing to mark it as a binary, so it is left out of All Scalar Fields; switch on Download File, or name it explicitly, to fetch it.

To attach an uploaded asset to an object, write the object's image field with the ID the upload returned: {"image": {"id": 483}}.

GraphQL

Runs any document against the endpoint, with variables. The escape hatch for custom queries, documents, field collections and object bricks.

Fields

GraphQL returns only what you ask for, so every read operation has a Fields setting. (This governs what a read returns; what a write sends is covered under Writing field values below.)

  • All Scalar Fields — every plain field, plus the ID and full path of related objects. Relation Depth controls how far relations are followed; 0 omits them entirely.
  • Selected Fields — a list built from the endpoint's schema. Relations expand into dotted paths (manufacturer.name), and the node assembles the inline fragments Datahub's union types require.
  • Raw Selection Set — write the GraphQL yourself. The only mode available on an endpoint with introspection disabled.

Writes return the saved object's identity. Switch on Return Written Object to get every scalar back as well. Relations are never returned from a write — see Known Pimcore behaviour below.

Where a query returns a union — an asset listing is a tree of assets and folders — the node writes one inline fragment per member type and drops the fields a member does not have, so asking for mimetype does not break on the folders in the result.

Writing field values

Every write operation — Create, Update, Create or Update — takes its field values through an Input Mode:

  • Mapped Fields (default) — the Fields to Write mapper. It loads the class's writable fields from the endpoint and gives each one its own input, typed from the schema: a toggle for a boolean, a number field for an Int, a JSON editor for a relation. Switch it to Map Automatically to take the fields straight off the incoming item, matched by name.
  • Raw JSON — one JSON object for the whole input, as in {"number": "SW-100", "image": {"id": 483}}. This is the only mode available on an endpoint with introspection disabled, since the mapper has no field list to load there.

The mapper offers only what a mutation actually accepts. Identity and placement — key, parent, published — are set through their own parameters rather than as mapped fields, so there is one place to set each. Pimcore's read-only metadata (creationDate, classname, version, …) is left out entirely.

In Map Automatically mode the item is narrowed to the fields the class accepts before being sent. An item that came out of a Get carries id, fullpath and the rest of the metadata, and GraphQL rejects an entire mutation on the first field the input type does not define.

Fields are opted into rather than all pre-added: a Pimcore product class routinely carries forty attributes, and a form with forty empty inputs is worse than a short one you add to.

Create or Update

Match By:      Field Value
Match Field:   number
Match Value:   {{ $json.article_number }}
If Not Found:  Create
Parent Path:   /products/import
Input Mode:    Mapped Fields

Field matching runs one lookup document for the whole batch, then one mutation document — two requests per batch regardless of batch size, not two per item.

If several objects match, the item fails with the matching IDs in the message. Silently updating an arbitrary one of three duplicates is how a PIM quietly loses data.

Localized fields

One call reads or writes one language, chosen by the Default Language option. To write two languages, send the item twice:

Upsert (Default Language: en)  →  Update (Default Language: de)

The second node addresses the object by the ID the first one returned. Note that localized fields marked mandatory are mandatory in every language, so a first write that fills only English needs Omit Mandatory Check switched on.

Batching and failure

Batched writes are not a transaction. GraphQL executes aliased mutations one after another, so item 7 failing does not roll back items 0–6. Each item carries its own result, and with Continue On Fail only the failed items get an error key. Set Batch Size to 1 for one request per item.

Filters

The Filter option takes Pimcore's Datahub filter syntax as JSON. A bare value means equals — there is no $eq operator, and an unrecognised $op key is read as a column name, which surfaces as an SQL error:

{ "number": "SW-100" }
{ "number": { "$like": "SW-%" } }
{ "$and": [{ "active": 1 }, { "priceGross": { "$gt": 100 } }] }

Available operators: $like, $notlike, $not, $notnull, $gt, $gte, $lt, $lte, and $and / $or for grouping.

Known Pimcore behaviour

Things that look like node bugs but are not:

  • A field is always null. Datahub's default not_allowed_policy returns null rather than an error for a field the endpoint's workspace does not grant. Check the workspace permissions, not the query.
  • Relations cannot be selected from a write. data-hub v2.3.0 crashes resolving a relation on a mutation's output (resolveValue(): Argument #1 ($descriptor) must be of type BaseDescriptor, array given). The node keeps mutation output to scalars; read relations back with a Get.
  • success: false does not always mean nothing was written. Pimcore save listeners can reject an object after values are set. Check the message.
  • Introspection can be switched off per endpoint. The class list and field picker then return an error explaining it; switch the class locator to By Name and use a raw selection set.
  • Asset mutations answer with assetData, where object mutations use output. Nothing in the schema hints at the inconsistency; the node handles it, but a hand-written mutation has to know.
  • createAsset demands a type, which Pimcore turns straight into an Asset\Image, Asset\Document and so on. A wrong value yields an asset of the wrong class rather than an error, so the node detects it from the binary.

Development

npm install
npm run build     # compile to dist/
npm test          # unit tests (build + node --test)
npm run lint      # n8n community node lint

The unit tests run against test/fixtures/introspection.json, captured from a real Pimcore instance, so the selection-set builder is exercised against real union-typed relations and a real asset tree rather than a toy schema.

Testing against a running n8n

Two ways to get the node into a local n8n, and they are mutually exclusive — both target ~/.n8n/nodes/node_modules/<package>.

Released build (what a user gets):

docker exec n8n sh -c 'cd /home/node/.n8n/nodes && \
  npm install @scope01gmbh/n8n-nodes-pimcore-datahub@latest'
docker restart n8n

Working copy (for iterating): bind-mount the repo over that same path when starting the container, and npm run build after each change.

docker run -d --name n8n -p 5678:5678 \
  -v n8n_data:/home/node/.n8n \
  -v "$PWD":/home/node/.n8n/nodes/node_modules/@scope01gmbh/n8n-nodes-pimcore-datahub \
  --add-host pimcore-x.local:host-gateway \
  docker.n8n.io/n8nio/n8n

Mounting into ~/.n8n/custom instead does not work: that path cannot resolve n8n-workflow, so the node loads as nothing at all with no error.

If you swap from the bind mount back to the npm install, delete the empty directory the mount leaves behind first — otherwise n8n finds a package folder with no package.json and logs failed to load package metadata, which reads like a corrupt download rather than a leftover mountpoint.

examples/ holds importable credentials and five workflows covering reads, two-language upserts, a full asset round trip, hand-written GraphQL, an endpoint without introspection, and a cleanup pass. See examples/README.md.

Note on the linter: npm run lint -- --fix rewrites name: properties into title case. GraphQL argument names are therefore keyed arg: in QueryBuilder.ts, not name:, so that fullpath does not silently become Fullpath. There is a regression test for this.

License

MIT