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

3.1.0

Stable

10/26/2026, 6:02:56 PM

Twitter Hide Ads v3.1.0

Google SSP ads removed, and the module speaks 14 languages

X 12.37 started showing real-time-bidding ads served through Google SSP. They are not posts, so 3.0.0 never saw them: they appeared on Home, between conversation replies and as full pages in the Video Tab, while promoted posts were still blocked. This release removes them.

Fixed

  • Real-time-bidding ads (UrtTimelineRtbImageAd, the cards with an "Ad" label and an Open or Install button) are removed on Home, in conversations and in the Video Tab.
    • Video Tab and conversations: the ads are dropped from the timeline's paging state before it is built. A conversation module is dropped only when everything inside it is such an ad.
    • Home: an RTB ad is handed to X's own empty presenter, the one X itself uses when Google SSP is off, so nothing is drawn and no gap is left.
    • Event Summaries, which X 12.37.2 also renders through its Google SSP code, are left alone.
  • The hooks are found by structure through DexKit once per X build and cached, so later starts add only a few milliseconds. Anything ambiguous installs nothing.
  • The inlined-caller lookup no longer fails with UnsatisfiedLinkError when the boundary resolver was served from its cache.

Added

  • The module description shown in Vector and LSPosed is translated into Simplified and Traditional Chinese, Spanish, Portuguese, Russian, Japanese, Korean, Indonesian, Hindi, Arabic, Turkish, Vietnamese, French and German.
  • A short user README in Simplified Chinese, Traditional Chinese, Spanish, Portuguese, Russian, Japanese, Korean and Indonesian, linked from a language bar on every README (#1).

Verified

  • On a Pixel 8 Pro, Android 17, Vector 2.2, X 12.37.2-prod.01: 3 full passes of 25 Home, 20 conversation and 20 Video Tab screens each showed no ad, with promoted posts still blocked and no crashes. Without the fix the same check found ads on most conversation screens and almost every Video Tab page.
  • 74 JVM tests, including the 411 162-comparison policy parity suite and the new RTB filter tests.
  • The first start after an X update spends about 3 s on DexKit discovery for the new hooks; later starts take 2–16 ms.

LSPatch, 32-bit and x86 devices were not run for this release.

Version metadata

  • Version name: 3.1.0
  • Android version code: 35
  • Git tag: v3.1.0
  • APK filename: TwitterHideAds-v3.1.0.apk

Updating

Install the v3.1.0 APK over v3.0.0, keep X (com.twitter.android) as the module's only scope, then force-stop and reopen X once. Confirm the framework log reports enforcement=ACTIVE_ADAPTIVE and RTB ad filter initialized with a mapper= other than none.

Assets

1

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

3.0.0

Stable

10/15/2026, 7:26:43 PM

Twitter Hide Ads v3.0.0

A hand-written Brainfuck policy core, compiled ahead of time

This release moves every filtering decision into Brainfuck, without changing what the module removes.

Changed

  • The filtering and decision policy is now three hand-written Brainfuck programs in libtwitterbf.so (brainfuck/src/post.bf, action.bf, discovery.bf), compiled ahead of time to C and then to native code with the NDK. There is no interpreter in the APK.
    • post: the entry-identifier grammar (segment boundaries, nested conversationthread-… and search-conversation-… prefixes, the 256-character limit), the "mentions promoted" ambiguity rule, verdict precedence, Video Tab item classes, the safe-mixed-batch rule and the copy and verification checks.
    • action: whether a value found by the fallback walk names a promoted post action.
    • discovery: render and Video Tab boundary eligibility, scores and thresholds, the exact-profile version table, and the discovery and traversal limits.
  • Hooks, DexKit discovery, reflection, caching and Kotlin list rebuilding stay in Java. Each render makes one @FastNative call with the post's text fields and a metadata flag, and allocates nothing.
  • Faster on a Pixel 8 Pro (release build): an organic post is classified in 2.4 µs (was 14.9 µs), a promoted post in 1.1 µs (was 6.1 µs), a 20-item Video Tab batch in 6.8 µs (was 275 µs).
  • Fails open on any policy failure: a missing library or ABI mismatch installs no hook, and an oversized, malformed or budget-exhausted request keeps the content.
  • New adaptive vector launcher icon — a struck-through shield over the X mark — with a themed (monochrome) layer.
  • Per-render boundary observation logs are debug-only; block logs now carry policy counters.
  • CI is split into test, build and release jobs. Signing secrets exist only in the release job, and the build checks that every native library ships in the APK.
  • The README follows the ThreadsHideAds layout; the design is documented in docs/BRAINFUCK_ARCHITECTURE.md and each program has a normative specification in docs/policy/.

Also in this release

  • Timeline models are classified PROMOTED, ORGANIC or UNKNOWN. A confident organic entry identifier skips the reflective action-graph walk, and the walk's result is cached per model instance.
  • Entry identifiers must match the whole-value grammar, so not-promoted-x, unpromoted-1 or free text containing promoted- never count.
  • Adaptive boundaries are witnessed at runtime and unhooked after 12 invocations without a post-like model.
  • The DexFile fallback covers com.x.urt, com.x.jetfuel and com.x.mappers, post package first.
  • Video Tab replacements are built through the persistent list's own builder; the dynamic-proxy emulation is removed.
  • Release-build logs redact entry identifiers, and the unique-block key set is bounded to 512 entries.

Verified

  • Parity with the 2.1.0 Java policy, kept as a frozen test oracle: 411 162 old-versus-new comparisons per run on the JVM and on arm64, including every character from U+0000 to U+FFFF, plus randomized, malformed-frame, concurrency and "organic is never blocked" tests.
  • On a Pixel 8 Pro, Android 17, Vector 2.2, X 12.28.0-prod.01: a cold DexKit rescan scored by the Brainfuck policy selected the same 9 boundaries with the same scores as 2.1.0; promoted posts blocked before render on the Home timeline and in post detail (conversationthread-<id>-promoted-tweet-<id>-<hash>); organic posts and replies untouched; search functional; Video Tab batches of 13, 24 and 35 entries filtered to 11, 22 and 31 with paging intact; no policy failures, crashes or ANRs.

No promoted search entry was served during the session. LSPatch, 32-bit and x86 devices were not run for this release.

Version metadata

  • Version name: 3.0.0
  • Android version code: 34
  • Git tag: v3.0.0
  • APK filename: TwitterHideAds-v3.0.0.apk

Updating

Install the v3.0.0 APK over v2.1.0, keep X (com.twitter.android) as the module's only scope, then force-stop and reopen X once. Confirm the framework log reports policyCore=brainfuck-aot abi=1.0 followed by enforcement=ACTIVE_ADAPTIVE.

Assets

1