# Añade un formulario de contacto a un sitio de GitHub Pages

> GitHub Pages no ejecuta código de servidor, así que un POST de formulario recibe 405 Not Allowed; envía a un backend de formularios y configura bien las redirecciones del project site.

Publicado el 2026-10-25 · https://ikiform.com/es/guides/contact-form-static-site/github-pages

GitHub Pages solo sirve archivos estáticos, así que un formulario que envía a tu propio sitio recibe `405 Not Allowed` y el mensaje se pierde. Pon un endpoint de Ikiform en el `action` del formulario, añade `username.github.io` y tu dominio personalizado, si tienes, a **Allowed domains**, y redirige a una página de agradecimiento que incluya la ruta de tu project site. No cambia nada en tu repositorio ni en los ajustes de Pages, y ningún secreto acaba en el repositorio público.

## Qué cambia en GitHub Pages

GitHub Pages publica lo que genera tu repositorio o tu workflow de Actions, y nada más. Jekyll se ejecuta en el build si lo usas, y el resultado son archivos. No hay manejador de peticiones, así que `mail()` de PHP, un script de Node o un formulario CGI en Python no van a funcionar, hagas el commit que hagas.

Puedes ver el fallo desde una terminal. Un POST a cualquier página de un sitio de Pages recibe esto:

`Terminal`:

```bash
curl -i -X POST -d "message=hello" https://ada.github.io/notebook/contact.html
```

`Respuesta`:

```http
HTTP/2 405
server: Varnish
content-length: 131

<html>
<head><title>405 Not Allowed</title></head>
```

Otras cuatro cosas distinguen a Pages de otros hosts estáticos, y cada una afecta al formulario de contacto:

| Rasgo de Pages | Qué significa para el formulario |
| --- | --- |
| Los project sites viven bajo una ruta, `username.github.io/repo/` | Una URL de agradecimiento `/thanks.html` apunta al user site, no a tu proyecto. Cada URL necesita la parte `/repo/` |
| No hay ajuste para cabeceras de respuesta personalizadas | Una Content-Security-Policy solo puede venir de una etiqueta `<meta>` en la página |
| El repositorio suele ser público | Todo lo que contiene es público. El id del formulario puede estar ahí; una clave de API secreta o la contraseña de un email, nunca |
| GitHub dice que los sitios de Pages no deben usarse para transacciones sensibles como enviar contraseñas o números de tarjeta de crédito | Que el formulario de contacto sea solo eso. No recojas datos de tarjetas ni contraseñas de cuentas con él |

Diagrama: Qué ejecuta GitHub Pages y a dónde tiene que ir el envío de un formulario de contacto

1. Haces push a la rama desde la que publica Pages.
2. Jekyll o tu workflow de Actions genera el sitio una vez.
3. Pages sirve el resultado como archivos estáticos en github.io o en tu dominio personalizado.
4. Un visitante envía el formulario de contacto. Si su action apunta a tu propio sitio, Pages responde 405 Not Allowed.
5. Con el action apuntando a tu endpoint de Ikiform, Ikiform guarda el mensaje y te envía un email.
6. Ikiform redirige el navegador a la página de agradecimiento, que Pages sirve como cualquier otro archivo.

El código se ejecuta en GitHub una vez por cada push. Nada tuyo se ejecuta cuando un visitante pulsa Enviar, y por eso el envío tiene que salir de github.io.

## El formulario

En un sitio escrito a mano, añade `contact.html` junto a `index.html`. Este es para un project site en `ada.github.io/notebook`, así que la redirección incluye `/notebook/`:

`contact.html`:

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.com"
    />
    <title>Contact</title>
  </head>
  <body>
    <h1>Get in touch</h1>
    <form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
      <label for="name">Name</label>
      <input id="name" name="name" autocomplete="name" required />

      <label for="email">Email</label>
      <input id="email" name="email" type="email" autocomplete="email" required />

      <label for="about">What is it about?</label>
      <select id="about" name="about">
        <option value="question">A question about the project</option>
        <option value="hire">Work together</option>
        <option value="other">Something else</option>
      </select>

      <label for="message">Message</label>
      <textarea id="message" name="message" rows="6" minlength="10" required></textarea>

      <input type="hidden" name="page" value="docs-home" />
      <input type="hidden" name="_redirect" value="https://ada.github.io/notebook/thanks.html" />
      <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
      <button type="submit">Send</button>
    </form>
  </body>
</html>
```

- La Content-Security-Policy en `<meta>` es opcional. Mantenla si ya tienes una, y asegúrate de que `form-action` y `connect-src` incluyen `https://ikiform.com`. Pages no puede enviar la cabecera por ti.
- El select `about` te dice por qué alguien escribió antes de que abras el mensaje. Cambia las opciones por los temas sobre los que la gente te escribe de verdad.
- El input oculto `page` registra en qué página estaba el formulario, algo útil cuando el mismo formulario está en varias páginas de documentación.
- Mantén `email` como nombre del campo. Los emails de notificación lo usan como dirección de respuesta.

Si el mismo HTML se despliega bajo varias rutas, o no sabes cuál será la ruta final, construye la redirección en el navegador a partir de la dirección de la propia página. Pon esto después del formulario:

`contact.html`:

```html
<script>
  document.querySelectorAll('input[name="_redirect"]').forEach((input) => {
    input.value = new URL("thanks.html", document.baseURI).href;
  });
</script>
```

¿Usas Jekyll? No escribas la URL a mano. La [guía de Jekyll](https://ikiform.com/es/docs/guides/jekyll.md) tiene un include que la construye con `absolute_url`, que añade tu `baseurl` en los project sites. ¿Generas con Astro, Vite o Hugo mediante un workflow de Actions? Pon la ruta base del framework en `/repo/` para que carguen los assets del formulario, y usa la guía de [Hugo](https://ikiform.com/es/docs/guides/hugo.md) o la de tu framework para el formulario en sí.

## Dominios permitidos en github.io

Añade `ada.github.io`, con tu propio nombre de usuario. Cubre tu user site y todos tus project sites, porque los project sites comparten el mismo host. Añade también tu dominio personalizado si usas uno; `example.com` también cubre `www.example.com`.

Nunca añadas `github.io` por sí solo. Coincidiría cualquier sitio de Pages, incluidas las copias de tu formulario en sitios de otras personas. Con dominios permitidos configurados, se sigue un `_redirect` a `https://ada.github.io/notebook/thanks.html`; sin ellos, Ikiform lo ignora y muestra su página de agradecimiento alojada.

> Activa **Enforce HTTPS** en los ajustes de Pages del repositorio. Ikiform solo sigue valores de `_redirect` que empiezan por `https:`, y un dominio personalizado que aún no tiene certificado sirve páginas `http:`.

## Reemplazar un enlace mailto:

Muchos sitios de Pages empiezan con un enlace `mailto:` en el pie de página. Cambiarlo por el formulario impide que los nuevos scrapers lean tu dirección en la página, y los visitantes sin una app de correo configurada por fin pueden contactarte. Dos cosas que debes saber antes de borrarlo:

- La dirección sigue en el historial de tu repositorio, y en cualquier fork o archivo de la página antigua. El formulario ayuda a partir de ahora; no vuelve privada una dirección que lleva años siendo pública.
- Las respuestas salen de tu propia bandeja de entrada. Los emails de notificación ponen al remitente como dirección de respuesta, así que pulsar **Reply** funciona como el enlace `mailto:`, pero desde el otro lado.

Ikiform nunca envía emails al visitante, así que indica en la página de agradecimiento cuándo puede esperar una respuesta. Si recibes más mensajes de los que puedes contestar por email, un webhook puede dejar cada uno en [Slack](https://ikiform.com/blog/form-submissions-to-slack.md) o [Discord](https://ikiform.com/blog/form-submissions-to-discord.md).

## Comprueba que funciona

Haz esto en la dirección `github.io` en producción. Pages no tiene despliegues de preview, así que prueba primero en local con un servidor local.

1. Añade `localhost` y `username.github.io` en **Allowed domains**, más tu dominio personalizado si tienes uno.
2. Haz commit de `contact.html` y `thanks.html`, espera a que termine el despliegue de Pages en la pestaña **Actions** del repositorio y abre la página publicada.
3. Envía el formulario. Deberías llegar a tu propio `thanks.html`, con la ruta del proyecto en la barra de direcciones.
4. Abre **Submissions** en el panel. El nuevo envío está ahí con todos los campos que enviaste.
5. Revisa la bandeja de entrada de cada destinatario de notificaciones. Cada uno recibe un email con los campos, y **Reply** va a quien envió el formulario cuando este tiene un campo `email`.
6. ¿No llegó nada? Cambia el filtro de estado del panel a **Spam**. El spam recibe la misma respuesta de éxito que un envío real.

## Problemas habituales

| Problema | Qué hacer |
| --- | --- |
| `405 Not Allowed` al pulsar Enviar | El formulario desplegado sigue enviando a tu propio sitio. Mira el código fuente de la página en github.io y revisa el `action`; un build antiguo en caché puede seguir publicado unos minutos. |
| La página de agradecimiento de Ikiform en lugar de la tuya | Al valor de `_redirect` le falta la ruta del repositorio, es `http:`, o el formulario no tiene dominios permitidos. Añade `username.github.io` y usa la URL `https:` completa. |
| `403 origin_not_allowed`, o todos los envíos acaban en spam | El dominio de la página no está en **Allowed domains** del formulario. Añádelo, más `localhost` para las pruebas locales. Los subdominios de un dominio de la lista están permitidos. |
| Los envíos funcionan en local, pero la consola muestra un error de CSP en github.io | A una Content-Security-Policy en `<meta>` de tu layout le falta `https://ikiform.com` en `form-action` o `connect-src`. |

## Preguntas

### ¿Puede GitHub Pages procesar envíos de formularios?

No. GitHub Pages sirve archivos estáticos y no ejecuta código cuando un visitante envía un formulario, así que un POST a una URL de Pages recibe 405 Not Allowed. Apunta el action del formulario a un backend de formularios.

### ¿Es seguro hacer commit del endpoint del formulario en un repositorio público?

Sí. El id del formulario acaba en tu HTML público de todos modos. Protege el formulario con dominios permitidos y las comprobaciones antispam, y nunca hagas commit de una clave de API secreta, porque un sitio estático no tiene ningún lugar seguro donde guardarla.

### ¿Por qué mi redirección de agradecimiento va a la página equivocada en un project site?

Una ruta como /thanks.html apunta a la raíz de username.github.io, no a tu proyecto. Usa la URL completa con la ruta del repositorio, o constrúyela a partir de document.baseURI en el navegador.

### ¿Sigue funcionando el formulario después de añadir un dominio personalizado a GitHub Pages?

Sí. El formulario envía a Ikiform sea cual sea el dominio que sirve la página. Añade el dominio personalizado en Allowed domains junto a username.github.io, y cambia cualquier valor de _redirect para que use el dominio personalizado cuando HTTPS esté forzado.

### ¿Ikiform es gratis?

No, no hay plan gratuito. Probar es gratis: puedes enviar 50 envíos de prueba al mes desde localhost antes de pagar. Para pasar a producción, Pro empieza en 6 $/mes por 500 envíos, o puedes pagar una sola vez 10.000 envíos por 49 $ o 50.000 envíos por 149, $ con créditos que no caducan nunca.

## Más guías

- [Formulario de contacto en un sitio estático](https://ikiform.com/es/guides/contact-form-static-site.md): Añade un formulario de contacto que funcione a un sitio estático sin servidor: a dónde va el POST, el spam, las respuestas por email y código para cada host y framework.
- [Netlify](https://ikiform.com/es/guides/contact-form-static-site/netlify.md): Usa un formulario de contacto en un sitio de Netlify sin Netlify Forms: sin detección en el build, envíos JSON desde React y un formulario aparte para las deploy previews.
- [Cloudflare Pages](https://ikiform.com/es/guides/contact-form-static-site/cloudflare-pages.md): Añade un formulario de contacto a Cloudflare Pages sin escribir una Function: envía a un backend de formularios, define cabeceras en _headers y usa tus propias claves de Turnstile.
- [Web de portfolio](https://ikiform.com/es/guides/contact-form-static-site/portfolio-website.md): Un formulario de contacto para portfolio que hace las preguntas de una primera llamada: proyecto, presupuesto, plazos y un enlace, sin servidor y respondiendo desde tu bandeja.
- [Vercel](https://ikiform.com/es/guides/contact-form-static-site/vercel.md): Una página estática en Vercel responde al POST de un formulario con 405 Method Not Allowed; aquí verás por qué y cómo enviar el formulario a un backend.
- [Amazon S3](https://ikiform.com/es/guides/contact-form-static-site/aws-s3.md): Los website endpoints de S3 solo aceptan GET y HEAD por HTTP plano, así que envía el formulario de contacto a un backend y configura bien las redirecciones con CloudFront.
