PHPackages                             iqual/pagedesigner\_export - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. iqual/pagedesigner\_export

ActiveDrupal-custom-module[Utility &amp; Helpers](/categories/utility)

iqual/pagedesigner\_export
==========================

Exports and imports Pagedesigner element trees with full translation support

1387↓27.3%[1 PRs](https://github.com/iqual-ch/pagedesigner_export/pulls)PHP

Since Apr 29Pushed 2mo agoCompare

[ Source](https://github.com/iqual-ch/pagedesigner_export)[ Packagist](https://packagist.org/packages/iqual/pagedesigner_export)[ RSS](/packages/iqual-pagedesigner-export/feed)WikiDiscussions main Synced 3w ago

READMEChangelogDependenciesVersions (4)Used By (0)

Pagedesigner Export/Import Module
=================================

[](#pagedesigner-exportimport-module)

Exports and imports Pagedesigner element trees with full translation support. Supports two modes:

1. **Preserve-IDs mode** — Surgical restore of element trees with exact ID preservation
2. **Clone mode** — Clone element trees with new auto-assigned IDs to different nodes or sites

Technical Handoff
-----------------

[](#technical-handoff)

For implementation details, bug history, and AI/developer troubleshooting notes, see `AI_HANDOFF.md` in this module directory.

Features
--------

[](#features)

- ✅ Full translation support (all languages exported/imported)
- ✅ Recursive tree walking (children + style element references)
- ✅ All configurable element types (content, image, gallery, webform, block, etc.)
- ✅ Preserves custom fields (field\_content, field\_media, field\_block, field\_webform, etc.)
- ✅ ID mapping for clone operations (all references fixed automatically)
- ✅ Dry-run validation mode
- ✅ CLI-first with Drush commands (GUI planned)
- ✅ JSON format for portability

Use Cases
---------

[](#use-cases)

### 1. Restore deleted content to production (Preserve-IDs mode)

[](#1-restore-deleted-content-to-production-preserve-ids-mode)

When elements are accidentally deleted from a live site but exist in a good local snapshot.

Note: The argument for pd:export is the root Pagedesigner element ID (container), not the node ID. In this example, node 516014 points to container/root element 509413.

```
# On local environment (good snapshot)
# 509413 = root Pagedesigner container element ID linked from node 516014
# (for example: node.field_pd_manual_content.target_id)
drush pd:export 509413 --output=/tmp/pd-root-509413.json --sanitize-local-urls

# Transfer file to production, then:
drush pd:import /path/to/pd-root-509413.json --dry-run
drush pd:import /path/to/pd-root-509413.json
drush cr
```

**Important**: Preserve-IDs mode requires the exact same element IDs to be free on the target system. It fails if any ID collision exists.

### 2. Clone a tree to a different field or node (Clone mode)

[](#2-clone-a-tree-to-a-different-field-or-node-clone-mode)

When moving or duplicating content within the same site or to another site.

```
# Export from production
drush pd:export 509413 --output=/tmp/tree-export.json

# Import to a different node/field locally
drush pd:import /tmp/tree-export.json \
  --mode=clone \
  --target-node=516014 \
  --target-field=field_pd_manual_content \
  --dry-run

drush pd:import /tmp/tree-export.json \
  --mode=clone \
  --target-node=516014 \
  --target-field=field_pd_manual_content
```

**Result**: New element tree is created with auto-assigned IDs, all references fixed, and target node field updated.

### 3. Migrate content between sites

[](#3-migrate-content-between-sites)

Export from site A, import to site B:

```
# On site A (production)
drush pd:export 12345 --output=/tmp/page-tree.json

# Copy file to site B and import
drush pd:import /tmp/page-tree.json \
  --mode=clone \
  --target-node=456 \
  --target-field=field_pagedesigner_content
```

Drush Commands
--------------

[](#drush-commands)

### `drush pd:export  [options]`

[](#drush-pdexport-element-id-options)

Export a Pagedesigner element tree and all translations to JSON.

**Arguments:**

- `element-id` — The root element ID to export (usually a container element)

**Options:**

- `--field=FIELDNAME` — Field name for reference (informational only, optional)
- `--output=FILE` — Save JSON to file (if omitted, output to stdout)
- `--langcode=LANG` — Default language code (default: `de`)
- `--sanitize-local-urls` — Convert absolute local URLs to relative paths (default: `1`/YES). Set to `0` to disable if you need absolute URLs

**Examples:**

```
# Export to stdout (useful for testing)
drush pd:export 242821

# Export to file
drush pd:export 242821 --output=/tmp/my-tree.json

# With field hint
drush pd:export 242821 --field=field_pd_manual_content --output=/tmp/tree.json

# Different default language
drush pd:export 242821 --langcode=en --output=/tmp/tree.json
```

### `drush pd:import  [options]`

[](#drush-pdimport-json-file-options)

Import a Pagedesigner element tree from JSON.

**Arguments:**

- `json-file` — Path to the JSON export file

**Options:**

- `--mode=MODE` — Import mode: `preserve` (default) or `clone`
- `--target-node=NID` — Target node ID (required for clone mode)
- `--target-field=FIELD` — Target pagedesigner field (required for clone mode)
- `--skip-existing` — Skip elements that already exist (useful for merges)
- `--dry-run` — Validate without writing
- `--sanitize-local-urls` — Convert absolute local URLs to relative paths (default: `1`/YES). Set to `0` to disable if you need absolute URLs

**Examples:**

```
# Surgical restore (preserve exact IDs)
drush pd:import /tmp/tree.json --dry-run
drush pd:import /tmp/tree.json

# Clone to different field (new IDs)
drush pd:import /tmp/tree.json \
  --mode=clone \
  --target-node=516014 \
  --target-field=field_pd_manual_content

# Clone with safety checks
drush pd:import /tmp/tree.json \
  --mode=clone \
  --target-node=516014 \
  --target-field=field_pd_manual_content \
  --dry-run
```

Modes Explained
---------------

[](#modes-explained)

### Preserve-IDs Mode (Default)

[](#preserve-ids-mode-default)

```
drush pd:import /tmp/tree.json
```

**How it works:**

- Creates/updates elements with **exact same IDs** from export
- All references (container, parent, children, styles) map directly
- Fails if any ID already exists on target system

**When to use:**

- Restoring from a known good snapshot to the exact same environment
- Safety: Only works if target IDs are available

**Risk:** Will fail gracefully if IDs collide, so safe to try.

### Clone Mode

[](#clone-mode)

```
drush pd:import /tmp/tree.json --mode=clone --target-node=516014 --target-field=field_pd_manual_content
```

**How it works:**

1. Creates new elements with auto-assigned IDs
2. Builds ID map: `{old_id → new_id}`
3. Fixes all references (container, parent, children, styles) using map
4. Updates target node's field to point to new root container

**When to use:**

- Duplicating content
- Migrating between sites
- Cloning to different node/field
- When you don't know if IDs exist on target

**Safer:** Always produces fresh IDs, avoids collisions.

JSON Format
-----------

[](#json-format)

Export format for reference:

```
{
  "root_id": 509413,
  "default_langcode": "de",
  "exported_at": 1713174000,
  "elements": {
    "509413": {
      "de": {
        "type": "container",
        "name": "Root Container",
        "status": true,
        "langcode": "de",
        "children": [
          { "target_id": 509414 },
          { "target_id": 509415 }
        ],
        "fields": {
          "field_content": []
        }
      },
      "en": {
        "type": "container",
        "name": "Root Container (EN)",
        "status": true,
        "langcode": "en",
        "children": [
          { "target_id": 509414 },
          { "target_id": 509415 }
        ]
      }
    },
    "509414": {
      "de": {
        "type": "content",
        "name": "Text Element",
        "status": true,
        "langcode": "de",
        "container": 509413,
        "parent": 509413,
        "fields": {
          "field_content": [
            { "value": "Hello World", "format": "basic_html" }
          ]
        }
      }
    }
  }
}
```

Workflow: Restoring Lost Content on Production
----------------------------------------------

[](#workflow-restoring-lost-content-on-production)

Your scenario: Node 516014 was broken on live, but you have a good local snapshot.

### Step 1: Export from local

[](#step-1-export-from-local)

```
# On local (with good snapshot activated)
drush pd:export 509413 --output=/tmp/backup-tree.json
# 509413 is field_pd_manual_content root container from good snapshot
```

### Step 2: Transfer file

[](#step-2-transfer-file)

```
# Local to production
scp /tmp/backup-tree.json user@production:/tmp/
```

### Step 3: Validate on production (dry-run)

[](#step-3-validate-on-production-dry-run)

```
# On production (in maintenance window)
drush pd:import /tmp/backup-tree.json --dry-run
# Should say "Validation successful"
```

### Step 4: Import on production

[](#step-4-import-on-production)

```
# The surgical restore
drush pd:import /tmp/backup-tree.json
# Wait for completion...
drush cr
```

### Step 5: Verify

[](#step-5-verify)

```
# Check the page loads
curl https://yoursite.com/de/node/516014
drush ws --severity=error --count=10  # Check for errors
```

Future: GUI Interface
---------------------

[](#future-gui-interface)

The module is designed for GUI extension. The Exporter/Importer services are independent of Drush and can be integrated into a web form:

- Export modal: Select node &gt; Select field &gt; Download JSON
- Import modal: Upload JSON &gt; Choose mode &gt; Dry-run &gt; Confirm

Troubleshooting
---------------

[](#troubleshooting)

### "Invalid JSON" error

[](#invalid-json-error)

The JSON file is corrupt or not readable:

```
# Test file validity
cat /tmp/tree.json | python3 -m json.tool > /dev/null && echo "Valid"
```

### "Element not found" during import

[](#element-not-found-during-import)

An element referenced as a child/style doesn't exist in the export:

```
# Check which elements are in the export
cat /tmp/tree.json | python3 -c "import sys, json; data=json.load(sys.stdin); print('Elements:', list(data['elements'].keys()))"
```

### "Target node does not have field" error

[](#target-node-does-not-have-field-error)

The target node doesn't have the specified pagedesigner field:

```
# Check available fields
drush devel:entity:load node 516014 --limit-fields | grep pagedesigner
```

### Clone mode ID mappings look wrong

[](#clone-mode-id-mappings-look-wrong)

Enable debug logging to see ID mappings during import:

```
# Check Drupal logs
drush ws --severity=notice --count=30 | grep pagedesigner_export
```

Development Notes
-----------------

[](#development-notes)

### Directory Structure

[](#directory-structure)

```
pagedesigner_export/
├── src/
│   ├── Commands/
│   │   └── PagedesignerExportCommands.php    # Drush commands
│   └── Service/
│       ├── Exporter.php                      # Tree walk & serialization
│       ├── Importer.php                      # Tree creation & ID fixing
│       └── IdMapper.php                      # ID mapping helper
├── pagedesigner_export.info.yml              # Module metadata
├── pagedesigner_export.services.yml          # Service definitions
├── drush.services.yml                        # Drush command registration
└── README.md                                 # This file
```

### API for GUI Integration

[](#api-for-gui-integration)

```
// In a custom form controller:
$exporter = \Drupal::service('pagedesigner_export.exporter');
$data = $exporter->export($element_id, $langcode);
$json = json_encode($data);

$importer = \Drupal::service('pagedesigner_export.importer');
$result = $importer->import($data, [
  'mode' => 'clone',
  'target_nid' => 516014,
  'target_field' => 'field_pd_manual_content',
  'dry_run' => TRUE,
]);
```

### Testing

[](#testing)

Verify both modes work correctly:

```
# Test export
drush pd:export 242821 --output=/tmp/test-export.json

# Test preserve-ids (requires fresh/dev DB)
drush pd:import /tmp/test-export.json --dry-run

# Test clone
drush pd:import /tmp/test-export.json \
  --mode=clone \
  --target-node=516014 \
  --target-field=field_pagedesigner_content \
  --dry-run
```

Security Considerations
-----------------------

[](#security-considerations)

- Module requires `administer site configuration` permission (Drush usage)
- No user-facing UI (yet) — requires trusted admin
- Import validates all field references before writing
- Dry-run mode prevents accidental data loss

See Also
--------

[](#see-also)

- [Pagedesigner Module](https://www.drupal.org/project/pagedesigner) — Core element management
- [Pagedesigner Debug Module](https://www.drupal.org/project/pagedesigner_debug) — Structure repair tools (do not use on production without understanding the impact)

Credits
-------

[](#credits)

Built to restore lost Pagedesigner content after the `pagedesigner_debug` module's orphan detection incorrectly flagged elements as dangling when multiple pagedesigner fields existed on the same node.

###  Health Score

26

—

LowBetter than 41% of packages

Maintenance58

Moderate activity, may be stable

Popularity19

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity16

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% 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.

### Community

Maintainers

![](https://www.gravatar.com/avatar/43082de1b2e2b2b2d48b7ff7dbc7c60ff1d0a015123d4062ac0b49ea0b3b725f?d=identicon)[iqual](/maintainers/iqual)

---

Top Contributors

[![youpixxl](https://avatars.githubusercontent.com/u/3398689?v=4)](https://github.com/youpixxl "youpixxl (2 commits)")

### Embed Badge

![Health badge](/badges/iqual-pagedesigner-export/health.svg)

```
[![Health](https://phpackages.com/badges/iqual-pagedesigner-export/health.svg)](https://phpackages.com/packages/iqual-pagedesigner-export)
```

###  Alternatives

[kartik-v/yii2-money

An advanced money mask input for Yii 2.0 based on jquery maskmoney plugin styled for Bootstrap 3.

29961.3k10](/packages/kartik-v-yii2-money)[shieldfy/sniffer

sniff input types

101.5k1](/packages/shieldfy-sniffer)

PHPackages © 2026

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