Company ownership reaches 45% of buy to let
Property118 Company ownership reaches 45% of buy to let Nearly half of buy to let ownership is now held through companies, according to new figures from property management and finance platform Lendlord. Its latest data puts company ownership at 45.1%, against 54.9% held privately. The balance changes sharply as portfolios grow, with 57.6% of ownership among landlords with 20 or more properties held through companies. Company ownership becomes the larger share for the first time among landlords with portfolios of 11 to 20 properties. Company ownership grows The firm’s co-founder and chief executive, Aviram Shahar, said: “Company ownership is no longer a niche structure used only at the very top of the market. “45.1% of BTL ownership is already sitting in a company, and among larger portfolios it is the majority model at 57.6%.” He added: “That split matters. Smaller landlords still tend to hold in their own name.” North East leads The North East has the highest proportion of company ownership at 53.5%. Companies also account for the larger share of buy to let ownership in Yorkshire and Humberside. Scotland is another market where company-held property exceeds private ownership. BOOK YOUR CONSULTATION TODAY 1. Property business details Tax year — Select — 2026/27 2027/28 Gross annual rent * Annual mortgage interest and other finance costs Non-finance costs — Select — Use a percentage of gross rent Enter an annual amount Non-finance costs as a percentage of rent Annual non-finance costs Number of associated companies Company profit extraction — Select — Retain all post-tax profit in the company Extract all post-tax profit as dividends Extract a percentage as dividends Percentage of post-tax profit to extract as dividends 2. Owners and shareholders Name Ownership / shareholding % UK tax resident? — Select — Yes No Other UK taxable income (excluding dividends) Other UK dividend income Include in salary optimisation Yes Include in dividend allocation Yes Over State Pension age Yes Entitled to a UK Personal Allowance Yes Apply UK dividend tax in this illustration Yes Apply UK PAYE and National Insurance to salary Yes Remove Name Ownership / shareholding % UK tax resident? — Select — Yes No Other UK taxable income (excluding dividends) Other UK dividend income Include in salary optimisation Yes Include in dividend allocation Yes Over State Pension age Yes Entitled to a UK Personal Allowance Yes Apply UK dividend tax in this illustration Yes Apply UK PAYE and National Insurance to salary Yes Remove Add another shareholder Get your free PDF report Your name Your email Yes, email me this report as a PDF. I understand a Property118 consultant may follow up. Email me the report (function(){ var uid = "crm-form-263729fd"; 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 ''; 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 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() !== ''; }); } function crmFirstInvalid(pageIdx) { for (var i = 0; i [ ][ ]". // 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 || ''; 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/' + "ffff0ec7-9df5-4d37-9e93-d9bb0b6b64fc" + '/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', "ffff0ec7-9df5-4d37-9e93-d9bb0b6b64fc"); 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 (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'; 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'; dl.textContent = '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 .p118-crm-form{--navy:#0b3d66;--pale:#f5f8fb;--line:#cfd9e3;--muted:#5b6775;max-width:980px;margin:24px auto;font-family:Arial,Helvetica,sans-serif;color:#1f2937} .p118-crm-form .crm-field{margin-bottom:14px} .p118-crm-form label{display:block;font-weight:700;color:var(--navy);margin-bottom:6px} .p118-crm-form input[type="number"],.p118-crm-form input[type="text"],.p118-crm-form input[type="email"],.p118-crm-form select{width:100%;min-height:44px;border:1px solid #aebdca;border-radius:7px;padding:10px 11px;background:#fff;font-size:16px;color:#1f2937;box-sizing:border-box} .p118-crm-form input:focus,.p118-crm-form select:focus,.p118-crm-form button:focus{outline:3px solid rgba(21,93,145,.24);outline-offset:1px} /* section headings */ .p118-crm-form .crm-section,.p118-crm-form h2,.p118-crm-form h3{color:var(--navy);font-size:20px;margin:22px 0 6px;padding-top:10px;border-top:1px solid var(--line)} /* shareholder repeater rows as cards */ .p118-crm-form .crm-repeater-field>label{font-size:20px;border-top:1px solid var(--line);padding-top:10px;margin-top:22px} .p118-crm-form .crm-repeater-rows{counter-reset:p118sh} .p118-crm-form .crm-repeater-row{counter-increment:p118sh;position:relative;border:1px solid #b9c7d3;border-radius:10px;padding:52px 16px 16px;margin-top:12px;background:#fbfdff} .p118-crm-form .crm-repeater-row::before{content:"Shareholder " counter(p118sh);position:absolute;top:15px;left:16px;font-size:18px;font-weight:700;color:var(--navy)} .p118-crm-form .crm-repeater-row-fields{display:grid;grid-template-columns:1fr;gap:12px} .p118-crm-form .p118-btl-calc-btn{appearance:none;border:0;border-radius:8px;padding:13px 20px;font-size:16px;font-weight:700;cursor:pointer;background:var(--navy);color:#fff;margin:12px 0} .p118-crm-form .crm-repeater-row .crm-field{margin-bottom:0} .p118-crm-form .crm-repeater-row-fields input:not([type="checkbox"]),.p118-crm-form .crm-repeater-row-fields select{width:100%;box-sizing:border-box} /* single-checkbox booleans (salary optimisation, dividends, pension, non-resident toggles) */ .p118-crm-form .crm-repeater-row-fields .crm-choices label{display:flex;gap:9px;align-items:center;font-weight:400;min-height:44px;margin:0;cursor:pointer} .p118-crm-form .crm-repeater-row-fields .crm-choices input[type="checkbox"]{width:18px;height:18px;min-height:0;flex:0 0 auto;margin:0} .p118-crm-form .crm-repeater-remove{position:absolute;top:12px;right:12px;background:#fff;color:#9f1239;border:1px solid #e6a6b8;border-radius:8px;padding:6px 10px;font-size:13px;font-weight:700;cursor:pointer} .p118-crm-form .crm-repeater-add,.p118-crm-form button[type="submit"]{appearance:none;border:0;border-radius:8px;padding:12px 17px;font-size:16px;font-weight:700;cursor:pointer} .p118-crm-form .crm-repeater-add{background:#e6eef5;color:var(--navy);border:1px solid #b9cad9;margin-top:12px} .p118-crm-form button[type="submit"]{background:var(--navy);color:#fff} /* consent + submit area */ .p118-crm-form .crm-consent-label{font-weight:400;display:flex;gap:8px;align-items:flex-start} /* results */ .p118-btl-results{margin-top:20px} .p118-btl-hint{background:var(--pale);border:1px dashed var(--line);border-radius:10px;padding:16px;color:var(--muted);font-size:15px} .p118-btl-out{border:2px solid var(--navy);border-radius:12px;background:var(--pale);padding:18px} .p118-btl-headline{font-weight:700;font-size:18px;margin:0 0 14px} .p118-btl-headline.p118-pos{color:#17633a} .p118-btl-headline.p118-neg{color:#9f1239} .p118-btl-tablewrap{overflow-x:auto;margin-top:12px} .p118-btl-table{width:100%;border-collapse:collapse;background:#fff;font-size:14px;min-width:640px} .p118-btl-table th,.p118-btl-table td{border:1px solid #d6e0e8;padding:9px 10px;vertical-align:top} .p118-btl-table th{background:var(--navy);color:#fff;text-align:left} .p118-btl-table td+td{text-align:right;white-space:nowrap} .p118-btl-table tr:nth-child(even) td{background:#f8fafc} .p118-btl-table tr.p118-btl-strong td{font-weight:700} .p118-btl-table td.p118-btl-net{text-align:right;white-space:nowrap} /* summary cards */ .p118-btl-summary{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin:14px 0} .p118-btl-card{background:#fff;border:1px solid var(--line);border-radius:10px;padding:15px;display:flex;flex-direction:column} .p118-btl-cardlabel{display:block;color:var(--muted);font-size:13px;margin-bottom:7px} /* margin-top:auto pins the value to the bottom of each (equal-height) card, so all three values line up even when a label wraps to two lines */ .p118-btl-value{font-size:24px;font-weight:700;color:var(--navy);margin-top:auto} .p118-btl-value.p118-pos{color:#17633a} .p118-btl-value.p118-neg{color:#9f1239} @media(max-width:760px){.p118-btl-summary{grid-template-columns:1fr}} /* figures-used recap + warning notes */ .p118-btl-figures{background:#fff;border:1px solid var(--line);border-radius:10px;padding:15px;margin-top:15px} .p118-btl-figures p{margin:6px 0 0;line-height:1.5} .p118-btl-warning{background:#fff8e6;border:1px solid #e8cc80;border-radius:9px;padding:12px;margin-top:14px;font-size:14px;line-height:1.5} /* "Estimated result" heading (override the section-heading border-top) */ .p118-btl-out .p118-btl-resulttitle{border-top:0;padding-top:0;margin:0 0 10px;color:var(--navy);font-size:21px} /* static disclaimer notes, always visible below the form */ .p118-btl-notes{margin-top:18px} .p118-btl-note{font-size:12px;color:var(--muted);line-height:1.5;margin-top:14px} /* title + intro at the top (matches the standalone calculator) */ .p118-btl-header{margin-bottom:8px} .p118-btl-title{color:var(--navy);font-size:28px;line-height:1.2;margin:0 0 10px} .p118-btl-intro{line-height:1.55;margin:0 0 6px;color:#1f2937} (function(){ 'use strict'; // ── Field ids (must match the seeded BTL Calculator form) ────────────────── var F = { year:'8ca5faab-7fd0-4263-ab16-c28cac2a33dd', rent:'29a6d2dd-1824-4d07-ae3f-5e14607d1599', interest:'a4cb8069-1050-4ccd-951f-d5aa4dea1cee', costMode:'3decd8c1-e458-4085-bb5c-fab26e71432b', costPct:'b6bf5767-e12f-4c46-aec2-06a7eba263f3', costAmt:'e9cb6264-8168-4b6d-81ff-c4ed51212e4f', associated:'aaa3b1a0-7c1d-4e01-aa7b-069a6335ff7d', extraction:'fdce52bf-c76f-449c-a63b-bce9e7906c6b', extractionPct:'8bd621d8-8eef-4114-9db9-d0f5f7c17963', shareholders:'c441faed-5bf9-4290-9335-37c9aba50a40', shName:'fe7f6621-acd1-42b9-b4fc-37ca29cbaf1a', shShare:'9df50ba8-ff5a-454a-9748-4bb9cb45f5c8', shResident:'e5601232-087b-4db0-8d4a-4b4a91cbbeb3', shOtherIncome:'3843a208-bb92-4b89-be11-9bd3088b57e9', shOtherDiv:'5609d332-3866-48ea-9ca1-4eb519005d46', shOptimise:'bce9079f-806e-4f61-b9ec-ea9591cd448f', shDividends:'b2b09c3b-0ef8-4ec5-8c29-686d2db85d8a', shPension:'5e6320c4-fd4b-4a97-93a0-f9048581c39a', // non-UK-resident only (shown when "UK tax resident?" = No) shEntitledPa:'7c1de2f0-11aa-4b22-9c33-a1b2c3d40001', shApplyDivTax:'7c1de2f0-11aa-4b22-9c33-a1b2c3d40002', shApplySalaryTax:'7c1de2f0-11aa-4b22-9c33-a1b2c3d40003', name:'df54eedb-ec85-410c-b865-c14e5d5983c6', reportData:'c8e98065-9275-4fd9-9bbe-607e5eb8e588' }; var repeater = document.querySelector('.crm-repeater[data-repeater-id="' + F.shareholders + '"]'); var mount = document.querySelector('.p118-btl-results'); var reportField = document.querySelector('[name="' + F.reportData + '"]'); if (!repeater || !mount) { return; } var form = repeater.closest('form') || document; // ── helpers to read the native fields ────────────────────────────────────── function el(id){ return form.querySelector('[name="' + id + '"]'); } function val(id){ var e = el(id); return e ? e.value : ''; } function num(id){ var v = parseFloat(val(id)); return isFinite(v) ? v : 0; } function entered(id){ var e = el(id); return !!e && String(e.value).trim() !== ''; } // read a sub-field inside a given repeater row by its child field id function rowEl(row, childId){ var w = row.querySelector('[data-field-id="' + childId + '"]'); return w ? w.querySelector('input,select,textarea') : null; } function rowVal(row, childId){ var e = rowEl(row, childId); return e ? e.value : ''; } function rowNum(row, childId){ var v = parseFloat(rowVal(row, childId)); return isFinite(v) ? v : 0; } function rowYes(row, childId, dflt){ var e = rowEl(row, childId); if (!e) return dflt; if (e.type === 'checkbox') return !!e.checked; return e.value === 'yes'; } function money(v){ return new Intl.NumberFormat('en-GB',{style:'currency',currency:'GBP',maximumFractionDigits:0}).format(isFinite(v)?v:0); } function percent(v){ return (isFinite(v)?v:0).toFixed(1).replace('.0','') + '%'; } function escapeHtml(v){ return String(v||'').replace(/[&<>'"]/g,function(ch){return {'&':'&',' ':'>',"'":''','"':'"'}[ch];}); } // ── TAX ENGINE (unchanged — same maths as the standalone calculator) ─────── var TAX = { '2026':{pa:12570,basicBand:37700,additionalThreshold:125140,generalRates:[0.20,0.40,0.45],propertyRates:[0.20,0.40,0.45],dividendRates:[0.1075,0.3575,0.3935],dividendAllowance:500,section24Rate:0.20,employeePT:12570,employeeUEL:50270,employeeMain:0.08,employeeUpper:0.02,employerST:5000,employerRate:0.15,ctLowerLimit:50000,ctUpperLimit:250000,ctSmallRate:0.19,ctMainRate:0.25,ctMarginalFraction:0.015}, '2027':{pa:12570,basicBand:37700,additionalThreshold:125140,generalRates:[0.20,0.40,0.45],propertyRates:[0.22,0.42,0.47],dividendRates:[0.1075,0.3575,0.3935],dividendAllowance:500,section24Rate:0.22,employeePT:12570,employeeUEL:50270,employeeMain:0.08,employeeUpper:0.02,employerST:5000,employerRate:0.15,ctLowerLimit:50000,ctUpperLimit:250000,ctSmallRate:0.19,ctMainRate:0.25,ctMarginalFraction:0.015} }; function personalAllowance(ani,eligible,tax){ if(!eligible){return 0;} return Math.max(0,tax.pa-Math.max(0,ani-100000)/2); } function allocateTax(amount,occupied,rates,zeroRateAllowance,tax){ amount=Math.max(0,amount);occupied=Math.max(0,occupied);zeroRateAllowance=Math.max(0,zeroRateAllowance||0); var basicAvailable=Math.max(0,tax.basicBand-Math.min(occupied,tax.basicBand)); var basic=Math.min(amount,basicAvailable);var remaining=amount-basic;occupied+=basic; var higherAvailable=Math.max(0,tax.additionalThreshold-Math.max(occupied,tax.basicBand)); var higher=Math.min(remaining,higherAvailable);remaining-=higher;var additional=Math.max(0,remaining); var slices=[basic,higher,additional];var totalTax=0; for(var i=0;i =upper){return profit*tax.ctMainRate;} return profit*tax.ctMainRate-(upper-profit)*tax.ctMarginalFraction; } function personalScenario(state,shareholders){ var tax=TAX[state.year];var propertyProfitBeforeInterest=state.rent-state.costs;var totalTax=0,totalCredit=0,totalCash=state.rent-state.costs-state.interest;var rows=[]; shareholders.forEach(function(s){ var propertyProfit=Math.max(0,propertyProfitBeforeInterest*s.share);var interest=state.interest*s.share; var base=computeIncomeTax({other:s.otherIncome,property:0,dividends:s.otherDividends,personalAllowance:s.personalAllowance,applyDividendTax:s.applyDividendTax},tax); var full=computeIncomeTax({other:s.otherIncome,property:propertyProfit,dividends:s.otherDividends,personalAllowance:s.personalAllowance,applyDividendTax:s.applyDividendTax},tax); var adjustedAboveAllowance=Math.max(0,full.ani-full.allowance);var creditBase=Math.min(interest,propertyProfit,adjustedAboveAllowance); var credit=Math.max(0,creditBase*tax.section24Rate);var incremental=Math.max(0,full.tax-credit-base.tax); totalTax+=incremental;totalCredit+=credit; }); return {tax:totalTax,credit:totalCredit,cashAfterTax:totalCash-totalTax,combinedWealth:totalCash-totalTax}; } function dividendAllocations(dividendPool,shareholders){ var participants=shareholders.filter(function(s){return s.receivesDividends;});var participatingShares=participants.reduce(function(sum,s){return sum+s.share;},0);var result={}; shareholders.forEach(function(s){result[s.index]=0;});if(dividendPool 0){return null;} var ct=corporationTax(preCT,state.associated,tax);var postCT=preCT-ct;var dividendPool=Math.max(0,postCT)*state.extractionPct; var dividendByShareholder=dividendAllocations(dividendPool,shareholders);var personalTax=0,employeeNi=0,netCash=0,rows=[]; shareholders.forEach(function(s,i){ var salary=Math.max(0,salaries[i]||0);var dividend=dividendByShareholder[s.index]||0; var base=computeIncomeTax({other:s.otherIncome,property:0,dividends:s.otherDividends,personalAllowance:s.personalAllowance,applyDividendTax:s.applyDividendTax},tax); var taxableSalary=s.applySalaryTax?salary:0; var full=computeIncomeTax({other:s.otherIncome+taxableSalary,property:0,dividends:s.otherDividends+dividend,personalAllowance:s.personalAllowance,applyDividendTax:s.applyDividendTax},tax); var incomeTax=Math.max(0,full.tax-base.tax);var eni=employeeNIC(salary,tax,s.statePensionAge,s.applySalaryTax); personalTax+=incomeTax;employeeNi+=eni;netCash+=salary+dividend-incomeTax-eni; rows.push({name:s.name,resident:s.resident,salary:salary,dividend:dividend,incomeTax:incomeTax,employeeNi:eni,net:salary+dividend-incomeTax-eni}); }); var retained=postCT-dividendPool;var combinedWealth=retained+netCash; return {operatingProfit:operatingProfit,employerNi:employerNi,employeeNi:employeeNi,ct:ct,dividendPool:dividendPool,personalTax:personalTax,retained:retained,combinedWealth:combinedWealth,rows:rows}; } function salaryOptimisationLimit(s,state,tax){ if(!s.optimiseSalary||!s.personalAllowance){return 0;} var dividendsForAllowance=s.applyDividendTax?s.otherDividends:0;var allowance=personalAllowance(s.otherIncome+dividendsForAllowance,true,tax);var unusedAllowance=Math.max(0,allowance-s.otherIncome);var companyCashProfit=Math.max(0,state.rent-state.costs-state.interest);return Math.min(unusedAllowance,companyCashProfit); } function candidateSalaries(s,state,tax){ var limit=salaryOptimisationLimit(s,state,tax);if(limit localBest.combinedWealth+0.01){localBest=result;localSalary=candidate;}}); if(localSalary!==salaries[i]){salaries[i]=localSalary;best=localBest;changed=true;}} if(!changed){break;}} for(var j=0;j best.combinedWealth+0.01){best=result;salaries=trial;}}} return best; } // ── read the form ────────────────────────────────────────────────────────── function getState(){ var costMode=val(F.costMode)||'percent';var rent=num(F.rent); var costs=costMode==='amount'?num(F.costAmt):rent*(num(F.costPct)/100); var extractionMode=val(F.extraction)||'retain'; var extractionPct=extractionMode==='retain'?0:(extractionMode==='all'?1:num(F.extractionPct)/100); return {year:val(F.year)||'2026',rent:rent,interest:num(F.interest),costs:costs,associated:Math.max(0,Math.floor(num(F.associated))),extractionMode:extractionMode,extractionPct:Math.max(0,Math.min(1,extractionPct))}; } function getShareholders(){ var rows=repeater.querySelectorAll('.crm-repeater-row'); return Array.prototype.map.call(rows,function(row,index){ var resident=rowVal(row,F.shResident)!=='no'; return {index:index,name:(rowVal(row,F.shName)||'').trim()||('Shareholder '+(index+1)),share:Math.max(0,rowNum(row,F.shShare))/100,resident:resident, otherIncome:Math.max(0,rowNum(row,F.shOtherIncome)),otherDividends:Math.max(0,rowNum(row,F.shOtherDiv)), optimiseSalary:rowYes(row,F.shOptimise,true),receivesDividends:rowYes(row,F.shDividends,true),statePensionAge:rowYes(row,F.shPension,false), // UK residents get full PA + UK dividend/PAYE treatment; for a non-resident // read the three conditional fields (defaults match the original: PA off, // dividend tax off, salary PAYE/NI on). personalAllowance:resident?true:rowYes(row,F.shEntitledPa,false), applyDividendTax:resident?true:rowYes(row,F.shApplyDivTax,false), applySalaryTax:resident?true:rowYes(row,F.shApplySalaryTax,true), entered:{share:String(rowVal(row,F.shShare)).trim()!==''}}; }); } function personName(){ var f=form.querySelector('[name="'+F.name+'[first]"]'),l=form.querySelector('[name="'+F.name+'[last]"]'); return ((f?f.value:'')+' '+(l?l.value:'')).trim(); } // ── render + report_data ──────────────────────────────────────────────────── function showMessage(msg){ mount.innerHTML=' '+escapeHtml(msg)+' '; if(reportField){reportField.value='';} } function recompute(){ var state=getState();var shareholders=getShareholders(); if(!entered(F.rent)||state.rent 0.0001){ return showMessage('Ownership percentages must total 100% (currently '+percent(shareTotal*100)+').'); } var personal=personalScenario(state,shareholders); var optimised=optimiseSalaries(state,shareholders); if(!optimised){ return showMessage('No feasible salary combination for these figures.'); } var diff=optimised.combinedWealth-personal.combinedWealth;var cls=diff>=0?'p118-pos':'p118-neg'; var headline=diff>=0?'The optimised company structure leaves an estimated '+money(diff)+' more in combined cash and retained profit each year.':'Personal ownership leaves an estimated '+money(Math.abs(diff))+' more cash each year.'; var nonResident=shareholders.some(function(s){return !s.resident;}); var html=' Estimated result '+headline+' '+ ' '+ ' Personal ownership: annual cash after tax '+money(personal.cashAfterTax)+' '+ ' Optimised company: combined cash and retained profit '+money(optimised.combinedWealth)+' '+ ' Ten-year straight-line difference '+money(diff*10)+' '+ ' '+ ' Metric Personal ownership Company (optimised) '+ row2('Annual property cash before tax',money(state.rent-state.costs-state.interest),money(optimised.operatingProfit))+ row2('Income Tax on property / remuneration (before Section 24 credit)',money(personal.tax+personal.credit),money(optimised.personalTax))+ row2('less: Section 24 finance-cost tax credit',money(personal.credit),'Not applicable')+ row2('Corporation Tax','Not applicable',money(optimised.ct))+ row2('Employer and employee National Insurance','Not applicable',money(optimised.employerNi+optimised.employeeNi))+ row2('Dividends extracted','Not applicable',money(optimised.dividendPool))+ row2('Profit retained in company','Not applicable',money(optimised.retained))+ ' '+cell('Combined wealth after tax')+cell(money(personal.combinedWealth))+cell(money(optimised.combinedWealth))+' '+ row2('Five-year straight-line total',money(personal.combinedWealth*5),money(optimised.combinedWealth*5))+ row2('Ten-year straight-line total',money(personal.combinedWealth*10),money(optimised.combinedWealth*10))+ ' '+ ' Shareholder UK tax status Salary Dividend Income Tax Employee NI Net received '+ optimised.rows.map(function(r){return ' '+cell(escapeHtml(r.name))+cell(r.resident?'UK resident':'Non-UK resident')+cell(money(r.salary))+cell(money(r.dividend))+cell(money(r.incomeTax))+cell(money(r.employeeNi))+' '+money(r.net)+' ';}).join('')+ ' '+ ' Figures used Gross annual rent: '+money(state.rent)+'. Non-finance costs: '+money(state.costs)+'. Annual mortgage interest and other finance costs: '+money(state.interest)+'. '+ (nonResident?' Non-UK resident shareholder warning: the calculator shows a simplified UK-only position. It does not calculate overseas tax, foreign tax credits, temporary non-residence rules or treaty outcomes. ':'')+ ' Salary optimisation: the calculator considered salaries only where a person had unused Personal Allowance, tested the relevant Income Tax and National Insurance outcomes, and restricted total salaries to the company’s available annual profit. Any salary must relate to genuine work performed for the company. '+ ' Projection warning: five- and ten-year figures are simple multiples of the annual result. They do not assume rent growth, inflation, refinancing, investment returns or future changes in tax law. '+ ' '; mount.innerHTML=html; if(reportField){ reportField.value=JSON.stringify({ name:personName()||undefined,taxYear:state.year,rent:state.rent,costs:state.costs,interest:state.interest, personal:{tax:personal.tax,credit:personal.credit,combinedWealth:personal.combinedWealth}, company:{operatingProfit:optimised.operatingProfit,ct:optimised.ct,nationalInsurance:optimised.employerNi+optimised.employeeNi,personalTax:optimised.personalTax,dividendPool:optimised.dividendPool,retained:optimised.retained,combinedWealth:optimised.combinedWealth, shareholders:optimised.rows.map(function(r){return {name:r.name,resident:r.resident,salary:r.salary,dividend:r.dividend,incomeTax:r.incomeTax,employeeNi:r.employeeNi,net:r.net};})} }); } return true; } function cell(v){ return ' '+v+' '; } function row2(label,a,b){ return ' '+cell(escapeHtml(label))+cell(a)+cell(b)+' '; } // ── two-step flow: "Calculate comparison" computes + reveals the report step ─ var revealed=false; var resultsField=mount.closest('.crm-field')||mount; var submitWrap=form.querySelector('.crm-submit'); // Everything AFTER the results mount is the "Get your free PDF report" step — // hidden until the user calculates. (Submit lives outside the field flow on // the WP render, so handle it explicitly too.) var reportEls=[];var sib=resultsField.nextElementSibling; while(sib){reportEls.push(sib);sib=sib.nextElementSibling;} function setReportVisible(v){ reportEls.forEach(function(e){e.style.display=v?'':'none';}); if(submitWrap){submitWrap.style.display=v?'':'none';} } setReportVisible(false); // Conditional inputs — show cost %/amount + extraction % based on the selects, // like the original calculator. Runs on load + on every change. function showField(id,show){ var e=el(id); var w=e?e.closest('.crm-field'):null; if(w){ w.style.display=show?'':'none'; } } // Per shareholder row: the three UK-treatment fields are only relevant to a // non-UK-resident, so reveal them only when that row's "UK tax resident?" = No // (exactly like the standalone calculator). function toggleShareholderRow(row){ var nonRes = rowVal(row,F.shResident)==='no'; [F.shEntitledPa,F.shApplyDivTax,F.shApplySalaryTax].forEach(function(cid){ var w = row.querySelector('[data-field-id="'+cid+'"]'); if(w){ w.style.display = nonRes ? '' : 'none'; } }); } function toggleShareholderRows(){ Array.prototype.forEach.call(repeater.querySelectorAll('.crm-repeater-row'),toggleShareholderRow); } // Checkbox fields can't be default-checked via the field config, so seed each // row's defaults once (salary optimisation + dividend allocation + PAYE/NI on; // everything else off). Marked so a user un-tick is never re-applied. function initShareholderRow(row){ if(row.getAttribute('data-p118-init')==='1'){return;} row.setAttribute('data-p118-init','1'); [F.shOptimise,F.shDividends,F.shApplySalaryTax].forEach(function(cid){ var e=rowEl(row,cid); if(e&&e.type==='checkbox'){e.checked=true;} }); // "UK tax resident?" renders with a blank "— Select —"; default it to Yes. var res=rowEl(row,F.shResident); if(res&&res.tagName==='SELECT'&&!res.value){res.value='yes';} } function initShareholderRows(){ Array.prototype.forEach.call(repeater.querySelectorAll('.crm-repeater-row'),initShareholderRow); } function toggleConditional(){ var cm=val(F.costMode)||'percent', ex=val(F.extraction)||'retain'; showField(F.costPct, cm==='percent'); showField(F.costAmt, cm==='amount'); showField(F.extractionPct, ex==='partial'); toggleShareholderRows(); } initShareholderRows(); toggleConditional(); form.addEventListener('change', toggleConditional); var calcBtn=document.createElement('button'); calcBtn.type='button';calcBtn.className='p118-btl-calc-btn';calcBtn.textContent='Calculate comparison'; if(resultsField.parentNode){resultsField.parentNode.insertBefore(calcBtn,resultsField);} calcBtn.addEventListener('click',function(){ if(recompute()){ revealed=true; setReportVisible(true); } }); // Once the report step is showing, keep the figures live as they tweak inputs. var t; function schedule(){ if(!revealed){return;} clearTimeout(t); t=setTimeout(recompute,120); } form.addEventListener('input',schedule); form.addEventListener('change',schedule); document.addEventListener('crm-repeater-change',function(e){ if(repeater.contains(e.target)||e.target===repeater){ initShareholderRows(); toggleShareholderRows(); if(revealed){ schedule(); } } }); // Title + intro at the very top, matching the standalone calculator. if (form && form.tagName === 'FORM' && !form.querySelector('.p118-btl-header')) { var header=document.createElement('div'); header.className='p118-btl-header'; header.innerHTML=' Buy-to-let tax comparison calculator Compare the estimated annual position where a residential property business is owned personally or through a limited company. Property information is entered first, followed by each owner or shareholder. '; form.insertBefore(header, form.firstChild); } // Static disclaimers — always visible below the form (as in the standalone // calculator). Appended outside the so the two-step reveal never hides them. var notes=document.createElement('div'); notes.className='p118-btl-notes'; notes.innerHTML=' This calculator is an indicative comparison for England, Wales and Northern Ireland. It does not calculate SDLT, CGT, ATED, Employment Allowance, pension contributions, student loans, Gift Aid, Marriage Allowance, High Income Child Benefit Charge, brought-forward losses or brought-forward finance costs. It does not model overseas tax or individual double-taxation treaties. Any salary must relate to genuine work performed for the company. '+ ' If the company is structured with multiple classes of shares, it may be possible for the founders or directors to distribute profits in proportions that differ from the overall shareholdings. The structure may also allow the value of the founders’ shares to be frozen, with future growth allocated to other share classes to incentivise the next generation and support longer-term inheritance tax planning. '; (form.parentNode||form).appendChild(notes); showMessage('Enter your figures above, then press Calculate comparison.'); })(); BOOK YOUR CONSULTATION TODAY The post Company ownership reaches 45% of buy to let appeared first on Property118 .
Originally published by
Property118