Why this works
Because ScreenJSON is text, git diff just works. Because every element
carries a UUID, changes to one scene don’t cascade into spurious diffs
on unrelated scenes. Because text is language-keyed, you can even see
translation changes as additions to a language map rather than replacements.
A reasonable repo layout
screenplays/
my-script/
manuscript.fdx # source in your writing tool
screenplay.json # canonical ScreenJSON
revisions/
2026-01-10-blue.json
2026-01-15-pink.json
notes/ # optional prose markdown
A pre-commit hook
Keep the canonical screenplay.json in sync with the source .fdx:
#!/usr/bin/env bash
# .git/hooks/pre-commit
set -e
for fdx in screenplays/**/manuscript.fdx; do
dir="$(dirname "$fdx")"
docker run --rm -v "$PWD/$dir:/data" screenjson/cli \
convert -i /data/manuscript.fdx -o /data/screenplay.json
git add "$dir/screenplay.json"
done
Pretty-printing for diffs
Commit the ScreenJSON pretty-printed — it diffs cleanly line-by-line:
screenjson convert -i manuscript.fdx | jq . > screenplay.json
Keeping UUIDs stable
Use a converter that honours existing UUIDs. Both screenjson-cli and
screenjson-export will, by default, reuse an existing
screenplay.json if one is present next to the source to keep UUIDs
stable across regenerations.
Git attributes
Mark ScreenJSON files for text diffing:
# .gitattributes
*.json diff
Automatic commits
screenjson-server can give each script its own
Git repository and commit after every burst of edits, pushing to GitHub,
GitLab or Bitbucket, so changes made through the API or by AI agents land in
history without anyone running git.