Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion content/advanced-extensions/build-sdk-with-ai.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ These models may struggle with complex state management or the specifics of the

If your goal is a functional component that interacts with Akeneo data, we recommend using an **AI-native IDE** (like Cursor) or a code agent such as **Claude Code**. These tools have better "reasoning" capabilities and can index your entire project folder to understand context.

::: info
If you use **Claude Code**, a dedicated plugin is available to streamline your workflow. See the [Claude Code Plugin](#claude-code-plugin) section below.
:::

## Step 1: Provide context

AI is only as good as the context you provide. Before asking the AI to write code, "prime" it by providing the structure of a valid Akeneo extension.
Expand Down Expand Up @@ -66,4 +70,26 @@ Once the AI generates your code (typically a single JavaScript file or a small p
| **Data-driven Tool** | Cursor / Claude Code | 70% (AI writes logic, you guide) |
| **Complex Integration** | Code agent + Manual Review | 50% (AI handles boilerplate, you debug) |

By combining a clear functional goal with the right AI prompts and Akeneo’s SDK documentation, you can build sophisticated custom components without being a JavaScript expert.
By combining a clear functional goal with the right AI prompts and Akeneo’s SDK documentation, you can build sophisticated custom components without being a JavaScript expert.

## Claude Code Plugin

The [Akeneo Extension SDK repository](https://github.com/akeneo/extension-sdk?tab=readme-ov-file#claude-code-plugin-beta) ships a **Claude Code plugin** called `akeneo-custom-component` that is purpose-built to streamline Custom Component development directly inside Claude Code.

### What it provides

- **Awareness skill** — automatically activates when Custom Components are discussed, giving Claude Code the right context without any manual prompting.
- **`/akeneo-cc-setup` skill** — a guided workflow that handles scaffolding, building, and deploying a Custom Component sequentially.

### Installation

Run the following commands inside Claude Code:

```

/plugin marketplace add akeneo/extension-sdk
/plugin install akeneo-custom-component@akeneo-custom-component
```

Once installed, Claude Code will automatically provide Akeneo-specific guidance whenever you work on Custom Components, and you can trigger the full setup flow at any time with `/akeneo-cc-setup`.

2 changes: 1 addition & 1 deletion content/advanced-extensions/sdk-in-depth.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ When developing with the SDK, keep these constraints in mind:
- **External Access**: Direct network requests (fetch, XMLHttpRequest) to external services are not allowed. All API interactions must go through the SDK methods. There is a specific method available to allow access to external resources.
- **DOM Access**: Limited access to the DOM is provided, with restrictions on what elements can be modified.
- **Global State**: The sandbox isolates your code from affecting the global state of the PIM application.
- **Resources**: Your script should be efficient as it runs within the PIM application context.
- **Resources**: Your script should be efficient as it runs within the PIM application context. Also note that the uploaded file must not exceed 10MB.

[![indepth_custom_extension.png](../img/extensions/ui-extensions/indepth_custom_extension.png)](../img/extensions/ui-extensions/indepth_custom_extension.png)

Expand Down
114 changes: 113 additions & 1 deletion content/extensions/action.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,119 @@ Examples :

## Signature

It's possible to configure a `secret` to sign the body of the POST request sent to the destination (<a href='https://wikipedia.org/wiki/SHA-2'>SHA-512</a> protocol).
You can configure a `secret` on the action to authenticate incoming requests. When a secret is set, the PIM computes an **HMAC-SHA512** signature of the raw request body and sends it in a `signature` HTTP header, formatted as:

```
signature: sha512=<hex digest>
```

### How to verify the signature

To verify a request on your server:

1. **Capture the raw request body as bytes** — before any JSON parsing.
2. **Compute** `HMAC-SHA512(body, secret)` and encode the result as a lowercase hex string.
3. **Strip the `sha512=` prefix** from the received `signature` header value.
4. **Compare** the two hex strings using a **timing-safe equality** function to prevent [timing attacks](https://en.wikipedia.org/wiki/Timing_attack).

::: warning
Always verify the signature **before** parsing the JSON body. Processing an unverified payload is a security risk.
:::

```php [snippet:PHP]
function isSignatureValid(string $rawBody, string $signatureHeader, string $secret): bool
{
if (!str_starts_with($signatureHeader, 'sha512=')) {
return false;
}
$received = substr($signatureHeader, strlen('sha512='));
$expected = hash_hmac('sha512', $rawBody, $secret);

return hash_equals($expected, $received);
}

// Usage in a controller / middleware:
$rawBody = (string) $request->getContent();
$signatureHeader = $request->headers->get('signature', '');

if (!isSignatureValid($rawBody, $signatureHeader, $_ENV['AKENEO_SECRET'])) {
return new Response('Invalid signature', 401);
}
```
```javascript [snippet:Node.js]
const crypto = require('crypto');

function isSignatureValid(rawBody, signatureHeader, secret) {
if (!signatureHeader?.startsWith('sha512=')) return false;
const received = signatureHeader.slice('sha512='.length);
const expected = crypto.createHmac('sha512', secret).update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}

// Usage with Express — capture raw body before JSON parsing:
app.use(express.json({
verify: (req, _res, buf) => { req.rawBody = buf.toString('utf8'); },
}));

app.post('/your-endpoint', (req, res) => {
const signature = req.headers['signature'] ?? '';
if (!isSignatureValid(req.rawBody, signature, process.env.AKENEO_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// process req.body ...
});
```
```python [snippet:Python]
import hmac
import hashlib

def is_signature_valid(raw_body: bytes, signature_header: str, secret: str) -> bool:
prefix = 'sha512='
if not signature_header.startswith(prefix):
return False
received = signature_header[len(prefix):]
expected = hmac.new(secret.encode('utf-8'), raw_body, hashlib.sha512).hexdigest()
return hmac.compare_digest(expected, received)

# Usage with Flask:
from flask import Flask, request, abort

@app.route('/your-endpoint', methods=['POST'])
def handle():
raw_body = request.get_data()
signature = request.headers.get('signature', '')
if not is_signature_valid(raw_body, signature, os.environ['AKENEO_SECRET']):
abort(401)
# process request.json ...
```

### Replay attack prevention

The payload always includes a `timestamp` (Unix seconds). After verifying the signature, check that the timestamp is recent — for example, within 5 minutes — to protect your server against [replay attacks](https://en.wikipedia.org/wiki/Replay_attack):

```php [snippet:PHP]
$body = json_decode($rawBody, true);
if (abs(time() - $body['timestamp']) > 300) {
return new Response('Request expired', 401);
}
```
```javascript [snippet:Node.js]
const body = JSON.parse(rawBody);
if (Math.abs(Date.now() / 1000 - body.timestamp) > 300) {
return res.status(401).json({ error: 'Request expired' });
}
```
```python [snippet:Python]
import json, time

body = json.loads(raw_body)
if abs(time.time() - body['timestamp']) > 300:
abort(401)
```

### Secret rotation

To rotate the secret without downtime, configure your server to accept both the current and the new secret — verify the signature against each one. Once all in-flight requests signed with the old secret have been processed, remove it and update the PIM configuration with the new secret only.

## Available Positions

Expand Down