<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Davy</title>
    <description>The latest articles on DEV Community by Davy (@pagecleanr).</description>
    <link>https://dev.to/pagecleanr</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4119483%2F2cb6face-9b9c-4223-8477-ff236d506fec.jpg</url>
      <title>DEV Community: Davy</title>
      <link>https://dev.to/pagecleanr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pagecleanr"/>
    <language>en</language>
    <item>
      <title>How Facebook Page bulk deletion actually works with the Graph API</title>
      <dc:creator>Davy</dc:creator>
      <pubDate>Thu, 10 Sep 2026 14:10:15 +0000</pubDate>
      <link>https://dev.to/pagecleanr/how-facebook-page-bulk-deletion-actually-works-with-the-graph-api-4d59</link>
      <guid>https://dev.to/pagecleanr/how-facebook-page-bulk-deletion-actually-works-with-the-graph-api-4d59</guid>
      <description>&lt;p&gt;There is no Meta endpoint called “bulk delete Facebook Page posts.”&lt;/p&gt;

&lt;p&gt;If you have ever searched for one, you already know the trap: Business Suite can delete a small batch, Activity Log is click-hell, and Chrome extensions pretend there is a bulk API by driving the website UI. Under the hood, official deletion is still one HTTP call per post.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;DELETE https://graph.facebook.com/v21.0/{page-id}_{post-id}
Authorization: Bearer {page-access-token}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful response is just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ten thousand posts means ten thousand of those calls. That is the whole architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can actually delete
&lt;/h2&gt;

&lt;p&gt;With a &lt;strong&gt;Page&lt;/strong&gt; access token and &lt;code&gt;pages_manage_posts&lt;/code&gt;, Graph will delete regular Page posts the Page published:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;status updates&lt;/li&gt;
&lt;li&gt;photos that appear as feed items&lt;/li&gt;
&lt;li&gt;Reels that appear in the feed&lt;/li&gt;
&lt;li&gt;link shares&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Scan the feed (&lt;code&gt;GET /{page-id}/feed&lt;/code&gt; or &lt;code&gt;/published_posts&lt;/code&gt;), filter by &lt;code&gt;created_time&lt;/code&gt;, then &lt;code&gt;DELETE&lt;/code&gt; each matching id.&lt;/p&gt;

&lt;p&gt;Scopes that matter in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pages_show_list&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pages_manage_posts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pages_read_engagement&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pages_read_user_content&lt;/code&gt; if you filter on comments/reactions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Missing them usually comes back as Graph error &lt;strong&gt;10&lt;/strong&gt; or &lt;strong&gt;200&lt;/strong&gt;. Reconnect Login. Do not paper over it with a pasted user token.&lt;/p&gt;

&lt;h2&gt;
  
  
  What never deletes
&lt;/h2&gt;

&lt;p&gt;This is the part most “bulk delete API” posts skip:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;profile pictures and cover photos&lt;/li&gt;
&lt;li&gt;story highlights&lt;/li&gt;
&lt;li&gt;personal profile timeline posts (not a Page)&lt;/li&gt;
&lt;li&gt;group posts&lt;/li&gt;
&lt;li&gt;posts that are already gone (you get a failed DELETE, not a crash)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CSV export of ids/dates/links is a checklist, not a restore. Graph does not give you the image files back, and there is no undo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bulk is a loop, not a batch
&lt;/h2&gt;

&lt;p&gt;A naive script looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;postIds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://graph.facebook.com/v21.0/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DELETE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pageToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// log and continue — one failure must not abort 8,000 remaining ids&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That script will work on a 50-post test Page and fall over on a real one. Meta rate-limits &lt;strong&gt;the Page&lt;/strong&gt; and &lt;strong&gt;the app&lt;/strong&gt;. “Done in seconds” is a lie once you leave a demo dataset.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate limits you will actually see
&lt;/h2&gt;

&lt;p&gt;When Graph throttles you, the JSON error code is usually &lt;strong&gt;4, 17, 32, or 613&lt;/strong&gt;, plus messages like “too many calls” or “request limit reached.” Also read the headers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;X-Page-Usage&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;X-App-Usage&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A workable policy we use in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;usage around 80% → slow the loop&lt;/li&gt;
&lt;li&gt;usage near 95%, or those error codes → pause, wait, resume&lt;/li&gt;
&lt;li&gt;backoff that has survived real Pages: 5 min, 5 min, 10 min, then 15 min&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Token errors are different. Code &lt;strong&gt;190&lt;/strong&gt; means reconnect Facebook Login and start a new job for leftovers. Retrying the same expired token does nothing.&lt;/p&gt;

&lt;p&gt;If you want the production notes (codes, headers, what we skip), I wrote up &lt;a href="https://pagecleanr.com/guides/facebook-bulk-delete-api" rel="noopener noreferrer"&gt;how Graph API page deletion works in production&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do this on a server, not in a browser tab
&lt;/h2&gt;

&lt;p&gt;Chrome extensions that “bulk delete” keep &lt;code&gt;facebook.com&lt;/code&gt; open and click the UI. That breaks when Meta changes markup, and it is not the Pages API.&lt;/p&gt;

&lt;p&gt;A Graph job should run on a worker:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;queue the post ids&lt;/li&gt;
&lt;li&gt;DELETE with pacing&lt;/li&gt;
&lt;li&gt;persist progress so a pause or a closed laptop does not lose the cursor&lt;/li&gt;
&lt;li&gt;stream a log to the browser if someone wants to watch&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Closing the tab should not stop deletion. Staying on the page is optional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tokens
&lt;/h2&gt;

&lt;p&gt;Use a &lt;strong&gt;Page&lt;/strong&gt; access token, not a user token. Hold it in short-lived server memory/cache. Do not write raw tokens to your primary database. They expire on the order of an hour unless you exchange them, and you should have a “clear token when the job finishes” switch.&lt;/p&gt;

&lt;p&gt;I work on PageCleanr, which wraps this loop (preview, CSV checklist, pause/resume). The Graph behavior above is Meta’s, not a product feature. If you are building your own cleaner, start with one &lt;code&gt;DELETE&lt;/code&gt;, one Page, and the usage headers — then add the queue.&lt;/p&gt;

</description>
      <category>api</category>
      <category>meta</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
