A wrong templateType fails silently
Of every failure mode we have hit building on HubSpot, this is the one that wastes the most time, because it does not look like a failure.
Build a system template with the wrong templateType and it uploads cleanly. It registers as a template. It appears in Design Manager exactly as expected. It just never appears in the dropdown it was written for.
Nothing appears in any log. There is no validation error and no warning. The only symptom is a human saying "I do not see it in the list", and the natural first assumption is that the upload did not work, so people upload it again.
So never guess the type
There are two places to get it, and they are both reliable.
Source A: HubSpot's own defaults. HubSpot ships its default system pages as readable source, so the type can be read straight off them.
/cms/v3/source-code/published/content/@hubspot/cmsdefaultsystempages/templates/membership/<file>.html
One catch: a directory listing of that folder returns an empty string, so you cannot browse it. Guess the filename from the slot name on the pattern membership-<slot-in-kebab>.html and fetch it directly. The naming is predictable enough that this usually works first or second try. Then read the annotation at the top of the file.
Source B: a template already working in your portal. List your templates and read template_type off one you know is assignable to that slot. Note it comes back as a number rather than a string.
When the two sources disagree, the working template wins
This is not hypothetical.
For the access denied page, HubSpot's own default declares error_page. A template built that way never appeared in the dropdown. The one already working in the portal was registered as plain page, and that is what worked.
Following HubSpot's own shipped default was the wrong move. Evidence from the portal you are actually working in beats documentation, and it beats HubSpot's own source, because what matters is what that portal will accept today.
The general shape
Silent failures are worth cataloguing separately from loud ones. A loud failure costs you the time to read the error. A silent one costs you the time to work out that there is something to look for, and that is an unbounded number.
The tell is always the same: something that should have appeared did not, and nothing anywhere is complaining about it. When you notice that shape, stop retrying the thing you just did. It probably worked. Go and find out what the platform expected instead.