A typed or filtered search goes straight from the app to the phone's index, in a single request; browsing is answered on the phone itself, from its own copy of the index. What you type can match the words a photo was read into, your own tags, its place and camera, and on plans with vector search, what your words mean.
Figure 1 — a typed search, an optional query vector, one POST to the index, results that open full screen in the app, one swipe from the next. Browsing with nothing typed never gets this far: it is served from the phone.
The top of the photos screen is one row of small bordered buttons, each an icon with a label. Left to right:
The app logo. Opens opensolr.com/admin/solr_manager in your default browser.
The Sync screen, see schedule & Force Re-Sync.
Your library in numbers, counted on the phone.
The map of the current search.
Opens the search line.
Each thumbnail can carry up to three small marks in its top-right corner, left to right: a pin when the photo has a location, a person when someone is named on it, and a tag when you tagged it. They step aside while you are picking photos.
There is no button for selecting: a long press on a photo starts it, and it ends by itself when the last tick is cleared. Keep the finger down and drag to tick every photo you pass; start the drag on a photo already ticked and it unticks them instead.
The search box is not on screen until you ask for it: the magnifier opens one compact search line with the filters button on it, and tapping the magnifier again closes it and clears the query. While a query is in force the line stays on screen on its own, so coming back from Stats, duplicates or similar photos never leaves you with results and no visible words behind them.
With nothing typed, the grid lists every photo in the index, newest first, by the date taken, under date headings. This is what you see when the app opens.
None of it is asked of the index. The phone keeps its own copy of every document its index holds — every field except the search vector and the keys used to find duplicates. The id, the path, the file name, the size, the dates, the camera and its EXIF, the place, the words the photo was read into, the printed text, the people, your own tags and the file's md5 are all there on the phone. The copy is read from the index once, at install or reinstall, and every write from then on keeps it in step.
So the years, the months, the days, their counts and the photos inside them all come from the phone: browsing makes zero requests, works with the phone offline, and costs nothing of your plan. Syncing compares your folders with the same copy, so a sync with nothing to do makes no request either.
What you type is trimmed to 300 characters and sent only as the bound parameter uq. The query that uses it is fixed in the app. On a plan with vector search, the app sends the query and your words to Opensolr's photos_select endpoint: there your words become a query vector, the vector goes into vectorQuery, and both legs run in Opensolr's own {!hybrid} parser, the one search.opensolr.com runs. Only the photos found come back to the phone, never the vector:
Words only, the same lexicalRaw weighs your tags most, then the names of the people, the text printed in the photo and what it shows: custom_tags_text^5 meaning^2 labels_t^2 ocr_t^3 persons_t^4 text file_name_text folder_text camera_text place_text^1.
An AI switch sits next to the count above the grid, once something is typed. On, the search blends meaning with words. Off, the meaning leg is left out entirely and the search matches words only — which is what you want for an exact code, a receipt number or a product reference, where meaning only drags the answer away from the thing you asked for. It is the same switch search.opensolr.com carries. On a plan without search by meaning, or once this month's AI requests are used up, the switch is shown off and greyed out.
How much the words weigh against the meaning is yours to set: the Semantic ↔ Lexical Balance slider in the account screen goes from 0 (meaning only) to 1 (words only). It starts at 0.2, which is the alpha=0.8 above.
Each leg is scored on its own and normalised per query, then blended by the balance above. text also collects the file name, the folder, the camera, the place and your tags, so typing screenshots, pixel or Austin finds what you would expect. Words are matched with accent folding and without stemming, so montreal finds Montréal.
When this month's AI requests are used up, when the search is a single letter, or when the vector service does not answer, the same request runs with words only. Nothing is said about it on the photos screen; the reasons are on the account screen.
Without vector search on the plan the difference is larger: nothing is sent to be read at all. Photos are indexed by their date, camera, place, file name and your own words, there are no read words and no printed text to search, and search is lexical.
A photo of a gas receipt, an invoice, a shelf label, a screenshot or a business card carries words that describe it better than anything a model can say about the picture. Those words are read on Opensolr's side and become part of the search: the receipt is found by the station's name, the total or the receipt number; the invoice by the company on it; the shelf label by its product code; the screenshot by what it says.
They carry their own weight in the search, just under what the photo shows, so a receipt is found by the station on it rather than only by looking like a receipt.
There is nothing to turn on and nothing new to type. The printed text lands in the index next to the words the photo was read into, and the search you already use looks through both.
On a plan without vector search, no photo is sent to be read. On the plans that have it, every photo is read, and the reading is kept only when it holds real text: at least four confidently read words of three letters or more. The specks a reader finds on a holiday photo or a wedding are dropped, so those photos carry no printed text in the index, and reading them costs nothing extra.
It also costs nothing extra on the photos where it does find something: a photo is a tenth of an AI request whether the work was the words, the search vector, the printed text, or all three (plan limits). A photo that has been read once is never read again — the reading is kept against the picture itself. The same file is recognised by its md5, so the printed text and the words already read out of a photo survive a later pass that cannot read it, whether because the plan has no vector search or because the month's AI requests are used up.
The reading never happens on your phone: it runs on the CPUs of api.opensolr.com, never on its GPU, at the same time as the image model works on the same photos, so a first sync of a phone full of paperwork does not queue behind the AI models.
People are put on a photo by naming a face. Open a photo, tap the face button under it, and every face found in it is shown with a frame; tap one and give it a name. From then on the app looks for that person in the rest of your photos: the faces that clearly match get the name on their own, and the ones it is less sure about are offered to you under Is this Anna?, ticked in advance when the match is close, for you to confirm or untick. Long press and drag ticks many at once. A face you leave unticked is never offered for that person again.
The faces are found and measured on your phone, by two small models that run inside the app (YuNet to find them, SFace to tell them apart). No picture and no face ever goes to Opensolr's AI servers for this. Each face becomes a short numeric fingerprint; the fingerprints, with the frames and the names, are kept on the phone and written into your own Opensolr Index alongside the photo's other facts, so a reinstall or a new phone gets them back without reading the photos again. Nothing but your phone ever compares them.
New photos have their faces read as they are indexed. Photos that were in your index before faces existed are read once with Find faces in all photos in Sync: it runs only while the phone is charging, in short runs, shows its progress and can be stopped there.
If something else has already recognised the faces on your photos — Google Photos, Lightroom, digiKam, Apple Photos — it wrote the names onto the files themselves, in the XMP property PersonInImage. Those names are read too, and put on the matching faces. The names land in persons_t and persons_ss and are copied into the search field, next to your own tags and what the photo shows (the index schema), so typing a name finds that person's photos.
Search folds accents, so Montreal finds Montréal and a name written with diacritics is found without them, and the other way round.
People go on a photo only by naming faces: the tag sheets take no typed names, and the app never guesses a person from a photo's tags. In Edit a name can only be taken off a photo. A person you already put on a photo is placed on the face that looks most like them, and the app only names a photo on its own when you put nobody on it. A name comes off your whole library from the People filter, with one tap on the cross next to it. The People filter and the People section of Stats list everyone by name, and Similar photos under any photo starts with the photos of the same people.
They are the owner's names for the owner's people: they go to that owner's index and nowhere else (privacy).
From the second character on, after a 200 ms pause in typing, a short list of up to eight distinct entries appears under the search box: your own tags, the words the image model read your photos into, camera makes and models, and the city, region, province and country they were taken in, all matched anywhere in the term (cat offers tabby cat). Tap one and it searches. The list is built from the phone's own copy of the index, so it appears instantly, costs no request and works offline; a photo the last sync wrote is offered right away. The names offered when you tag people come from the same place.
Every typed search is also spellchecked against the same tags, words, camera names and places (the spell field, checked by Solr's DirectSolrSpellChecker). When a correction exists, Did you mean briefcase? appears above the results, one tap away. Only the words your photos actually carry can be suggested: the dictionary is your index, nothing else.
The button above the grid chooses how the photos are laid out, and the choice is kept from one search to the next: Best match, Date, Place, People, My tags, Folder and Camera (browsing with nothing typed offers all but Best match). Folders are shown as the tree they are, three levels deep like the dates — Pictures, then 2019, then 11 — since a library kept as 2019/11 would otherwise read as a flat list of numbers. A folder holding nothing but one other folder is written on one line, and anything deeper than the third level is held by the group at the bottom. A camera is its make and model as one name, Nikon Z6. Photos with neither sit in a last group of their own, No folder or No camera. None of this costs a request of its own: the values are asked for together with the results, or read from the phone's own copy of the index.
The grid's grouping follows the last search that ran, not the text being typed: typing alone never regroups the grid. A search runs on Enter, on a picked suggestion, on clearing the box, and on any filter.
- Browsing (nothing typed): date headings, on three levels — year, then month, then day. Today, Yesterday and the weekdays of the last six days still stand above the years, because each of them is already a day. Everything older sits under its year, 2025; inside the year come its months, September; inside each month come its days, Saturday 5, Friday 4. Every month is spelled out by its days, even a month that holds photos from a single day.
- Headings sit on a faint band of the app's orange, a little stronger for a month than for a day, with text that reads well on the light and the dark theme, so they show as something to tap without competing with the photos. Every heading folds away with a tap and says how many it is hiding (September (1,480)), and it stays folded between runs of the app. The expand all / collapse all button next to the duplicates icon folds or opens every group of what is on screen at once. A long press on a photo starts picking photos, and it is the only way in. Keep your finger down after the long press and drag: every photo between the one you started on and the one under your finger is ticked as you go, dragging back unticks what the drag ticked, and near the top or bottom edge the grid scrolls itself so a run can pass what is on screen. While selecting, a tap on a heading ticks its whole group instead — a year, a month or a day, every photo in it, not only the ones already loaded on screen.
- A bar down the right edge drags the grid: one movement crosses months, with the month and, under it, the day shown in a badge lifted clear of your thumb. It answers back as it goes — a firmer tap for every month you cross, a lighter one for every day — and fades away when you stop. It rides the rows already on screen, so it costs no request.
- After a typed search: Best matches and Also similar, split at the biggest fall in score among the results below 60% of the top score. The split is only made with at least 8 results, and Best matches always holds at least 3. Neither heading carries a group tick: the line between them moves as more results arrive, so there is no fixed group to tick.
- The count line shows only N photos.
- The next page of results is asked for halfway through the last page loaded, so it is usually already there when the grid reaches it.
- Photos that could not be read: a red icon next to the duplicates icon, only when there are some, lists the photos your phone itself cannot open or decode. They never reach Opensolr. Each has a red frame, a red ! and its file name with the reason, and opens and selects like any other photo. They are not tried again until the file changes, or until you pick them for Re-sync.
- Suggestion pills above the grid appear only after a typed search.
- Your text only ever travels in
uq, read by the query throughv=$uq. It is never pasted into the query syntax, so characters such as braces, colons or quotes are just characters. - Every filter value travels the same way, as a bound parameter (see filters).
- Page size is clamped and the start offset cannot be negative.
- The request is a POST: a query vector is too long for a URL, and a request body keeps what you searched for out of server access logs.
- Browsing, the suggestions under the search box and the counts on the headings never leave the phone, so for those there is no request to attack in the first place.
Results come 60 at a time and more load as you scroll. The thumbnails are drawn from the photos on your phone; nothing is downloaded to show the grid. Tapping and holding a photo is described in filters & photo details.
- Reload: swiping the grid down reloads the results and starts a sync as well (a sync that is running is stopped first and started again). The reload icon on the count line reloads only. Both are asked for by hand, so a typed search goes to the index itself: whatever the phone was holding for that search is thrown away first. Coming back to the photos from another screen reloads them as well.
- The buttons above the grid, from the left: the AI switch (once something is typed), Filters, Duplicates (groups of alike photos, see duplicates), the red ! for photos your phone could not read (only when there are some), Reload and Expand / Collapse all. The count beside them is kept short (1.2K).
- A long press on a photo turns selection on; the bar at the bottom then shares, deletes or re-syncs the ticked photos.
The same typed search asked again shortly after is answered from the copy the phone kept, so it costs none of your plan's search bandwidth. How long an answer may be reused, and a button that throws them all away, are in the account screen; your own edits, the photos you delete and every finished sync clear it on the spot. The lists a filter offers you are asked for once and kept until a sync actually writes something.
Select photos, then press Delete. The app always shows its own warning first:
They are removed from this phone and from your index. This cannot be undone.
On Android 11 and newer, the system's own confirmation follows. The deleted photos leave the results at once and are deleted from the index; should that fail, the next sync removes them anyway.