Unique lines preserving order
Paste alpha, beta, alpha, gamma, beta, one per line, and get alpha, beta, gamma: each line stays where it first appeared and the 2 later copies are dropped. Nothing is sorted. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- Text
- alpha beta alpha gamma beta
- Text
- alpha beta gamma
- Lines
- alpha, beta, gamma
- Quantity
- 3
- Original
- alpha beta alpha gamma beta
Lists collected by hand almost always contain repeats: a SKU pasted twice, a log line that fired again, a ranked name copied into a later section. The usual spreadsheet move is to sort the column and delete adjacent twins, which silently destroys the order that made the list useful. Unique lines preserving order is the opposite contract. This tool reads your multiline text from top to bottom, keeps the first occurrence of each line in the position where it first appeared, and drops later exact copies so a ranked playlist, a changelog, or a first-seen inventory stays in the sequence you already chose. You can optionally compare lines after trimming surrounding whitespace while still keeping the first original spelling.
How unique lines keep first-seen order instead of sorting
Preserving order while making a list unique is not the same as sorting and then deleting neighbors. A sort-then-unique pipeline rewrites rank: the first SKU you listed, the first error you saw, and the first name in a playlist all jump to alphabetical position. People who search for unique lines preserving order want the opposite contract.
This engine walks the file from the first line to the last, remembers each line the first time it appears, drops later exact copies, and leaves the survivors in that first-seen sequence. It splits on CR, LF, and CRLF so Windows, Unix, and old Mac dumps compare fairly. Each line is a candidate as written, including a first blank line if one exists.
Membership uses an exact string key by default, so trailing spaces and letter case matter. When trim is true, the membership key is the line with leading and trailing whitespace removed, but the kept text is still the first original spelling you typed. There is no locale collation, no fuzzy edit distance, and no last-occurrence rewrite.
Use this page when order is part of the meaning. Use a sort-lines tool when you want A-to-Z instead. Use a case-folding dedupe when Apple and apple should collapse.
This landing is for first-seen unique lists that must not be reordered.
Where a first-seen unique list earns its keep
Developers cleaning log snippets and shell history dumps need a first-occurrence unique list before pasting into tickets, without jumping the earliest error to the alphabetical middle of the file. Product and merchandising teams export a ranked SKU column as plain text and want unique values that keep the merchandising order they already fought for. Playlist editors, changelog authors, and curriculum writers collapse accidental copy-paste repeats while leaving the intended sequence intact.
Sysadmins merge allowlists from several sources and need a deterministic unique set that does not reorder earlier trusted entries. Teachers build vocabulary or answer-key line sets without accidental repeats from the clipboard. If you later need alphabetical sort, case-insensitive collapse, row-level CSV deduplication with column keys, or fuzzy semantic merge of near-duplicate rows, treat those as separate search intents and landings rather than overloading this unique-lines-preserving-order page.
What you can do with it
Keep ranked lists unique without resorting them
Paste a merchandising rank, playlist, or priority queue and drop later copies while the first-seen order stays exactly where you put it.
Clean logs and history in first-seen order
Collapse repeated log lines or shell history so tickets stay short, and keep the earliest occurrence at the top instead of sorting alphabetically.
FAQ
How do unique lines preserving order work?
The engine splits your text on CR, LF, or CRLF, keeps the first occurrence of each line, and drops later copies. Survivors stay in first-seen order. Nothing is sorted.
How is this different from sorting unique lines?
Sort-then-unique rewrites rank into A-to-Z order. This tool never sorts: the first time a line appears is the position it keeps. Use a sort-lines tool if you want alphabetical output.
What does the trim option change?
When trim is true, lines that differ only by leading or trailing spaces count as the same line. The kept text is still the first original spelling you typed, including those spaces.
What happens if text is missing or empty?
A blank box is treated as a client error rather than a successful empty unique list.
Are Apple and apple treated as the same line?
No. Lines are compared exactly, so a change in letter case makes them different lines and both are kept. If capitals should not matter, make the case uniform before you paste the list.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/str/unique-lines-ordered \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"alpha\nbeta\nalpha\ngamma\nbeta"}'const res = await fetch("https://api.kit.forhosting.com/str/unique-lines-ordered", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"text": "alpha\nbeta\nalpha\ngamma\nbeta"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/str/unique-lines-ordered",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"text": "alpha\nbeta\nalpha\ngamma\nbeta"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/str/unique-lines-ordered", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"text":"alpha\\nbeta\\nalpha\\ngamma\\nbeta"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"text":"alpha\nbeta\nalpha\ngamma\nbeta"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/str/unique-lines-ordered", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"text": "alpha\nbeta\nalpha\ngamma\nbeta"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "str.unique_lines_ordered",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_chars | 65536 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
Technical notes
Empty input is rejected with invalid_input instead of a silent empty string, which protects clients that forgot to bind a field. Successful API calls cost $0.002 per item.
How unique lines keep first-seen order instead of sorting
The scan is a single linear pass with a hash set, so identical text and options always produce identical JSON you can pin in tests.
What you send, what you get back, and how errors work
Send a required field named text containing the non-empty multiline string you want uniquified. When trim is omitted it is false: comparison is exact, including surrounding spaces. Aliases such as input, value, s, content, body, and lines are accepted when older clients or form generators use those names. Missing text, a non-string value, or a zero-length string all raise invalid_input with a message that names the field, and those failures are not charged. Optionally send trim as a boolean or as common string forms true, false, yes, no, one, or zero. On success the response returns text as the unique lines joined with newline, lines as the same unique list in an array, count as how many unique lines remain, original as the input echo, original_count as how many lines were split before uniquify, removed as how many later copies were dropped, first_positions as the one-based source indexes of each kept line, the resolved trim flag, and method set to unique_lines_ordered. Classic checks include a ranked fruit list with repeated banana and apple collapsing to three unique lines whose first_positions point at the original rows, a whitespace-padded list that collapses further when trim is true, and a single unique line that passes through unchanged with removed equal to zero. Extremely long payloads above the documented character cap fail with invalid_input so a single request cannot monopolize memory. Pricing for successful API calls is the published base of $0.002 per item.
Where a first-seen unique list earns its keep
Because this capability is a pure parse engine, API pricing is the published base of $0.002 per successful item and failed validation does not consume balance. Keep payloads to ordinary document sizes suitable for a Worker isolate rather than multi-megabyte warehouse dumps.
Integration use cases
Build stable fixtures from messy exports
Turn a plain-text dump of URLs, SKUs, or emails into a unique line list for CI, with first_positions mapping each survivor back to its source row.
Technical questions
What does unique lines preserving order cost?
The browser tool is free. API requests use prepaid KIT balance at $0.002 per successful call; invalid input is not billed.