Back to Property Insights
Take back your time and put money in the bank by selling your properties
Industry News Pinned

Take back your time and put money in the bank by selling your properties

Property118 · · 15 min read

Property118 Take back your time and put money in the bank by selling your properties News that self-managing landlords spend an average of 51 hours every month managing their rental properties will strike a chord with many landlords across the country. The figures from Pegasus Insight reveal how much time managing a portfolio can take, from licensing paperwork and complying with regulations to organising repairs and responding to tenants. For landlords juggling these responsibilities alongside the Renters’ Rights Act and the everyday demands of running their properties, it raises a familiar question: is the extra hassle still worth it? For one landlord, it wasn’t worth all the stress and uncertainty of running his property portfolio, and with new regulations on the way, many landlords are simply saying now is the best time to exit the market. Landlord Sales Agency helped Alasdair sell 35 of his properties in less than 28 days and all for the best price. Alasdair ran a portfolio of more than 200 properties and was often on call around the clock to help his tenants. He says: “Running a 200+ property portfolio sounds very exotic but in reality, it is very time-consuming. “We got to the point where I wanted to downsize and do something more manageable.” Alasdair says that by using Landlord Sales Agency he was able to sell his 35 properties quickly without having to worry about the long wait time. He says: “Buying a tenanted property means for the buyer they get rental income from day one and for us it means we get rental income until the day of the sale. “If a tenant doesn’t want to leave the property, it can take a long time to resolve, delaying getting it on the market and advertised. Plus, there’s always uncertainty until the day of completion, which can sometimes take months. “However, Landlord Sales Agency secured a quick sale for us, eliminating the worry and making the process much smoother.” Downsizing does not have to mean leaving the market altogether. You may want to sell one property, release cash from part of your portfolio or sell an entire portfolio in bulk. We typically achieve 85 – 90% of the market value, and for that we cover all the costs and take away all the hassle that comes with selling the portfolio. You won’t get a higher price for the service, and any company promising you 100% market value is hiding a huge list of costs that are going to come after the sale. That’s not the case with us. It’s what makes us different. We’re a company founded by landlords for landlords, made up of the UK’s top portfolio exit specialists who know exactly what it takes to overcome all problems associated with selling buy-to-lets. We solve every single problem, no matter what it is. If managing your properties is taking up more of your life than you want, now could be the time to explore your options. Contact Landlord Sales Agency now for a free, no-obligation chat about selling one property, part of your portfolio or the whole portfolio. Name * Email * Phone * Please give us details of how we can help and the properties in question * Privacy Policy * I have read and accept Property118’s Privacy Policy (function(){var el=document.getElementById("ts-607776b5-3f7b-431c-b5cc-fc304603fee3");if(!el)return;var b=document.body,h=document.documentElement;var dark=b.classList.contains("dark-mode")||b.classList.contains("dark")||b.classList.contains("night-mode")||h.classList.contains("dark");if(!dark){var bg=window.getComputedStyle(b).backgroundColor,m=bg.match(/\d+/g);if(m)dark=(m[0]*0.299+m[1]*0.587+m[2]*0.114) Submit (function(){ var uid = "crm-form-ba09a29b"; var form = document.getElementById(uid + '-form'); var wrap = document.getElementById(uid); var msg = wrap.querySelector('.crm-message'); var totalPages = 1; var curPage = 0; // ── Name casing ────────────────────────────────────────────────────── // Tidy a name box as the visitor leaves it, so they are not staring at // "JOHN SMITH" in their own form. Deliberately cosmetic: the API runs // the same normalisation on submit and is the authority on what gets // stored, so a disagreement here is harmless. Kept in step with // packages/shared/src/names.ts. var NAME_PARTICLES = ' van von de del della der den di da dal du la le el bin ibn al dos das do ter ten '; var NAME_MACS = ' macdonald macintyre macleod macarthur macgregor macneil macpherson macrae mackinnon macqueen '; function nameUp(s){ return s.replace(/[a-z]/i, function(c){ return c.toUpperCase(); }); } function isParticle(w){ return NAME_PARTICLES.indexOf(' ' + w.toLowerCase() + ' ') >= 0; } function nameRecase(atom){ if (!/[a-z]/i.test(atom)) return atom; var dot = atom.charAt(atom.length - 1) === '.' ? '.' : ''; var core = dot ? atom.slice(0, -1) : atom; var lower = core.toLowerCase(); if (/^(?:II|III|IV|VI|VII|VIII|IX|XI|XII)$/.test(core.toUpperCase())) return core.toUpperCase() + dot; if (lower === 'jr' || lower === 'sr') return nameUp(lower) + dot; if (core.length === 1) return core.toUpperCase() + dot; var mc = /^mc([a-z]{2,})$/.exec(lower); if (mc) return 'Mc' + nameUp(mc[1]) + dot; if (NAME_MACS.indexOf(' ' + lower + ' ') >= 0) return 'Mac' + nameUp(lower.slice(3)) + dot; return nameUp(lower) + dot; } function nameWellFormed(atom){ // "Mccoll" has its leading capital but still wants McColl. Safe only // because "Mc" is unambiguous — "Mac" deliberately has no such rule. if (/^Mc[a-z]{2,}$/.test(atom)) return false; return /^(?:[A-Z][a-z]*|[A-Z][a-z]+[A-Z][A-Za-z]*|O[A-Z][a-z]+|[A-Z]{1,3}|[IVX]+)$/.test(atom); } function tidyName(value){ var v = (value || '').replace(/\s+/g, ' ').replace(/^ | $/g, ''); if (!v) return value; if (/[@\d,]|&[a-z]+;|&#|https?:|www\./i.test(v)) return value; // not a name var words = v.split(' '); var deliberate = words.every(function(w){ // A lone particle is not a particle — it is someone's name (Di, Al). if (words.length > 1 && isParticle(w)) return true; return w.split(/[-'\u2019.]/).filter(function(seg){ return /[a-z]/i.test(seg); }) .every(function(seg){ return nameWellFormed(seg); }); }); if (deliberate) return v; return words.map(function(w, i){ if (i > 0 && isParticle(w)) return w.toLowerCase(); return w.split(/([-'\u2019.])/).map(function(seg){ return /^[-'\u2019.]$/.test(seg) ? seg : nameRecase(seg); }).join(''); }).join(' '); } // ── Thousand separators ────────────────────────────────────────────── // Opt-in per field (data-thousands). Mirrors formatThousands() and the // caret helpers in the CRM's packages/shared/src/numbers.ts. The CRM // strips the separators again on submit, so storage is unaffected. function groupThousands(value){ var raw = String(value == null ? '' : value).replace(/,/g, ''); if (!raw) return ''; // More than one point is not a number to group: "5.000.000" became "5.000000". if (raw.indexOf('.') !== raw.lastIndexOf('.')) return raw; var neg = raw.charAt(0) === '-'; var body = neg ? raw.slice(1) : raw; var dot = body.indexOf('.'); var digits = function(x){ return x.replace(/[^0-9]/g, ''); }; var whole = digits(dot === -1 ? body : body.slice(0, dot)); var frac = dot === -1 ? null : digits(body.slice(dot + 1)); if (!whole && frac === null) return neg ? '-' : ''; var grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ','); return (neg ? '-' : '') + grouped + (frac === null ? '' : '.' + frac); } // Reposition by digit count, not character index: commas appear and // vanish to the left of the caret, and counting characters would throw // it to the end of the field on every keystroke. function caretAfterGrouping(formatted, digitsBefore){ if (digitsBefore s.lastIndexOf(',') ? '.' : ','; else if (dots + commas === 1) { var mark = dots ? '.' : ','; var bits = s.split(mark); point = bits[1].length === 3 && bits[0].length 2) return null; var digits = function(t){ return t.replace(/[.,]/g, ''); }; var n = Number((digits(parts[0]) || '0') + '.' + (digits(parts[1] || '') || '0')) * scale; if (!isFinite(n)) return null; n = Math.round(n * 100) / 100; return neg ? -n : n; } function crmWholeOf(n) { return n % 1 !== 0 && Math.abs(n) "1,600,000". Unreadable stays as typed. function crmTidyMoney(value) { var n = crmReadMoney(value); if (n === null) return value; var whole = crmWholeOf(n); return whole % 1 === 0 ? groupThousands(String(whole)) : String(whole); } function crmMoneyReading(value) { var text = String(value == null ? '' : value).trim(); if (!text) return ''; var n = crmReadMoney(text); if (n === null) return 'Enter an amount in pounds, e.g. 1,600,000'; var whole = crmWholeOf(n); var pounds = whole % 1 === 0 ? whole.toLocaleString('en-GB') : whole.toLocaleString('en-GB', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); var millions = Math.abs(whole) >= 1e6 ? ' (£' + Number((whole / 1e6).toFixed(2)).toLocaleString('en-GB') + ' million)' : ''; return 'Reads as £' + pounds + millions; } var crmWarnings = []; // What a money box reads as, and any sense check, shown under it. function crmMoneyNotes() { form.querySelectorAll('input[data-money="1"]').forEach(function(el){ var note = el.parentNode.querySelector('.crm-money-reading'); if (note) note.textContent = crmMoneyReading(el.value); }); crmWarnings.forEach(function(wf){ var el = form.querySelector('[name="' + wf.id + '"]'); var note = el ? el.parentNode.querySelector('.crm-money-warning') : null; if (!note) return; var msg = ''; var n = crmNumberAt(wf.id), b = wf.ofId ? crmNumberAt(wf.ofId) : null, ofW = wf.ofId ? crmWrapper(wf.ofId) : null; if (wf.below !== null && n !== null && isFinite(n) && n 0 && ofW && ofW.style.display !== 'none') { var pct = n / b * 100, what = '“' + wf.ofLabel + '”'; if (wf.max !== null && pct > wf.max) msg = 'Please check: this is ' + (wf.max === 100 ? 'more than ' : 'over ' + wf.max + '% of ') + what + '.'; else if (wf.min !== null && pct parseFloat(cmp); case 'lessthan': return parseFloat(val) [ ][ ]', so the prefix arm above // collects EVERY rendered row. Demanding all of them be filled means one blank // extra row - added by mistake, or rendered by settings.minRows - blocks Next and // Submit with a generic message on the wrapper. Only the first row is required. var firstRow = form.querySelector('[data-field-id="' + fld.id + '"] .crm-repeater-row'); if (firstRow) { reqEls = Array.prototype.filter.call(reqEls, function (el) { return firstRow.contains(el); }); } var toTest = reqEls.length ? reqEls : [els[0]]; return Array.prototype.every.call(toTest, function (el) { return (el.value || '').trim() !== ''; }); } var crmMinimums = []; // Mirrors the CRM's numberMinimumError, numberMaximumError and numberShareError (packages/shared/src/numbers.ts). function crmNumberAt(id) { var el = form.querySelector('[name="' + id + '"]'); if (el && el.getAttribute('data-money') === '1') { var m = crmReadMoney(el.value); return m === null ? null : crmWholeOf(m); } var text = el ? String(el.value || '').replace(/,/g, '').trim() : ''; return text === '' ? null : Number(text); } function crmMinimumMessage(mf) { var mel = form.querySelector('[name="' + mf.id + '"]'); if (mf.money && mel && String(mel.value || '').trim() !== '' && crmReadMoney(mel.value) === null) { return mf.label + ' must be an amount in pounds, e.g. 1,600,000.'; } var n = crmNumberAt(mf.id); if (n === null || !isFinite(n)) return null; if (mf.min !== null && n mf.max) { return mf.label + ' must be at most ' + mf.max.toLocaleString('en-GB') + '.'; } if (mf.pct !== null) { var ofW = crmWrapper(mf.ofId); var b = crmNumberAt(mf.ofId); if (ofW && ofW.style.display !== 'none' && b !== null && isFinite(b) && b > 0) { var least = Math.ceil(b * mf.pct / 100); if (n [ ][ ]". // Group those flat keys into an array of row objects under the repeater // id. Plain fields, checkbox "[]" arrays and single-bracket composites // (name[first], address[city]) don't match the double-bracket pattern, so // ordinary submissions are unchanged. var _repRe = /^(.+?)\[(\d+)\]\[(.+)\]$/; var _repIds = {}; Object.keys( data ).forEach( function ( key ) { var m = key.match( _repRe ); if ( ! m ) { return; } var rid = m[1], row = parseInt( m[2], 10 ), sub = m[3]; _repIds[ rid ] = true; if ( ! Array.isArray( data[ rid ] ) ) { data[ rid ] = []; } if ( ! data[ rid ][ row ] || typeof data[ rid ][ row ] !== 'object' ) { data[ rid ][ row ] = {}; } data[ rid ][ row ][ sub ] = data[ key ]; delete data[ key ]; } ); Object.keys( _repIds ).forEach( function ( rid ) { if ( Array.isArray( data[ rid ] ) ) { data[ rid ] = data[ rid ].filter( function ( r ) { return r && typeof r === 'object'; } ); } } ); // GF auto-substituted {user_agent} / {referer} on hidden fields // at render time. We do the equivalent right before submit so // fields whose default value carries these placeholders // resolve to the browser's actual values rather than being // stored as literal "{user_agent}" / "{referer}" strings. // The last ARTICLE the visitor read (set client-side on post views by // P118_Article_Views). Used to attribute the enquiry to the article // even after navigating away or with a stripped/absent referer. var lastPost = ''; try { var lpm = document.cookie.match(/(?:^|;\s*)p118_last_post=([^;]+)/); if (lpm) lastPost = decodeURIComponent(lpm[1]); } catch (e) {} var subs = { '{user_agent}': navigator.userAgent || '', // Prefer the last article read; fall back to the (lossy) HTTP referer. '{referer}': lastPost || document.referrer || '', '{last_post}': lastPost || '', '{embed_url}': window.location.href || '', }; for (var name in data) { if (!data.hasOwnProperty(name)) continue; var v = data[name]; if (typeof v !== 'string') continue; for (var tag in subs) { if (v.indexOf(tag) !== -1) v = v.split(tag).join(subs[tag]); } data[name] = v; } // Agent/BDM attribution — read LIVE so it works even on a fully cached // page (the browser always sees the real URL + cookie). URL ?ataid=/?cid= // first, then the 30-day agent_id/cid cookies set by atat-tracking.php. var _qp = new URLSearchParams(window.location.search); var _ataid = _qp.get('ataid') || (document.cookie.match(/(?:^|;\s*)agent_id=([^;]+)/) || [])[1] || ''; var _cid = _qp.get('cid') || (document.cookie.match(/(?:^|;\s*)cid=([^;]+)/) || [])[1] || ''; if (_ataid) data.agent_id = decodeURIComponent(_ataid); if (_cid) data.bdm_id = decodeURIComponent(_cid); // Which page the form was actually on. Read live for the same reason // as the attribution above: on a cached page the server has no idea // which URL it is being served as, but the browser always does. data._sourceUrl = window.location.href; // …and what sent them to it. Read live for the same reason: a cached // page cannot know either, but the browser does. Blank when they // typed the address, used a bookmark, or came from an email client — // which is the honest answer, not a gap to be filled in server-side. data._referrerUrl = document.referrer || ''; // This browser's progress through a multi-page form is now sent. if (typeof progVisit !== 'undefined' && progVisit) data._visitId = progVisit; return data; } function uploadFiles(data) { var fileInputs = form.querySelectorAll('input[type=file][data-crm-file-field]'); var uploads = []; fileInputs.forEach(function(el) { if (!el.files || !el.files[0]) return; var fd = new FormData(); fd.append('file', el.files[0]); var fieldName = el.name; uploads.push( fetch(crmApiUrl + '/public/forms/' + "08ceeaae-4809-444e-ba0f-88da5836c463" + '/upload', { method: 'POST', body: fd }) .then(function(r) { if (!r.ok) throw new Error('File upload failed (' + r.status + ')'); return r.json(); }) .then(function(res) { if (res.path) data[fieldName] = res.path; else throw new Error(res.error || 'File upload failed'); }) ); }); return Promise.all(uploads).then(function() { return data; }); } function submitFormData(data) { var body = new FormData(); body.append('action', 'p118_crm_submit'); body.append('form_id', "08ceeaae-4809-444e-ba0f-88da5836c463"); body.append('data', JSON.stringify(data)); // Embed-page context for GF-style merge tags ({embed_url}, // {embed_post:post_title}, {embed_post:ID}). Captured PHP-side // at render time, then echoed to JS so the submit fetch can // forward to V2 as request headers. body.append('embed_url', ""); body.append('embed_post_id', "0"); body.append('embed_post_title', ""); return fetch("https:\/\/www.property118.com\/wp-admin\/admin-ajax.php", { method: 'POST', body: body, credentials: 'same-origin' }) .then(function(r){ return r.json(); }) .then(function(res){ var p = res.data || res; if (p && p.success) { if (typeof progDone === 'function') progDone(); if (p.confirmationType === 'form' && p.nextFormId) { return swapInNextForm(p.nextFormId, p.prefill || {}); } if (p.confirmationType === 'redirect' && p.confirmationRedirectUrl) { window.location.href = p.confirmationRedirectUrl; } else { form.style.display = 'none'; msg.className = 'crm-message success'; // Sent: the progress line says so, not "Step 6 of 6". var doneText = wrap.querySelector('.crm-progress-text'); if (doneText) doneText.textContent = 'Completed'; var doneFill = wrap.querySelector('.crm-progress-fill'); if (doneFill) doneFill.style.width = '100%'; msg.innerHTML = p.confirmationMessage || 'Thank you for your submission.'; // Carry the calculator's own results across. A calculator form // renders its figures into [data-iht-results] as the visitor // types; hiding the form hid those too, so the completion // screen went blank and the visitor lost what they had just // worked out. Nothing happens on forms without that element. var resultsEl = form.querySelector('[data-iht-results]'); if (resultsEl && resultsEl.innerHTML.trim()) { var carried = document.createElement('div'); carried.className = 'crm-message-results'; carried.innerHTML = resultsEl.innerHTML; msg.appendChild(carried); } // Download link for a generated report, present only when the // API filed the PDF before responding. Built through the DOM // rather than concatenated into innerHTML so the URL cannot // inject markup. if (p.reportDownloadUrl) { var dlWrap = document.createElement('p'); dlWrap.className = 'crm-report-download'; var dl = document.createElement('a'); dl.className = 'crm-report-button'; // Building through the DOM stops markup injection but not a // javascript: URL, which would run in this origin the moment // the submitter clicks the button. dl.href = /^https?:\/\//i.test(String(p.reportDownloadUrl)) ? p.reportDownloadUrl : '#'; dl.target = '_blank'; dl.rel = 'noopener noreferrer'; // The form's generator names its own button (e.g. a guide, not a report). dl.textContent = (typeof p.reportDownloadLabel === 'string' && p.reportDownloadLabel) ? p.reportDownloadLabel : 'Download your report (PDF)'; dlWrap.appendChild(dl); msg.appendChild(dlWrap); } msg.style.display = 'block'; if (msg.scrollIntoView) { msg.scrollIntoView({ behavior: 'smooth', block: 'start' }); } } } else { throw new Error((p && p.error) || 'Submission failed.'); } }); } // Replace this whole form widget with another form, rendered server-side // with the carried-over values seeded in. Inline injected via // innerHTML won't run, so we re-create each script node to execute it // (this is what wires up the new form's submit / conditional logic). function swapInNextForm(nextFormId, prefill) { var rbody = new FormData(); rbody.append('action', 'p118_crm_render_form'); rbody.append('form_id', nextFormId); rbody.append('prefill', JSON.stringify(prefill || {})); return fetch("https:\/\/www.property118.com\/wp-admin\/admin-ajax.php", { method: 'POST', body: rbody, credentials: 'same-origin' }) .then(function(r){ return r.json(); }) .then(function(res2){ var pd = res2.data || res2; if (!pd || !pd.html) { throw new Error((pd && pd.error) || 'Could not load the next form.'); } var frag = document.createElement('div'); frag.innerHTML = pd.html; var parent = wrap.parentNode; var nodes = []; while (frag.firstChild) { var node = frag.firstChild; parent.insertBefore(node, wrap); nodes.push(node); } parent.removeChild(wrap); function reexec(old) { var s = document.createElement('script'); for (var a = 0; a The post Take back your time and put money in the bank by selling your properties appeared first on Property118 .

Originally published by

Property118

Read full article