Twitter Hide Ads

Removes promoted posts from the official X Android app. Suppression happens before Compose renders the post, so an advertisement leaves no gap and no placeholder behind.

What it removes

  • Promoted posts on the Home timeline.
  • Promoted posts nested inside a post detail view and inside search results.
  • Promoted videos in the full-screen Video Tab, filtered at the data layer before the pager is created.

Ordinary posts, replies and search results are passed through untouched.

Scope

The module declares a static scope of:

com.twitter.android

META-INF/xposed/scope.list carries that declaration, so no scope selection is required in the manager. Do not add other applications.

Requirements

  • A framework implementing the modern Xposed API, version 101 or newer. The legacy de.robv.android.xposed API is not used, so a framework implementing only that API cannot load this module.
    • Rooted: Vector on Android 8.1 or newer, with Magisk or KernelSU and Zygisk enabled.
    • Rootless: LSPatch, the JingMatrix fork. It embeds Vector into a patched X APK and loads modern libxposed modules through the same runtime. The archived LSPosed/LSPatch build predates the modern API and cannot load this module.
  • Android 8.0 (API 26) or newer.
  • The official X application (com.twitter.android).

Installation

  1. Install the APK from the release attached to this repository.
  2. Enable Twitter Hide Ads in the framework manager. With LSPatch, select it when patching X in manager mode, or embed it in integrated mode.
  3. Force-stop X once, then reopen it.
  4. Framework log entries are tagged:
[TwitterHideAds]

How it works

X ships a fully obfuscated, Compose-rendered timeline whose class and method names change with almost every release, so the module resolves its hooks structurally at runtime rather than by name.

Discovery is deferred until Application.attach(), when X has supplied its real application context and final class loader. If the installed X version matches one of the validated builds, an exact compatibility profile is used. Otherwise DexKit enumerates the application's static void methods, and each candidate is scored by the role its parameters play — a Composer followed by the compiler-generated $changed mask, a post model in first position, and any Modifier or layout-scope parameters in between — rather than by parameter position. Every boundary at or above the activation threshold is hooked, because the Kotlin Compose compiler emits both a defaulted and a non-defaulted entry point for a single composable.

Each resolved boundary is deoptimized through XposedInterface.deoptimize. ART inlines these small composables into their callers, and without deoptimization a compiled caller keeps running its inlined copy and the hook is never reached.

At render time a post model is classified from its own fields: an entry identifier carrying the promoted- token, or a promoted-metadata field, with a bounded action-graph walk as fallback where that metadata class has itself been obfuscated away. Only the Home timeline names an entry promoted-tweet-<id>-<hash>; every surface that nests a post inside a module prefixes its own entry, so the same advertisement arrives as conversationthread-<id>-promoted-tweet-<id>-<hash> in a post detail. Matching the token at any - segment boundary is what makes suppression app-wide rather than Home-only.

Resolved boundaries are cached by X version and APK fingerprint, and rescanned after an X update. Unresolved boundaries fail open, so the timeline is never left blank.

Validation

Validated on device against X 12.23.1-prod.01 under Vector 2.2. The adaptive resolver installed 11 deoptimized boundaries reporting enforcement=ACTIVE_ADAPTIVE, and promoted entries were blocked before render on both the Home timeline and in post detail. Video Tab dataset filtering remains scoped to callers under com.x.video.tab.

Troubleshooting

Problem Suggested action
Promoted posts still appear Confirm the module is enabled and check whether the log reports enforcement=ACTIVE_ADAPTIVE. FAIL_OPEN_NO_BOUNDARY means no boundary was resolved.
Hooks install but nothing is blocked Check deoptimized= in the initialization line. 0 alongside deoptimizeSupported=false means the framework interface was never attached to the module entry.
no-structural-candidate after an update X changed its render boundary shape. Capture the DexKit structural query and Adaptive boundary log lines and open an issue.
Stale boundary after an X update Resolution is cached by X version and APK fingerprint and should rescan automatically. Force-stop X once to trigger a fresh resolution.

Credits

Project Contribution
DexKit by LuckyPray Runtime DEX parsing and discovery of obfuscated classes and methods. Apache-2.0.
Vector by JingMatrix The ART hooking framework, and the method deoptimization used to reach inlined composables. GPL-3.0.
LSPatch by JingMatrix Embeds Vector into a patched APK, which is how this module runs without root. GPL-3.0.
libxposed API The modern Xposed module API this module compiles against. Apache-2.0.

Disclaimer

Not affiliated with, endorsed by, or sponsored by X Corp. or Twitter. The module modifies the app's behaviour in memory on the user's own device; it does not redistribute or patch the X APK, and it transmits nothing off the device. Provided for educational and personal use. App updates may break the hooks without notice.

Releases

2.1.0

Stable

9/9/2026, 1:37:49 PM

First release in the Xposed Module Repository.

Removes promoted posts from the X Android app on the Home timeline, in post detail, in search, and in the full-screen Video Tab. Requires a framework implementing the modern Xposed API 101 or newer: Vector with root, or the JingMatrix fork of LSPatch without.

The application ID is io.github.mrxsin.twitterhideads. If you previously installed a build under the old my.MrxSiN prefix, this installs alongside it rather than upgrading it, so uninstall the old copy.

Assets

1