PHPackages                             kugarocks/bookstack-content-sync - 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. [File &amp; Storage](/categories/file-storage)
4. /
5. kugarocks/bookstack-content-sync

ActiveLibrary[File &amp; Storage](/categories/file-storage)

kugarocks/bookstack-content-sync
================================

Pulling BookStack content into a local directory, making changes, and pushing it back.

v0.2.0(1mo ago)033MITPHPPHP ^8.2

Since Apr 10Pushed 1mo agoCompare

[ Source](https://github.com/kugarocks/bookstack-content-sync)[ Packagist](https://packagist.org/packages/kugarocks/bookstack-content-sync)[ RSS](/packages/kugarocks-bookstack-content-sync/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (8)Dependencies (4)Versions (9)Used By (0)

bookstack-content-sync
======================

[](#bookstack-content-sync)

Pulling BookStack content into a local directory, making changes, and pushing it back.

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

[](#requirements)

- PHP `>=8.2`
- BookStack `>=26.03`

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

[](#quick-start)

### Initialize Directory

[](#initialize-directory)

Create a local content directory and starter `sync.json`:

```
php artisan bookstack:init-content-dir /path/to/content
```

[![init-content-dir](https://raw.githubusercontent.com/kugarocks/bookstack-content-sync/main/docs/images/init-content-dir.png)](https://raw.githubusercontent.com/kugarocks/bookstack-content-sync/main/docs/images/init-content-dir.png)

This command creates the target directory if needed, writes `sync.json`, and reminds you which environment variables to export before running a pull.

```
{
    "version": 1,
    "app_url": "http://localhost:8080",
    "bookstack_path": "/path/to/bookstack",
    "content_path": "content",
    "env_vars": {
        "token_id": "BOOKSTACK_API_TOKEN_ID",
        "token_secret": "BOOKSTACK_API_TOKEN_SECRET"
    }
}
```

### Pull Content

[](#pull-content)

```
php artisan bookstack:pull-content /path/to/content
```

[![pull-content](https://raw.githubusercontent.com/kugarocks/bookstack-content-sync/main/docs/images/pull-content.png)](https://raw.githubusercontent.com/kugarocks/bookstack-content-sync/main/docs/images/pull-content.png)

### Push Plan

[](#push-plan)

```
php artisan bookstack:push-content /path/to/content
```

[![push-plan](https://raw.githubusercontent.com/kugarocks/bookstack-content-sync/main/docs/images/push-plan.png)](https://raw.githubusercontent.com/kugarocks/bookstack-content-sync/main/docs/images/push-plan.png)

### Push Execution

[](#push-execution)

```
php artisan bookstack:push-content /path/to/content --execute
```

[![push-execution](https://raw.githubusercontent.com/kugarocks/bookstack-content-sync/main/docs/images/push-execution.png)](https://raw.githubusercontent.com/kugarocks/bookstack-content-sync/main/docs/images/push-execution.png)

How it Works
------------

[](#how-it-works)

This system performs a one-way sync from local content to BookStack by computing state differences and applying them to the remote.

### Push Mechanism

[](#push-mechanism)

- Sync is one-way: local content is the source of truth.
- `bookstack:push-content` builds the current local state.
- It compares this state with the previous `snapshot.json`.
- Based on the diff, it determines and executes the required remote actions.

### Behavior &amp; Constraints

[](#behavior--constraints)

- **Data constraint**
    - A book can belong to only one shelf at a time.
- **Naming &amp; ordering**
    - Prefixes such as `01-xxx`, `02-xxx` are used to define ordering.
    - Items are sorted lexicographically based on these prefixes.
- **Tag ordering**
    - Tag order is preserved in local files and treated as part of content changes.
- **Renaming behavior**
    - Renaming local files or directories only updates the `file` field in `snapshot.json`.
    - It does not affect the identity of the corresponding remote entity.

### Slug Behavior

[](#slug-behavior)

- Official BookStack does not preserve custom slugs for content entities via the API.
- Hosts with [custom slug support](https://github.com/kugarocks/BookStack/commit/e6c75b4d13dab676424461c210b14f730c2a6ad3) enable this behavior.
- If the requested slug is not preserved, `push-content` treats the remote slug as the source of truth.
- The command emits a warning and rewrites both the local file slug and the `snapshot.json` slug to match the remote value.

### Empty Page Behavior

[](#empty-page-behavior)

- Because the BookStack API does not support empty page content, remote sync uses a reserved transport placeholder while local empty pages remain `""`.
- When pushing an empty page, the remote transport uses the reserved placeholder ``.
- When pulling, that placeholder is decoded back to `""`, and `snapshot.json` plus content hashing continue to use the decoded empty-string value.

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

[](#installation)

### Packagist

[](#packagist)

```
composer require kugarocks/bookstack-content-sync
```

### Local Development

[](#local-development)

For local development against an unpublished working tree, add the local repository to the BookStack host `composer.json`:

```
{
  "repositories": [
    {
      "type": "path",
      "url": "../bookstack-content-sync",
      "options": {
        "symlink": true
      }
    }
  ]
}
```

Then require the package from the BookStack host:

```
composer require kugarocks/bookstack-content-sync:*@dev
```

The `@dev` suffix is only needed for local path installation while the package is resolved as `dev-main`.

Global Wrapper
--------------

[](#global-wrapper)

Create a global command that can be run from any content directory:

```
# From a BookStack installation
ln -sf /path/to/bookstack/vendor/kugarocks/bookstack-content-sync/bin/bookstack-sync /usr/local/bin/bookstack-sync

# From this repository
ln -sf /path/to/bookstack-content-sync/bin/bookstack-sync /usr/local/bin/bookstack-sync
```

The wrapper reads `sync.json` in your current directory and runs the matching BookStack artisan command using `bookstack_path`.

### Setup

[](#setup)

Set the global BookStack path (one-time setup):

```
bookstack-sync config set-bookstack-path /path/to/bookstack
```

This creates `~/.config/bookstack-content-sync/config.json` with your BookStack installation path.

### Usage

[](#usage)

```
# Initialize a new content directory
bookstack-sync init /path/to/content

# Pull content
bookstack-sync pull

# Generate push plan
bookstack-sync push

# Execute push
bookstack-sync push --execute
```

### Configuration

[](#configuration)

Path resolution priority:

1. `bookstack_path` in current directory's `sync.json`
2. Global config at `~/.config/bookstack-content-sync/config.json`
3. Error if neither is found

Example `sync.json` with project-specific path:

```
{
    "version": 1,
    "app_url": "http://localhost:8080",
    "bookstack_path": "/path/to/bookstack",
    "content_path": "content",
    "env_vars": {
        "token_id": "BOOKSTACK_API_TOKEN_ID",
        "token_secret": "BOOKSTACK_API_TOKEN_SECRET"
    }
}
```

When running `bookstack-sync init`, the `bookstack_path` is automatically written to `sync.json` using the global config value.

Testing
-------

[](#testing)

Install dependencies and run the full package test suite:

```
composer install
composer test
```

Test boundaries:

- Unit tests validate isolated sync logic.
- Integration tests execute real local file reads and writes in temporary directories.
- Integration tests do not call a real BookStack server; HTTP is mocked through the package test shim.

Run only unit tests:

```
composer test-unit
```

Run only integration tests:

```
composer test-integration
```

Run pull-focused tests:

```
composer test-pull
```

Run push-focused tests:

```
composer test-push
```

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance89

Actively maintained with recent releases

Popularity7

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity41

Maturing project, gaining track record

 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.

###  Release Activity

Cadence

Every ~1 days

Total

8

Last Release

54d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/68dd09fbc0485062286b5aa8acdf065aef056a871ab37f9b9f5d950a86bdc0e0?d=identicon)[kugarocks](/maintainers/kugarocks)

---

Top Contributors

[![kugarocks](https://avatars.githubusercontent.com/u/7481255?v=4)](https://github.com/kugarocks "kugarocks (121 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/kugarocks-bookstack-content-sync/health.svg)

```
[![Health](https://phpackages.com/badges/kugarocks-bookstack-content-sync/health.svg)](https://phpackages.com/packages/kugarocks-bookstack-content-sync)
```

###  Alternatives

[aws/aws-sdk-php

AWS SDK for PHP - Use Amazon Web Services in your PHP project

6.3k532.1M2.5k](/packages/aws-aws-sdk-php)[google/cloud

Google Cloud Client Library

1.2k16.5M56](/packages/google-cloud)[eslazarev/wildberries-sdk

Wildberries OpenAPI clients (generated).

232.5k](/packages/eslazarev-wildberries-sdk)[spatie/laravel-health

Monitor the health of a Laravel application

88011.3M149](/packages/spatie-laravel-health)[drupal/core

Drupal is an open source content management platform powering millions of websites and applications.

21764.8M1.6k](/packages/drupal-core)[drupal/core-recommended

Locked core dependencies; require this project INSTEAD OF drupal/core.

6941.5M395](/packages/drupal-core-recommended)

PHPackages © 2026

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