Saturday, November 7, 2026
HomeAndroid App Publishing & Google Play PolicyHow to Index APKs for Custom App Distribution Websites

How to Index APKs for Custom App Distribution Websites

How to Index APKs for Custom App Distribution Websites

A custom distribution site lives or dies by how fast a visitor can find the exact release they need. A visitor arrives with a package name, a version number, or a device that only runs older releases. If the site cannot answer that query in seconds, the visitor leaves, and the file sitting on your server never gets used. That is the whole job of an index: turn a pile of package files into a catalog that can be searched, filtered, sorted, and trusted.

I have seen small catalogs grow from fifty apps to five thousand in a single year, and the ones that survived were the ones that treated indexing as a system from day one. The ones that failed kept adding files to folders and patching search with guesses. This guide walks through the system that works: the fields every record needs, how to pull those fields from the package itself, how to design search and version history, and how to keep the catalog honest with checks before and after publication.

This is written for a site owner, a small team, or a developer building a distribution site for their own apps and for apps they have a lawful right to distribute. You do not need an enterprise budget. You need a clear schema, a parser worker, a publication checklist, and the discipline to block a record that is incomplete.

What an APK index actually does for a custom distribution site

An index is a structured catalog of app packages that a website can search and browse. Each entry describes one app version with facts taken from the package itself. A good index separates the app identity from the file that a visitor downloads, so the website can show consistent facts even when files move between storage locations, mirrors change, or a download domain is replaced.

That separation is the difference between a catalog and a file dump. A folder can hold files, but it cannot answer a query by package name, by version code, or by category. An index stores searchable fields in a database and links each record to its file location. Search, filtering, and sorting all depend on those fields being complete and consistent across every record, not just the popular ones.

An index is a database, not a folder of files. Think of the index as three layers. The first layer is the app: the package name, the display label, the category, and the description. The second layer is the release: the version code, the version name, the release date, the minimum Android version, and the status of that release. The third layer is the file: the size, the checksum, the signature identity, and the storage path. When those layers are separate in your database, you can change a storage path without touching the app page, and you can add a new release without rewriting the app description.

A custom distribution website needs an index that grows without breaking older entries. New versions should add new records rather than overwriting the only record for an app. Old versions should stay reachable when the website promises version history. A stable index design makes both behaviours natural instead of fragile, because each release is a row, and the app page is a view that groups those rows together.

The practical test is simple. Pick an app with ten versions and ask your site three questions: show me the latest version, show me the version from March last year, and show me every version that runs on Android 9. If any of those questions needs a manual lookup or a guess, the index is not finished yet.

Core fields every APK index entry needs

Every record in the catalog needs the same core fields, filled the same way, for every app. The moment one record skips a field, every filter and sort that depends on that field starts lying to visitors. Fill a field for every record, or do not offer it as a filter. That rule sounds strict, and it is the rule that keeps a catalog trustworthy at scale.

Identity fields: package name, version code and version name

The package name is the primary identity for an app record. It looks like com.example.app, and it is unique to one app. The version code is an integer that orders versions for the same package: a higher code means a newer build, even when the version name looks confusing. The version name is the human readable label that users see, such as 4.2.1. The app label is the display name from the package, such as the name shown under the icon on a phone.

These four fields together identify one release in a way that a file name alone cannot. A file name can be renamed, mistyped, or copied from another release. A package name plus a version code cannot be faked by a rename, because the parser reads both from inside the package. Store the version code as an integer in the database, never as text, so sorting by version code is numeric and correct: version code 42 must sort above version code 9, which text sorting would get wrong.

One more identity habit pays off early. Keep a stable internal app id that never changes, and link every release record to it. The package name is the public identity, but the internal id is what your code joins on. If a package name ever changes hands or a listing is corrected, the internal id keeps the history together.

File facts: size, checksum and signature identity

The file size and the checksum identify the exact file behind the record. The checksum lets the website detect a changed or corrupted file before users download it, and it lets a visitor verify the file after the download finishes. Record the size in bytes, and record at least one strong checksum, such as SHA-256, computed during the same pass that parses the package.

The signature identity helps the website detect a package that claims an old identity with a new signer. Two files can share a package name and a version code, but if the signer differs, they are not the same release from the same author. Record the signer certificate fingerprint, not just a yes-or-no flag that a signature exists. When a new release arrives with a different signer fingerprint, that is a fact worth flagging for review, not a detail to overwrite quietly.

The minimum Android version tells users whether the release can run on their device. It comes from the manifest, and it should be stored as the API level as well as the friendly version number, so a filter for Android 10 and above can use the number without guessing. A record without a minimum version will show up in searches for devices it cannot run on, and that is how a catalog earns complaints.

Which checksum algorithm to record

SHA-256 is the sensible default for a distribution catalog. It is widely supported, tools on every platform can compute it, and visitors can check it with a single command. MD5 is faster to compute but too weak to trust as the only check, and SHA-1 sits in the same awkward middle ground. If your parser already computes one hash, computing SHA-256 at the same time costs almost nothing, because the file is already being read. Record the algorithm name next to the value, so a future migration to a stronger hash does not leave old records ambiguous.

Discovery fields: category, description and icon

Additional fields improve search without changing identity. A category field groups apps for browsing, and it should come from a fixed list your team controls, not free text typed per record. A description field supports text search when it comes from a lawful source, and it should be stored separately from the short summary shown on listing pages. An icon reference supports visual browsing, and it should point to a file your site controls, with a fallback icon for records where the package icon cannot be read cleanly.

A release date field supports sorting by recency, and it needs a clear definition: is it the date the file was built, the date it arrived on your server, or the date it was published on the site? Pick one, write it down, and use it everywhere. Mixing definitions across records makes a “newest first” sort meaningless, because one record’s date means built and another’s means published. In practice, publish date is the most useful for visitors, and build date is the most useful for audits, so record both when you can.

Each extra field should have a clear source and a clear update rule. If the category comes from the original listing, say so. If the description is rewritten by your team, say so. A field with no owner and no update rule will drift, and drift is what turns a clean catalog into a pile of guesses.

Extracting metadata from the package file itself

Metadata extraction should read facts from the APK file itself whenever possible. A package parser can read the manifest and report package name, version, and permissions. It can read the app label and the icon reference. It can compute file size and checksum during the same pass. Facts from the package are more trustworthy than facts typed by hand from a listing page, because the package cannot be edited by a typo.

That trust is why the parser is the heart of the whole system. Every other part of the site, from search to safety checks, depends on the parser telling the truth. Spend your effort here first, and the rest of the catalog gets easier.

What a package parser reads from the manifest

The Android manifest inside the package is a binary XML file, and a parser such as aapt2, apkanalyzer, or a library in your server language can read it without installing the app. From the manifest you get the package name, version code, version name, minimum and target SDK levels, the app label reference, the icon reference, and the full permission list. From the package structure you get the file size, the checksum, and the list of native libraries and their architectures, which tells you whether a build is ARM-only, ARM64, x86, or universal.

For split packages, the parser needs one extra step. A base APK record differs from a split set record, and the format explanation in understanding APK and XAPK and APKS formats shows why that distinction matters for install requirements. A single APK installs on its own. A split set needs its base APK plus its configuration splits, and an index that ignores format will mislabel split sets and mislead users about what they need to install. Record the format, the number of parts, and the total size of the set, not just the size of the first file.

Permissions deserve their own table, not a single text field. Store each permission as its own row linked to the release, so a safety review can query for a specific permission across the whole catalog, and so a future feature such as a permission filter can be built without re-parsing every file.

Why parsing belongs in a worker queue, not a web request

Extraction should run in a controlled worker and not inside the web request that uploads a file. A worker can retry a failed parse and can log the exact failure. A web request that parses a large file inline can time out and can leave a partial record. A queue based worker keeps the website responsive while large packages are processed, and it gives you a clear place to watch progress: how many files are waiting, how many failed, and how long each parse took.

The flow I recommend is short. The upload handler stores the file, creates a pending record with a status of queued, and returns at once. The worker picks the job up, runs the parser, fills the fields, computes the checksum, and moves the record to a review or published state. If the parser fails, the record stays pending with an error message attached, and nobody sees a half-filled listing. Failed extraction should block publication for that record. A record without a package name or without a version code cannot be searched reliably. A record without a checksum cannot be verified by users. Blocking a bad record is better than publishing a record that misleads users about the file behind it.

What to log when parsing fails

Log the file name, the file size, the parser exit code or exception, and the first lines of the parser output. Log the job id and the time, so a retry can be traced to the same file. Do not log the full binary content or any private key material, and do not copy the entire manifest dump into a public log. A failure log that a human can act on in thirty seconds is worth more than a dump that nobody reads. Keep failures visible in a small dashboard, even if the dashboard is only a page your team checks each morning.

Designing search, filters and sorting users can trust

Search should match package name, app label, and description text in a predictable order. Exact package name matches should rank highly because the package name is unique. App label matches should follow because labels can repeat across different apps. Description matches should rank lower because description text is broader and noisier. When a visitor types a package name and gets the exact app first, they learn to trust the search box. When they get ten unrelated results first, they stop using it.

That ranking order should be written down and tested with real queries, not left as a default. Take twenty package names, twenty app labels, and twenty description phrases, run them through search, and check the first result each time. If the right app is not first for a package name query, the ranking is wrong, no matter what the search library claims by default.

Ranking rules that match how people actually search

People search in three ways, and each way needs its own handling. Some paste a package name from a phone or a forum post. Some type an app label from memory, often with a spelling that is close but not exact. Some describe what they want, such as a photo editor or a file manager, and browse the results. A good search design serves all three: exact package match first, then label match with tolerant spelling, then description match with category as a tie-breaker.

Full-text search on the description needs a language-aware setup, or it will match every common word and bury the useful results. Use a proper full-text index in your database, such as MySQL full-text or PostgreSQL tsvector, rather than a LIKE query across a text column. A LIKE query scans the whole table on every search, slows down as the catalog grows, and cannot rank results sensibly. A full-text index is one of those choices that feels like extra work on day one and saves the site on day three hundred.

Search results should also show why a result matched, at least in a small way. Showing the version name, the category, and the release date next to each result lets a visitor judge in one glance whether the result is the app they meant. A result list that only shows names forces the visitor to open each one, and most visitors will not.

Filters that only use fields filled for every record. Filters should use indexed fields rather than free text alone. A category filter, a minimum version filter, and a device compatibility filter each narrow results in a useful way. A filter that depends on a field that extraction does not fill will return empty or misleading results, and visitors will assume the catalog has nothing in that category when the real problem is an empty field.

Before you add a filter to the search page, run a query that counts how many records have that field filled. If the answer is not every published record, either fill the missing values first or do not ship the filter yet. The same rule applies to sorting. Sorting should be explicit on the results page. Sorting by version code is different from sorting by upload date. A website that sorts by upload date while claiming latest version can mislead users when an old version is uploaded late. Label the sort order in plain words, such as “Newest version first” or “Most recently published”, so that users can judge the results for themselves.

Two filters earn their place on almost every distribution site. The first is a compatibility filter: show only releases that run on my Android version. The second is a version filter: show only the latest release per app, or show all versions. Both are simple once the index fields are right, and both are painful to add later if the fields were never filled.

Handling versions, split packages and withdrawn releases

Each version needs its own record linked to the same app page. The app page should show the latest stable version first and should list older versions in descending version code order. A user who needs an older version for an older device can find it without guessing which file is which. A user who wants the latest version should not need to compare version codes by hand, because the page has already done that work.

Version codes order releases even when version names look confusing. A version name can jump from one label to another without a clear pattern, and a developer can reuse a friendly name across different builds. The version code is the reliable order key inside the package. The index should store both values and should sort by version code while displaying the version name to users, so the page reads naturally and sorts correctly at the same time.

One app page, many version records

The app page is a view, not a record. It shows the latest release as the main download, then a version history table with the version name, version code, release date, file size, and minimum Android version for each older release. Each row links to its own version record, with its own checksum and its own download link. That structure also fixes a common SEO problem: one page per app, with version history on the same page, ranks far better than a separate page per version that competes with itself.

When a new version arrives, the worker creates a new release record and the app page updates automatically. Nobody edits the app page by hand to change the latest version. If a human has to edit a page to publish a release, the pipeline is incomplete, and the day the human is away is the day the catalog goes stale.

Base APKs, split sets and device-specific builds

Split packages need care in the index, because a visitor who downloads only the base APK of a split set may get an app that installs but looks broken, with missing graphics or missing language files. Record each split set as one release with several file parts, and show the total size and the number of parts on the app page. The download for a split set should deliver the whole set, or clearly explain what the visitor needs, rather than silently handing over one part.

Device-specific builds are a related case. One version name can have several builds for different architectures, and each build has its own file size, checksum, and architecture label. Store the architecture label on every file record so a visitor on an ARM64 phone is never offered an x86 build by accident. Extraction is the first step in that sequence for many teams, and the extraction method in extracting APK files from installed apps shows how a package copy is obtained before parsing begins. A clean extraction step makes the later parsing step more reliable, and it gives the parser a file it can trust.

How to label a split set so installs do not fail

Label the release with the words split set or bundle in plain language on the app page, list the parts, and state the total size. If your site offers an installer or a single combined file, say which one the download provides. If the visitor must install the parts together, say so before the download starts, not after it fails. A short line such as “This release is a split set: base plus 3 configuration files, 48 MB total” prevents more support messages than any help page.

File storage, stable links and checksum checks

Store files in a location that is separate from the web page path. A storage path based on package name and version code is stable and predictable, such as a folder per package with a subfolder per version code. A storage path based on upload order alone is fragile when files move, because the path tells you nothing about what the file is. Stable paths make checksum checks and mirror checks easier over time, and they make a manual inspection of the storage folder readable.

Download links should point to the indexed file for that record and not to a latest file that changes silently. A user who shares a link to a specific version expects that version to download. A latest link that changes behind the scenes breaks that expectation, and it breaks every forum post, bookmark, and message that linked to the old version. Offer both a version link and a latest link with clear labels when both are needed, and never let the version link point anywhere except the file recorded for that version.

A storage layout based on package name and version code

A layout such as files/com.example.app/1042/app.apk is easy to reason about, easy to back up, and easy to mirror. The package name gives you the app, the version code gives you the release, and the file name stays simple. Keep the original file name in the database as well, because a visitor who checks a download may want to see it, but do not let the original file name decide the storage path.

Separate the storage from the web root when you can, and serve downloads through a script that checks the record, logs the download, and then streams the file. That script is also the right place to enforce a withdrawn status, to count downloads per version, and to redirect to a mirror when the main storage is busy. Direct links to files in a public folder skip all of those controls, and they are hard to take back once they spread.

Version links versus latest links. Check stored files on a schedule and not only at upload time. Storage corruption and accidental replacement can change a file after publication. A periodic checksum check compares the stored file against the indexed checksum. A mismatch should suspend the download link until the file is restored or the record is corrected, because serving a file that does not match its checksum is worse than serving no file at all. A daily or weekly check over the whole catalog, plus a check of recently published files, is enough for most sites, and the check should write its results to a log your team actually reads.

When a file is replaced for a legitimate reason, such as a re-sign or a corrected build, do not overwrite the old file in place. Create a new release record, or at least a new file record with a new checksum, and keep the old checksum in history. Overwriting a file while keeping the old checksum is how a catalog starts serving files that fail verification for every visitor who kept the old hash.

Safety and provenance checks before publication

A publication check should verify that the package name, version, and checksum match the indexed record. It should verify that the file opens as a valid package and that the manifest parses without error. It should verify that the signature is present and that the file size matches the record. Any mismatch should block publication until a human or a worker resolves it, because a mismatch at this stage is a fact, not an opinion, and facts should not be published with a warning label and a hope.

Think of the publication check as a gate with a list, not a feeling. The gate runs the same checks on every record, in the same order, and it writes a pass or fail result for each check. When a record fails, the team sees exactly which check failed. When a record passes, the team can prove it passed. That record of checks is also the evidence you need when a visitor asks why a file was removed or why a version is delayed.

The publication checklist every record must pass

The checklist starts with identity: package name present, version code present and higher than the previous release for the same package unless the release is a deliberate re-publish, version name present. Then file facts: size matches the stored file, checksum matches the computed value, manifest parses, signature present and fingerprint recorded. Then discovery: category set, description present, icon present or a fallback assigned. Then policy: provenance recorded, takedown status clear, and any channel rule satisfied.

A safety check should also flag packages with risky permission combinations for manual review. A flag is not a verdict. It is a prompt for a closer look at the manifest and the app purpose. A review queue keeps risky packages out of public search until the review finishes. A public catalog without review becomes a fast path for misleading listings, and a review queue that never empties is a sign that the flag rules are too broad and need tuning.

Permission flags and manual review queues. Flag combinations, not single permissions in isolation. A flashlight app that asks for location, contacts, and SMS is a different story from a messaging app that asks for contacts. Build your flag rules around combinations and around the app category, and review the flags each week to see which ones caught real problems and which ones only caught harmless apps. A rule that flags a hundred apps a day is not a safety rule, it is noise, and noise trains a team to ignore the queue.

Provenance checks matter as much as technical checks. Record where each package came from and when it arrived. A package with unclear provenance should not gain a trusted label in the catalog. Clear provenance lets the team answer a takedown request or a user complaint with facts rather than guesses, and it lets you remove every file from one source in one operation when that source turns out to be unreliable.

Keeping the index fresh without breaking old links

Freshness comes from a regular intake pipeline and not from manual edits to live records. An intake worker should parse a new package, create a new record, and run publication checks. A human edit to a live record should be logged with a reason, because unlogged edits make later audits impossible when a record changes without explanation. If a version name changes on a live page and nobody knows who changed it or why, the catalog has a process problem, not a data problem.

Alternative distribution channels add another layer to index design. A catalog that lists apps from several channels must record the channel for each record. That channel field prevents a store release and an alternative release from merging into one confusing record. Channel aware records keep search results honest when the same app appears in more than one place, and the publishing discussion in publishing apps on alternative Android application stores covers channel differences that an index should reflect. A catalog that ignores channel rules will eventually publish a listing that a channel does not allow.

Duplicate detection before a new record goes live

Duplicate detection should run before a new record is published. A duplicate check on package name and version code catches a re-upload of the same release. A duplicate check on checksum catches the same file under a different name. Both checks prevent a catalog from filling with identical listings that confuse search results and split download counts across two pages for the same file.

When a duplicate is found, do not delete the incoming file silently. Log the duplicate, link it to the existing record, and tell the uploader which record already exists. That log is useful twice: it stops a second copy from entering the catalog, and it shows you which apps people keep trying to upload again, which is often a sign that the existing listing is hard to find.

Scheduled re-checks and audit logs. Catalog facts also depend on correct format handling, and a weekly audit should check more than checksums. It should count records with missing fields, records with no icon, records whose latest version is older than the newest version code in the database, and app pages whose latest release does not match the newest record. A weekly audit report with those four numbers tells you the health of the catalog in one glance, and it turns maintenance from a vague worry into a list of jobs.

Keep an audit log of every change to a published record: who changed it, when, what the old value was, and what the new value is. That log is the difference between a catalog you can defend and a catalog you can only hope is right. It costs almost nothing to write, and it is the first thing you will wish you had when a listing is disputed.

Frequently asked questions about APK indexing

What is the difference between an APK index and a folder of APK files?

A folder holds files. An index holds searchable facts about those files, such as package name, version code, checksum, category, and release date, and links each fact to its file. A folder cannot answer a search by package name or sort by version code. An index can, because its fields are stored in a database with indexes on the fields people search and filter by.

How do I handle a new version without breaking links to the old one?

Create a new release record for the new version and keep the old record untouched. The app page shows the new version first and lists the old version in history, and the old version link still points to the old file. Never overwrite the old file or the old record, and never point a version link at a different file after it has been shared.

Can I index split packages in the same catalog as single APKs?

Yes, if you record the format on every release. A single APK is one file that installs alone. A split set is one release with several parts, and the record should show the format, the part count, and the total size. Label split sets clearly on the app page, so visitors know they need the whole set and do not download one part and wonder why the install fails.

Why should a failed parse block publication instead of publishing with missing fields?

Because a record with missing identity fields cannot be searched, filtered, or verified, and a visitor who finds it will trust a listing that the site itself could not verify. Blocking the record keeps the catalog honest, and the failure log tells your team exactly what to fix. A catalog with a hundred complete records earns more trust than a catalog with a thousand records, of which a hundred are broken.

Start with the schema, then the parser worker, then the publication check, then search on the fields the parser fills for every record. Add version history after the basic record works. Add filters after search works on the core fields. Add scheduled checksum checks after publication checks work. Each step builds on a working previous step, and a catalog built in this order stays trustworthy while it grows. Build the index once, build it correctly, and let every new release flow through the same pipeline — that is the habit that turns a small distribution site into a catalog people return to.

RELATED ARTICLES

Most Popular