
How I Fixed the Postiz X Media Upload Bug: A First-Hand Account
The Problem That Drove Me Crazy
I was using Postiz to schedule posts to X (Twitter) when I discovered a frustrating issue. My text posts to X worked perfectly, but any post with images or videos would show as "published" in the Postiz calendar while never actually appearing on X.
After testing both text-only and media posts, I confirmed that text posts worked but media posts failed silently. This told me it wasn't an authentication issue - something was specifically wrong with how Postiz handled media uploads to X.
Getting My Hands Dirty with Container Access
I accessed the Postiz container to investigate:
bash
docker exec -it postiz-service sh
Then I checked the worker logs to see what was happening:
bash
pm2 logs workers --lines 50
Buried in the logs, I found the smoking gun:
$.media.media_ids[0]: object found, string expected
This error from X's API told me exactly what was wrong - Postiz was sending media IDs as objects when X expected strings.
Hunting Down the Bug in the Code
I needed to find where Postiz handles media uploads for X. I searched for the relevant files:
bash
find /app -name "*.ts" | xargs grep -l "media_ids"
This pointed me to the X provider file. I examined the source code:
bash
cat /app/libraries/nestjs-libraries/src/integrations/social/x.provider.ts
Looking at the media upload logic around lines 314-355, I could see the problem. The code assumed client.v1.uploadMedia()
would always return a string, but in newer versions of the twitter-api-v2 library, it returns an object like {media_id_string: "123", media_id: 123}
.
I confirmed this was a version issue by checking:
bash
cat /app/package.json | grep twitter-api-v2
Postiz was using "twitter-api-v2": "^1.24.0"
, which spans versions where this API response format changed.
Making the Fix
I backed up the original file first:
bash
cp /app/libraries/nestjs-libraries/src/integrations/social/x.provider.ts /app/libraries/nestjs-libraries/src/integrations/social/x.provider.ts.backup
Instead of manually editing the file, I used sed to make the precise change:
bash
sed -i 's/acc\[val\.postId\]\.push(val\.id);/acc[val.postId].push(typeof val.id === "object" ? val.id.media_id_string || String(val.id.media_id) : val.id);/' /app/libraries/nestjs-libraries/src/integrations/social/x.provider.ts
This command replaced the problematic line that just pushed val.id
with code that checks if it's an object and extracts the string value appropriately.
I verified the change was applied:
bash
grep -n "typeof val.id" /app/libraries/nestjs-libraries/src/integrations/social/x.provider.ts
The Build Challenge
When I tried to rebuild the application, I hit a memory issue:
bash
npm run build:backend
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
I solved this by increasing Node.js memory allocation:
bash
export NODE_OPTIONS="--max-old-space-size=4096"rm -rf node_modules/.cachenpm run build:backend
This time the build completed successfully.
Restarting and Testing
I restarted the workers to load the new code:
bash
pm2 restart workerspm2 status
Initially, I saw some Redis connection errors in the logs, but I realized these were old timestamps from before the restart. To confirm Redis was working, I tested connectivity from within the container:
bash
docker exec -it postiz-service sh -c "nc -zv redis-cache 6379"
Getting redis-cache (172.20.0.3:6379) open
confirmed the connection was working.
The Moment of Truth
I monitored the logs in real-time while testing:
bash
docker exec -it postiz-service sh -c "pm2 logs workers --follow"
Then I created a test post in Postiz with an image attachment and scheduled it to X.
It worked. The post appeared on X with the media properly attached, and the dreaded $.media.media_ids[0]: object found, string expected
error was gone.
Success
What I Learned
The fix came down to one line of defensive programming that handles both the old string format and new object format returned by the twitter-api-v2 library:
typescript
acc[val.postId].push(typeof val.id === "object" ? val.id.media_id_string || String(val.id.media_id) : val.id);
This approach ensures backward compatibility while fixing the immediate issue.
The most frustrating part was that this was a silent failure - posts showed as "published" in Postiz but never appeared on X. The only way to catch it was by digging into the container logs and finding that specific API error message.
Using sed for the fix was cleaner than opening an editor, especially since I knew exactly what needed to change. The precise regex replacement ensured I modified only the problematic line without risking any other changes to the codebase.
Why This Happened
This bug occurred because Postiz uses a version range (^1.24.0
) for the twitter-api-v2 dependency, and somewhere within that range, the library changed how uploadMedia()
returns media IDs. The code worked fine with older versions that returned strings, but broke when the library started returning objects.
The fix I implemented handles both formats, so the issue won't resurface even if the dependency gets updated again. It's a simple but effective solution that turns a breaking change into a non-issue through defensive programming.