Pushword supports a Git-integrated content workflow for power users who manage content through Git repositories. This allows marketing teams to edit content via flat files while keeping the codebase separate.
The recommended setup uses Git submodules to separate content from code:
project/
├── src/ # Application code
├── content/ # Git submodule (content repo)
│ └── your-host/
│ ├── homepage.md
│ ├── about.md
│ └── media/
└── var/
└── flat-sync/ # Lock and state files
Marketing teams only access the content repository, never seeing the source code.
Power users can control the production lock state via curl requests.
curl -X POST https://prod.example.com/api/flat/lock \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"reason": "Bulk content update in progress",
"ttl": 7200
}'
Parameters:
host (optional): Target specific host, omit to lock all hosts (global lock)reason (optional): Message shown to admin usersttl (optional): Lock duration in seconds (default: 1 hour)curl -X POST https://prod.example.com/api/flat/unlock \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
To unlock a specific host only:
curl -X POST https://prod.example.com/api/flat/unlock \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"host": "example.com"}'
# Check global lock status
curl https://prod.example.com/api/flat/status \
-H "Authorization: Bearer YOUR_API_TOKEN"
# Check specific host lock status
curl https://prod.example.com/api/flat/status?host=example.com \
-H "Authorization: Bearer YOUR_API_TOKEN"
Response:
{
"locked": true,
"isWebhookLock": true,
"remainingSeconds": 3542,
"lockInfo": {
"locked": true,
"lockedAt": 1706000000,
"lockedBy": "webhook",
"ttl": 3600,
"reason": "Bulk content update",
"lockedByUser": "user@example.com"
}
}
API tokens are managed per-user through the admin interface.
The token can be regenerated or revoked at any time from the same interface.
curl -X POST https://prod.example.com/api/flat/lock \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"reason": "Marketing update Q1"}'
git add -A && git commit -m "Update Q1 content" && git push
curl -X POST https://prod.example.com/api/flat/unlock \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
When locked via webhook:
name: Deploy Content
on:
push:
branches: [main]
paths: ['content/**']
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Wait for unlock
run: |
for i in {1..30}; do
STATUS=$(curl -s "${{ secrets.PROD_URL }}/api/flat/status" \
-H "Authorization: Bearer ${{ secrets.FLAT_API_TOKEN }}")
if echo "$STATUS" | jq -e '.locked == false' > /dev/null; then
echo "Unlocked, proceeding..."
exit 0
fi
echo "Locked, waiting 10s... (attempt $i/30)"
sleep 10
done
echo "Timeout waiting for unlock"
exit 1
- name: Deploy and sync
run: |
ssh user@server "cd /var/www && git pull && php bin/console pw:flat:sync"
deploy-content:
stage: deploy
script:
- |
for i in $(seq 1 30); do
STATUS=$(curl -s "$PROD_URL/api/flat/status" -H "Authorization: Bearer $FLAT_API_TOKEN")
if echo "$STATUS" | jq -e '.locked == false' > /dev/null; then
echo "Proceeding with deployment..."
break
fi
echo "Locked, waiting... ($i/30)"
sleep 10
done
- ssh user@server "cd /var/www && git pull && php bin/console pw:flat:sync"
only:
changes:
- content/**
When both admin and flat files are modified, conflicts are resolved automatically using a "most recent wins" strategy.
filename~conflict-{id}.md# List conflict files
find content/ -name "*~conflict-*"
# Clear all conflict files after review
php bin/console pw:flat:conflicts:clear --dry-run
php bin/console pw:flat:conflicts:clear
Configure email notifications for conflicts and errors:
# config/packages/flat.yaml
flat:
notification_email_recipients:
- admin@example.com
- devops@example.com
notification_email_from: noreply@example.com
Access via Admin > Notifications to:
flat:
# Existing options
flat_content_dir: '%kernel.project_dir%/content/_host_'
change_detection_cache_ttl: 300
auto_export_enabled: true
use_background_export: true
lock_ttl: 1800
auto_lock_on_flat_changes: true
# Webhook lock options
webhook_lock_default_ttl: 3600 # Default 1 hour
# Notification options
notification_email_recipients: []
notification_email_from: null
# Lock/unlock via CLI
php bin/console pw:flat:lock [host] --reason="Manual lock" --ttl=3600
php bin/console pw:flat:unlock [host]
# Clear conflicts
php bin/console pw:flat:conflicts:clear [host] --dry