Trim blank lines top and bottom
Paste a block with two empty lines above and two below, and those four go while the three lines in between stay untouched, gaps and indents included. Lines holding only spaces or tabs count as empty at the edges. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- Text
- Hello world Keep this spacing
- Input
- Hello world Keep this spacing
- Text
- Hello world Keep this spacing
- Line count
- 3
- Lines
- Hello world , , Keep this spacing
Pasted snippets and exported documents often arrive with a halo of empty lines at the top and bottom that no reader asked for. This tool removes those edge blank lines and nothing else: interior paragraphs, the extra space you left between sections, and the padding on a content line all stay exactly as they were. Send any string—an email draft, a Markdown file, a log dump, or a homework essay—and the engine normalizes Windows and old Mac line endings, walks inward from both ends until it finds a line that still has visible text, and returns the remaining block. Whitespace-only lines count as blank, so a row of spaces or a lone tab at the start of a paste is dropped just like a truly empty line.
What edge-blank-line trim means and why it is not String.trim
Most editors expose a whole-string trim that only peels whitespace from the first and last characters of a buffer. That is the wrong tool when the mess you care about is empty lines stacked above a title or after a signature, while the body still needs its paragraph gaps and its indented quotes.
This capability walks the line list, not the character list: after Windows CRLF and legacy CR endings are normalized to LF, the text is split on LF, then the walk starts at index zero and advances while each line is empty or contains only Unicode whitespace, and a matching walk starts at the last index and retreats.
The first and last lines that still hold visible characters become the new edges; everything between them is copied unchanged, including blank separators you meant to keep and spaces that live inside a sentence. A line that is only spaces or tabs is treated as blank at the edges, because that is how pasted chat logs and exported Word documents usually look.
A line that still has a letter, digit, or punctuation mark is content even if it also has leading indent, so code samples and nested lists keep their first-column padding. The algorithm never collapses two interior blank lines into one, never joins paragraphs, and never trims the left or right of a content line. That contract is what people mean when they search for trim blank lines top and bottom rather than trim every line or collapse all whitespace.
Practical tips, limits, and related searches
Use this capability when the search intent is trim blank lines from the top and bottom, strip leading and trailing empty lines, or tidy a pasted snippet without touching inner spacing. Keep sibling tools in mind. Per-line whitespace trim is a different job: that landing strips spaces from every line and does not drop empty rows.
Collapse-all-whitespace and remove-all-spaces are also different jobs: they destroy the inner rhythm of a document. Removing every line break joins the file into one line. Counting paragraphs or lines reports structure without changing it.
Prefer this landing when humans already formatted a document and you only need the empty halo gone before you store, email, or print the file. When teaching students or onboarding reviewers, show the same sample before and after so they see that only the edge blank lines change and that word-internal spacing plus interior empty rows are left alone.
What you can do with it
Tidy pasted emails and chat exports
Strip the empty halo above a greeting and below a signature while keeping the blank line that separates paragraphs.
Normalize homework and Markdown dumps
Teachers and static-site pipelines drop leading and trailing empty rows from submitted files before they store or render the body.
FAQ
Does this delete blank lines in the middle of the text?
No. Only blank lines at the very beginning and the very end are removed. Interior empty rows stay where they are.
Are spaces between words or at the start of a content line removed?
No. Content lines are copied unchanged. Leading indent, trailing spaces on a non-blank line, and double spaces inside a sentence are preserved.
What counts as a blank line?
A line with no characters, or a line that contains only whitespace such as spaces or tabs. A line with any visible character is content.
What happens if I omit the text field?
An empty string is accepted and returns an empty cleaned document.
Is trimming blank lines at the edges free?
The browser tool is free and runs locally.
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/trim-blank-edges \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"\n\n Hello world \n\nKeep this spacing\n\n"}'const res = await fetch("https://api.kit.forhosting.com/str/trim-blank-edges", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"text": "\n\n Hello world \n\nKeep this spacing\n\n"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/str/trim-blank-edges",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"text": "\n\n Hello world \n\nKeep this spacing\n\n"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/str/trim-blank-edges", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"text":"\\n\\n Hello world \\n\\nKeep this spacing\\n\\n"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"text":"\n\n Hello world \n\nKeep this spacing\n\n"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/str/trim-blank-edges", 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": "\n\n Hello world \n\nKeep this spacing\n\n"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "str.trim_blank_edges",
"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 | 100000 |
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
How to call the API and what the response contains
An empty string is valid: there are no content lines, so the result is an empty text field, zero remaining lines, and a message that reports how many edge blanks were removed from that empty buffer. Prefer sending the raw multi-line body rather than pre-trimming it yourself when you want the counts and the changed flag to reflect the original paste. Send a JSON body with the required string field text. Aliases input, value, string, s, content, and body are accepted so older clients do not have to rename a payload. The value must be a string and must not exceed one hundred thousand characters, a hard cap that keeps a single paste from exhausting Worker or browser memory. Missing text, a null value, a non-string type, or an oversize payload returns invalid_input and is never charged. On success the response echoes input as the original string you sent, text as the edge-trimmed result joined with LF, line_count as the number of remaining lines including interior blanks, lines as that ordered array, leading_blank_lines and trailing_blank_lines as the counts peeled from each end, blank_lines_removed as their sum, chars_before and chars_after as the lengths of the original and cleaned strings, changed as true when at least one edge blank line was dropped, and message as a short English summary. Pricing is a flat $0.002 per successful request with unit item. When integrating, assert on text or lines for the cleaned document, compare leading_blank_lines and trailing_blank_lines for regression checks, and treat invalid_input as a client bug for a missing field rather than a billing event.
Practical tips, limits, and related searches
Round-trip checks for tests are easy: feed two empty lines, a title, an interior blank, a body line, and two more empty lines, and expect the title and body with the interior blank still between them; feed a whitespace-only first line and a tab-only last line around a paragraph and confirm those edges vanish while indent on the paragraph remains; deliberately omit text to confirm invalid_input names the field. HTML and Markdown are treated as raw text: tags are not stripped, and a markup-only line still counts as content if it has visible characters. Failed tasks that return invalid_input do not consume prepaid balance, which keeps form validation and CI negative tests cheap.
Integration use cases
Prepare log and snippet fixtures
QA scripts paste console output with extra blank rows at both ends and need a stable fixture that still preserves interior spacing.