<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
  <title>TeamHub blog</title>
  <link>https://tldr.host/teamhub/blog/</link>
  <description>Notes on team collaboration, digital sovereignty and running TeamHub on your own Nextcloud.</description>
  <language>en</language>
  <lastBuildDate>Tue, 08 Sep 2026 09:00:00 GMT</lastBuildDate>
  <atom:link href="https://tldr.host/teamhub/blog/feed.xml" rel="self" type="application/rss+xml"/>
  <item>
    <title>Where do you keep your team's decisions? A decision log inside Nextcloud</title>
    <link>https://tldr.host/teamhub/blog/decisions-module-teamhub.html</link>
    <guid isPermaLink="true">https://tldr.host/teamhub/blog/decisions-module-teamhub.html</guid>
    <pubDate>Tue, 08 Sep 2026 09:00:00 GMT</pubDate>
    <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Justin Doek</dc:creator>
    <category>decisions</category>
    <category>governance</category>
    <category>nextcloud</category>
    <description>Six months on, somebody always asks why we do it this way. The Decisions module in TeamHub gives a Nextcloud team one place to record the answer.</description>
    <content:encoded><![CDATA[<p>We make decisions all day, from small ones to those that matter to many people. But where do we store them? Who initiated the decision your team took a while ago? Who decided this was the way to do it? And was there even a decision in the first place, or is it just something somebody assumes was decided?</p>
<p>Six months after a call is made, somebody always asks the same question: <strong>why do we do it this way?</strong></p>
<p>In most teams the honest answer is a shrug. The conversation happened &mdash; with some luck it was in chat or mail and you can find it again. In most cases the conclusion was never written down. In the worst case the person who made the call has moved to another team, or another company.</p>
<p><strong>Decisions</strong> is a module in TeamHub, the team workspace app for Nextcloud. It gives a team one place to put a proposal, discuss it, lock its wording, and record who approved it and why.</p>
<p>The scope is deliberately narrow. It is for all those choices your team will be asked about again: budgets, hiring, vendors, tooling, process changes, architecture. Anything where the reason matters as much as the outcome.</p>
<p><img src="https://tldr.host/teamhub/blog/images/decisions-hero.jpg" alt="A TeamHub decision, showing impact, category, proposer, the locked proposal text, linked tasks and the audit trail" width="1536" height="512"></p>
<h2>How a team benefits</h2>
<ul>
<li>Every outcome carries a written reason.</li>
<li>The wording locks before anyone approves it.</li>
<li>Proposals route themselves to the right approver.</li>
<li>Every decision has its own audit trail.</li>
<li>Decisions link into a tree, not a list.</li>
<li>A settled question gets superseded, not overwritten.</li>
<li>Nothing leaves your server.</li>
</ul>
<h2>How it works</h2>
<p>There is <strong>one way in</strong>: <strong>Propose decision</strong> on the team's Decisions tab, or the <strong>+</strong> on the Decisions widget on the team home.</p>
<p>The form asks for a <strong>subject</strong> (what is the team deciding?), the <strong>context</strong> (the full proposal, in Markdown, with file attachments that become part of the record), an <strong>impact</strong> of low, medium or high, an optional <strong>level</strong> of operational, tactical or strategic, and a <strong>category</strong>. The category is what routes the proposal: each one carries its own approvers, so filing under Budget puts it in front of the finance lead without anyone having to chase.</p>
<p>Then, at the bottom of the form, after you have written it, it asks how the proposal should be handled:</p>
<ul>
<li><strong>Finalize it now.</strong> Straight to <em>Awaits approval</em>. The wording locks immediately.</li>
<li><strong>Discuss it with people I pick.</strong> Opens a Talk conversation with just those people. The proposal stays open.</li>
<li><strong>Discuss it with the whole team.</strong> Posts it as a thread in the team conversation, where any member can respond. The proposal stays open.</li>
</ul>
<p>A private discussion is genuinely private. The proposal stays out of everybody else's activity feed, message stream and decisions list until it is finalized. A direct link to it reports that it does not exist, rather than that you are not allowed to see it.</p>
<p>While a proposal is open, the proposer can edit it in response to feedback. That is the entire point of a discussion phase: you should be able to act on what people said without withdrawing the proposal and starting over, losing the discussion with it. Comments are threaded underneath and cannot be switched off. A discussion you can't reply to isn't one.</p>
<p><strong>Finalize</strong> locks the wording, writes a snapshot of the final proposal into the team's folder as a Markdown file, and hands the proposal to the approver.</p>
<p>The approver, or approvers, find it in the <strong>Approve</strong> tab of the Decisions widget, carrying a count of what needs their attention, and in their personal <strong>My Work</strong> queue. They open it, read the locked text, write a reason, and press Approve or Deny. The reason field cannot be left empty.</p>
<p>A decision does not have to be the end of it. Link a decision to one or several tasks, and to the earlier decision it executes on, and the flat list becomes a tree: the operational call visibly hanging off the tactical decision that authorised it. Links work in both directions. When a settled decision needs revisiting, you propose a superseding decision, and the original stays in the history with the link carried forward.</p>
<h2>The synergy: it links, it does not copy</h2>
<p>TeamHub's rule is that it does not become another silo. Deck stays the source of truth for tasks, Talk for conversations, Files for documents. Decisions links to them and writes back to them.</p>
<ul>
<li><strong>Talk.</strong> The discussion venue. A private conversation with the people you picked, or a thread in the team's own conversation.</li>
<li><strong>Deck.</strong> Link an existing card to a decision, or create one that arrives already linked. Approved decisions usually mean somebody has to do something; this is where that lands.</li>
<li><strong>Files.</strong> The finalized proposal and its attachments live in the team folder as Markdown. Readable without TeamHub, and they outlive it.</li>
<li><strong>Calendar.</strong> An approver can schedule a meeting with the other approvers of the category, pre-filled with the proposal text, and the meeting is recorded back onto the decision.</li>
<li><strong>My Work.</strong> The personal queue across every team you are in. Proposals awaiting your approval sit under <em>Action required</em> and can be approved or denied straight from the row. Your own open proposals sit there too, because only you can finalize them. Proposals you will be asked to approve later appear under <em>Waiting for others</em>, so approvals stop arriving as surprises.</li>
<li><strong>What's new.</strong> An <em>Open proposals</em> tab with its own count, across all your teams at once.</li>
<li><strong>Timeline.</strong> Decisions appear as dated events, with an optional overlay drawing the line from a decision to the tasks it produced.</li>
<li><strong>Advanced projects.</strong> Decisions can be attached to milestones, and an unresolved decision close to a milestone date marks that milestone as slipping in the project health widget. When a project closes, its decisions are written out as part of the closing record.</li>
<li><strong>Notifications.</strong> The ordinary Nextcloud bell, for proposals in your category, and for outcomes and comments on your own.</li>
</ul>
<h2>What a team admin or owner controls</h2>
<p>All of it is per team, under <strong>Manage team</strong>.</p>
<ul>
<li><strong>The master switch.</strong> <em>Integrations &rarr; Decisions</em>. Off hides the tab and the widget; on brings them back together with every past decision. New teams get it switched on in the create-team wizard, where you can untick it. Turning it off deletes nothing.</li>
<li><strong>Categories.</strong> <em>Integration settings &rarr; Decisions</em>. Each one gets a name, an icon, an optional description and its own approver list. Any one approver can decide; they do not all have to. A category cannot be saved with zero approvers. Create one without, and the team owner is added, because a category that routes to nobody is a decision that gets stuck. The first category is the default on the proposal form, so put the most common one first.</li>
<li><strong>Minimum role for actions.</strong> Member, Moderator or Admin, governing Finalize, Link task, Create task and Link decision. Tighten it in teams where decisions need careful curation; leave it open where everyone keeps their own thread tidy.</li>
<li><strong>Propose is always open to every member.</strong> Deliberately. Proposing is how a team surfaces a question, and restricting it defeats the point of the module.</li>
<li><strong>The level field.</strong> Off by default. Turn it on if your team genuinely thinks in operational, tactical and strategic terms. Leave it off if it would just be one more required field.</li>
</ul>
<p>Above all of this sits one control for the Nextcloud administrator: <strong>Admin settings &rarr; TeamHub &rarr; Decisions module</strong>, which decides whether team admins can enable it at all.</p>
<h2>Getting started</h2>
<p>Decisions ships with TeamHub and is switched on per team. Set the categories up first: each one carries its own approver list, and that list is what lets a proposal route itself instead of being chased. The <a href="https://tldr.host/teamhub/docs/user/decisions">user documentation</a> walks through proposing, discussing and approving, and the <a href="https://tldr.host/teamhub/docs/team-management/decisions">team management guide</a> covers categories and permissions.</p>
<p>TeamHub installs from the <a href="https://apps.nextcloud.com/apps/teamhub">Nextcloud App Store</a>, and every decision it records stays in your own database, on your own server.</p>]]></content:encoded>
  </item>
  <item>
    <title>How to migrate from Microsoft Teams to Nextcloud without rebuilding every team</title>
    <link>https://tldr.host/teamhub/blog/migrate-from-microsoft-teams-to-nextcloud.html</link>
    <guid isPermaLink="true">https://tldr.host/teamhub/blog/migrate-from-microsoft-teams-to-nextcloud.html</guid>
    <pubDate>Sat, 08 Aug 2026 09:00:00 GMT</pubDate>
    <dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Justin Doek</dc:creator>
    <category>migration</category>
    <category>sovereignty</category>
    <category>nextcloud</category>
    <description>Most migrations off Microsoft Teams stall at the same place: somebody works out that moving 200 teams by hand is three weeks of clicking. Here is how to do it with one CSV instead.</description>
    <content:encoded><![CDATA[<p>Every migration off Microsoft Teams starts the same way. Somebody builds the business case, the licence maths works, everyone agrees the data should live somewhere you control — and then a project manager opens a spreadsheet, counts 200 teams, and quietly works out that recreating them by hand is three weeks of clicking.</p>
<p>That is usually where it stops. Not because the destination is worse, but because the first day of work is unreasonable.</p>
<p>TeamHub 4.6.11 adds <strong>bulk import</strong>, which turns that first day into one upload. This post covers what the file looks like, how to get one out of Microsoft Teams, and what to check before you press the button.</p>
<h2>What actually needs to move</h2>
<p>A team in Microsoft Teams is a name, a description, a set of owners, a set of members, and some tabs. Almost none of that is precious — it is just tedious to recreate. The messages themselves are a separate question, and worth being honest about: <strong>bulk import moves the structure, not the chat history.</strong></p>
<p>In practice that matters less than it sounds. Old chat is usually kept for compliance rather than for reading, and it can stay where it is, or be exported to archive storage, while day-to-day work moves. What people need on Monday morning is their team, their people and their files — not last March&#39;s thread about the printer.</p>
<p>So the goal is narrow and achievable: <strong>every team exists in Nextcloud, with the right people in it, on day one.</strong></p>
<h2>The file</h2>
<p>Bulk import reads a CSV with eight columns:</p>
<table>
<thead>
<tr><th>Column</th><th>What goes in it</th></tr>
</thead>
<tbody>
<tr><td><code>name</code></td><td>The team name. Letters, digits, spaces, hyphens and underscores</td></tr>
<tr><td><code>description</code></td><td>Free text, shown on the team home</td></tr>
<tr><td><code>template</code></td><td><code>project</code>, <code>collaboration</code> or <code>department</code></td></tr>
<tr><td><code>project_mode</code></td><td>Whether the team starts in project mode</td></tr>
<tr><td><code>owner</code></td><td>One account — the person who owns the team</td></tr>
<tr><td><code>members</code></td><td>Everyone else, separated by semicolons</td></tr>
<tr><td><code>apps</code></td><td>Which Nextcloud apps appear as tabs</td></tr>
<tr><td><code>modules</code></td><td>Which TeamHub modules are switched on</td></tr>
</tbody>
</table>
<p>The separators are forgiving. The file can be comma, semicolon or tab separated, and cells holding several values accept either semicolons or vertical bars — which matters more than it sounds, because a Dutch or German Excel export is semicolon-separated by default and would otherwise need fixing by hand.</p>
<p>The <code>template</code> column is the one worth thinking about, because it decides which modules a team starts with. Three options:</p>
<ul>
<li><strong><code>project</code></strong> — for work with a start and an end. Phases, milestones, budget and time.</li>
<li><strong><code>collaboration</code></strong> — for a group that works together continuously without a delivery date.</li>
<li><strong><code>department</code></strong> — for a standing organisational unit.</li>
</ul>
<p>If you are not sure, <code>collaboration</code> is the safe default. Modules can be switched on later per team; nothing here is a one-way door.</p>
<h2>Getting the file out of Microsoft Teams</h2>
<p>The Teams admin centre will give you a team list, but not in this shape. The reliable route is Microsoft Graph, which can enumerate groups and their members directly:</p>
<pre><code class="language-powershell">Connect-MgGraph -Scopes &quot;Group.Read.All&quot;, &quot;User.Read.All&quot;

$teams = Invoke-MgGraphRequest -Method GET `
  -Uri &quot;https://graph.microsoft.com/v1.0/groups?`$filter=resourceProvisioningOptions/Any(x:x eq &#39;Team&#39;)&quot;

foreach ($team in $teams.value) {
  $owners  = Invoke-MgGraphRequest -Method GET `
    -Uri &quot;https://graph.microsoft.com/v1.0/groups/$($team.id)/owners&quot;
  $members = Invoke-MgGraphRequest -Method GET `
    -Uri &quot;https://graph.microsoft.com/v1.0/groups/$($team.id)/members&quot;
  # emit one CSV row per team
}</code></pre>
<p>Two mismatches will bite you, and both are better handled in the export than in TeamHub:</p>
<ol>
<li><strong>Microsoft Teams allows several owners. The import takes one.</strong> Pick a rule and apply it consistently — first alphabetically is fine — and put the remaining owners in the members column so nobody loses access. They can be promoted afterwards.</li>
<li><strong>Team names travel badly.</strong> Ampersands, slashes and emoji are common in Teams names and are not all valid in TeamHub. Sanitise on the way out, and keep a record of what you changed, because someone will ask why &quot;R&amp;D — Q3&quot; is now &quot;R-D Q3&quot;.</li>
</ol>
<p>It is worth writing a second file at the same time: a warnings list of teams with no owner, teams with several owners, names that were changed, duplicates, and accounts that could not be matched. That file is what you send to the department heads before you import anything.</p>
<blockquote>
<p>The migration is not the hard part. Agreeing who owns what, before you press go, is the hard part. A warnings file turns that from a meeting into an email.</p>
</blockquote>
<h2>The dry run</h2>
<p>This is the step that makes the whole thing safe, so do not skip it.</p>
<p>Upload the file and TeamHub validates every row without creating anything. It resolves each owner and member against real Nextcloud accounts and tells you which ones it could not find. It flags duplicate team names. It shows you exactly what will exist when you confirm.</p>
<p>Typical first-run findings, in rough order of frequency:</p>
<ul>
<li>People who left, still listed as members</li>
<li>Accounts that exist in Microsoft Entra but were never provisioned in Nextcloud</li>
<li>Two teams that turn out to have the same name once you strip the emoji</li>
<li>A department that has been quietly dead for a year</li>
</ul>
<p>None of these are import problems. They are the inventory you never had, and it is much cheaper to see them here than after 200 teams exist.</p>
<p>One deliberate piece of leniency: a row asking for <strong>advanced projects</strong> without an active licence becomes a basic project instead of failing. One missing licence does not stop the run.</p>
<h2>Pressing the button</h2>
<p>Confirm and the teams are created with live progress. Limits are 500 teams and 2 MB per file, which is a lot of teams — and if you have more, split the file by department and run it more than once. There is no harm in several runs.</p>
<p>You get a results CSV at the end with the outcome of every row. Keep it. It is the answer to &quot;did Marketing actually get created?&quot; three weeks later, and it is the thing you attach to the change record.</p>
<p>The flow, end to end:</p>
<ol>
<li>Export from Microsoft Graph to CSV</li>
<li>Fill the <code>template</code> column and fix the warnings</li>
<li>Upload and dry run</li>
<li>Confirm</li>
<li>Keep the results CSV</li>
</ol>
<p>Realistically the export and the cleanup take an afternoon. The import itself takes minutes.</p>
<hr>
<h2>Why this matters beyond convenience</h2>
<p>There is a reason we built this rather than writing a migration guide and wishing people luck.</p>
<p>The argument for self-hosting is rarely lost on the merits. It is lost on switching cost. If leaving a hyperscaler means a quarter of manual work, most organisations stay where they are and keep paying — not because they think it is right, but because the exit is priced out of reach. That is not really a technology decision.</p>
<p>Removing that first day of clicking is the point. Your teams end up on hardware you own, under <a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener noreferrer">AGPL-3.0</a>, with no seat count and no vendor able to change the terms — a <strong>sovereign solution your organisation actually controls</strong>, and one you can leave again if you want to, since the export is a CSV too.</p>
<p>That last part is deliberate. A migration path that only runs one way is just a different kind of lock-in.</p>
<h2>Getting started</h2>
<p>Bulk import lives in <strong>Admin settings → TeamHub → Import/Export</strong>, and there is a sample CSV there to match your columns against. Full detail is in the <a href="https://tldr.host/teamhub/release_notes/4.6.11/release_notes_4.6.11.html">4.6.11 release notes</a>, and the <a href="https://tldr.host/teamhub/docs">documentation</a> covers the column formats in more depth.</p>
<p>If you are sizing a migration and want a second opinion on the export, get in touch — we would rather help you plan it than watch it stall at the spreadsheet.</p>]]></content:encoded>
  </item>
</channel>
</rss>
