Utilizar CKEditor en el proyecto de Laravel

Video thumbnail

Índice de contenido

Cuando empecé a trabajar con editores WYSIWYG en Laravel, probé muchos. Plugins ligeros, alternativas modernas, incluso algunos muy bonitos visualmente… pero al final siempre regresé a CKEditor. Para mí es “el Word para la web”: directo, completo y capaz de mostrar exactamente lo que el usuario va a ver. Y como ya he trabajado con CKEditor desde la versión 3, puedo decir que el salto a CKEditor 5 —que es el que vamos a integrar aquí— vale totalmente la pena.

Este tutorial no es solo otro “copiar y pegar la instalación”. Aquí te cuento cómo lo uso yo en mis proyectos reales, los errores que casi siempre te van a salir, cómo reaccionar cuando CKEditor decide mostrarte el famosísimo filerepository-no-upload-adapter, y cómo preparar tu build personalizado con Vite en Laravel.

CKEditor es ideal para mi para mi dashboard, para la gestión de este tipo de publicaciones, cuyo módulo protejo mediante el sistema de Permisos con Spatie en Laravel.

Qué es CKEditor 5 y por qué es ideal para proyectos en Laravel

Si nunca has trabajado con editores WYSIWYG, CKEditor 5 es literalmente lo que ves-es-lo-que-obtienes. Lo llamo así porque se comporta como un “Word online”: puedes poner títulos, tablas, bloques de código, colores, tachados, lo que quieras… y lo ves tal cual aparecerá en tu blog, panel o sección administrativa.

En mi caso, lo uso mucho para proyectos de publicación, especialmente blogs en Laravel. Cuando abro un detalle de un post, no quiero ver un texto plano y triste: quiero que tenga vida, formato y, si hace falta, incrustaciones de video o bloques de código bien destacados.

CKEditor: el Word para la web

Puede que sea la primera vez que escuchas sobre esto, o tal vez no, no lo sé. Pero básicamente, CKEditor es tal cual lo dice su nombre: lo que ves es lo que obtienes. Yo lo llamo un “Word en línea” o un “Word para la web”, ya que ofrece características similares a Microsoft Word o Google Docs.

Me refiero a un editor que te permite aplicar formatos como negritas, itálicas, texto tachado, cambio de color, títulos, tablas, listados, etc. Esto es precisamente lo que significa el término What You See Is What You Get (abreviado como WYSIWYG): puedes aplicar formato a tu texto y ver en tiempo real cómo se verá.

Es decir, estamos hablando de un plugin que nos permite enriquecer el contenido, para que no se vea tan plano o aburrido, sino con un estilo más atractivo visualmente. Esto es ideal, por supuesto, cuando trabajamos con un blog.

Aplicándolo en nuestro blog

Por eso es que vuelvo a utilizar este proyecto de Laravel, ya que se supone que estamos trabajando en un blog y no queremos que nuestras publicaciones aparezcan tan simples.

Cuando entras al detalle de una publicación, no debería verse tan “pálida”, sino que debería mostrar contenido enriquecido. Por ejemplo, puedes insertar videos de YouTube, algo más avanzado que las configuraciones básicas que traen estos editores por defecto.

En este tipo de editores, como el que ves aquí arriba, no tienes opciones para insertar un video de YouTube o algo similar. Solo cosas básicas como títulos, texto en negrita o subrayado.

Además, en mis publicaciones también coloco bloques de código. Ese formato lo defino desde otro lado, pero cuando usemos CKEditor, podremos indicar directamente que un bloque corresponde a código, entre otras muchas opciones.

Video thumbnail

Ahora bien, ya que sabemos qué significan estas siglas —What You See Is What You Get—, que pueden referenciarse de varias formas, en pocas palabras se trata de una forma de definir contenido enriquecido.
Ya te mostré un ejemplo de contenido enriquecido tanto en mi libro como en mi blog, así que creo que ha llegado el momento de presentar algún plugin disponible para esto.

Buscando un editor WYSIWYG

Obviamente, si escribes "What You See Is What You Get JavaScript" en Google, vas a encontrar muchísimos plugins. Ojo: no estoy diciendo que sean malos ni nada por el estilo. Seguramente muchos de ellos son excelentes, y llevan bastante tiempo en el mercado.

¿Por qué uso CKEditor?

En mi caso, utilizo CKEditorpor una razón muy simple: es un proyecto que, coloquialmente hablando, me parece serio.
Tiene años en el mercado, yo lo uso desde hace tiempo —creo que desde la versión 3, aunque no tanto—, y el equipo detrás del plugin lo mantiene constantemente actualizado.

Esto es importante, porque no todos los proyectos tienen esa constancia en sus mejoras.
Además, CKEditor es muy personalizable, y cuenta con buena documentación (aunque en ocasiones puede parecer un poco abstracta).

¿Qué lo hace destacar?

  • Está disponible para Vanilla JavaScript, que es el que vamos a usar (sin frameworks adicionales).
  • También puedes integrarlo con frameworks como Angular, React, Vue 2, Vue 3, e incluso con .NET.
  • Tiene una documentación amplia, y un equipo activo que mantiene vivo el proyecto.
  • Hay una sección de demos, donde puedes ver cómo luce, cómo funciona, y probar muchas de sus funcionalidades.
  • El editor incluye incluso un asistente con IA, lo cual demuestra la evolución constante del producto.
  • Puedes exportar a PDF, Word, modificar la interfaz, etc.
  • Es bonito visualmente y muy completo funcionalmente.

Ventajas frente a otros editores WYSIWYG

He probado muchos otros. Algunos son buenos, otros muy simples. Pero CKEditor destaca por:

  • Estabilidad (años en el mercado).
  • Un equipo activo que actualiza constantemente.
  • Funciones avanzadas como exportar a PDF, Word, plugins premium, CKBox, etc.
  • La posibilidad de crear tu propio build con solo marcar casillas.
  • Integración limpia con frameworks modernos.

Por qué lo uso para proyectos reales en Laravel

Yo lo uso porque:

  • Me ha respondido bien durante años.
  • Sé que no se quedará corto si mañana necesito más funciones.
  • El build es altamente personalizable.
  • Aunque la documentación puede ser algo “espesa”, una vez entiendes el flujo, todo encaja.

¿Y el precio?

Algunas de sus funciones más avanzadas son de pago. Eso es algo a tener en cuenta.
Por ejemplo, el producto llamado SunBox (creo que así se llama), que puedes integrar incluso con servicios como Dropbox y otros discos en línea, es una solución aparte que tiene un costo.

Sin embargo, incluso con las limitaciones de la versión gratuita, el plugin ya ofrece muchísimo.

¿Por qué lo uso?

En resumen, uso CKEditorpor tres razones:

  1. Confianza: llevo años usándolo y siempre ha respondido bien.
  2. Personalización: puedo ajustarlo prácticamente a lo que quiera.
  3. Escalabilidad: sé que nunca se me va a quedar corto si necesito ampliar funcionalidades.

Sí, es cierto que su configuración puede ser un poco compleja. De hecho, por eso mismo estoy haciendo estos videos: para ayudarte a superar esa barrera inicial.

Requisitos previos y preparación del entorno

Instalación vía NPM

Laravel ya trae integración con Node, así que este paso es directo:

$ npm i ckeditor5

Estructura inicial de archivos (JS, CSS y build)

Normalmente creo:

resources/js/ckeditor.js
resources/css/ckeditor.css

Y en ckeditor.js importo los módulos del build:

// Mis comentarios personales van siempre con doble barra (//)
// Así luego sé qué borrar y qué dejar.
import Editor from './build/editor.js';
import './build/styles.css';

Generar el build a medida

Video thumbnail

Desde la siguiente web:

https://ckeditor.com/ckeditor-5/builder/

Puedes generar tu CKEditor a medida, listo para copiar y pegar y sustituir o complementar código en el editor.js.

Configurar CKEditor 5 en un proyecto Laravel con Vite

Aquí viene una de las partes que más confunde a los principiantes.

Ajustes en vite.config.js

Laravel necesita saber cuáles archivos JS y CSS debe procesar. Por eso, agrego el editor:

export default defineConfig({
   plugins: [
       laravel({
           input: [
               'resources/css/ckeditor.css',
               'resources/js/ckeditor.js',
           ],
           refresh: true,
       }),
   ],
});

En el caso de Vite, es importante tener en cuenta la configuración. Creo que ya lo expliqué antes, pero básicamente, cuando empleamos Vite —que en este caso funciona como un “compilador”, por así decirlo— lo que hace es interpretar nuestra transpilación. Es decir, traduce nuestro CSS o JavaScript a un formato que el navegador pueda entender.

Para aclarar el concepto: un compilador tradicional sería, por ejemplo, Java, que toma un archivo .java y lo compila a código binario. En el caso de Vite, se mantiene el mismo tipo de código, pero se transforma, por ejemplo, de JavaScript moderno a una versión compatible con navegadores antiguos, o de SCSS a CSS.

Dentro de la configuración de Vite, definimos los archivos que queremos que interprete y también indicamos la ruta correspondiente, tal como lo establecimos anteriormente.

Integrar CKEditor 5 en un formulario Blade

Puedes usar un textarea:

<textarea class="form-control !hidden content" name="content">
   {{ old('content', $post->content) }}
</textarea>

O un div:

<div id="editor">
   {!! old('content', $post->content) !!}
</div>

El <div> es lo que CKEditor transformará en editor visual; la textarea queda escondida para guardar los datos al enviar el formulario.

Y al final:

***
@vite(['resources/css/ckeditor.css', 'resources/js/ckeditor.js'])

Inicialización del editor paso a paso

Este código es la parte crucial del editor y ya viene configurado en el build generado antesÑ

Editor
   .create(document.querySelector('#editor'))
   .then(editor => {
       // Sin miedo: aquí puedes personalizar
       window.editor = editor;
   })
   .catch(error => console.error(error));

Cómo habilitar la carga de imágenes (SimpleUploadAdapter)

Video thumbnail

La mayoría de personas se atasca porque arrastran una imagen, CKEditor intenta procesarla… y aparece el error:

ckeditor5.js?v=efbb40b8:9479 filerepository-no-upload-adapter 
Read more: https://ckeditor.com/docs/ckeditor5/latest/support/error-codes.html#error-filerepository-no-upload-adapter

A mí me pasó mil veces hasta que entendí que CKEditor no sube nada si no le especificas qué adaptador vas a usar.

Configurar el adaptador en el lado del cliente

Ejemplo:

import { SimpleUploadAdapter } from 'ckeditor5';
const editorConfig = {
   plugins: [ SimpleUploadAdapter ],
   simpleUpload: {
       uploadUrl: '/dashboard/post/upload/ckeditor',
       headers: {
           'X-CSRF-TOKEN': document
               .querySelector('meta[name="csrf-token"]')
               .content
       }
   }
};

Sin esto, CKEditor no sabe adónde enviar la imagen:

uploadUrl : '/dashboard/post/upload/ckeditor',	

Así que regresamos por acá. Vamos a importarlo en cualquier parte. Recuerda que es la importación, así que no importa dónde la coloques; puede ir arriba.

Abajo se encuentran las opciones simples. Aquí está un adapter; pendiente con el nombre, ya que con este nombre lo referenciamos en el apartado de plugins. Lo encuentras por aquí arriba, voy a colocarlo y, con esto, podemos utilizar la opción llamada simple.

Luego de todo esto, no importa el orden; lo importante es que sea después del plugin. Todas estas son las opciones, tal como te explicaba antes, un poco cuando te comentaba la estructura que tenemos en todo esto.

Evitar errores 419 configurando correctamente CSRF

Recuerda que en Laravel tenemos la protección de tipo CSRF, así que también la tenemos que configurar y usualmente lo tenemos en una etiqueta meta:

<meta name="csrf-token" content="{{ csrf_token() }}">

Ahora, para configurar los headers, vamos al apartado de headers y colocamos lo siguiente:

	simpleUpload:{
		uploadUrl : '/dashboard/post/upload/ckeditor',	
		headers: {
			'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
		}
	},

Ruta de subida y controlador en Laravel

Route::post('/dashboard/post/upload/ckeditor', [PostController::class, 'upload']);

Y en el controlador:

public function upload(Request $request)
{
   $file = $request->file('upload');
   $path = $file->store('uploads', 'public');
   return response()->json([
       'url' => asset('storage/'.$path)
   ]);
}

Plugins recomendados y opciones avanzadas del editor

Plugins gratuitos y muy usados:

  • Essentials
  • Paragraph
  • Bold
  • Italic
  • Table
  • CodeBlock
  • Image
  • ImageUpload

Plugins premium: CKFinder, CKBox, etc.

Video thumbnail

Cuando probé CKBox, noté que es excelente, pero de pago. Para la mayoría de proyectos pequeños o medianos, SimpleUploadAdapter es más que suficiente.

Formas de Configurar el Upload en CKEditor

Podemos trabajar con dos formas principales: la Premium, usando un par de paquetes, y la “forma de pobres”, que es más sencilla y emplea estas dos opciones: el SimpleUploadAdapter, que ya viene como un paquetico.

import {
    ClassicEditor,
    SimpleUploadAdapter,
    ***
} from 'ckeditor5';

De esta manera podemos importar todo. Más adelante veremos cómo importarlo, configurarlo y ajustar algunos detalles.

La otra opción es crear un adaptador personalizado. Lo importante es que notes la palabra adaptador, que es justamente lo que se indica aquí.

En cuanto a cuál emplear: yo me iría por el SimpleUploadAdapter, porque es más sencillo. La otra opción la solía usar en CKEditor 3 o 4, pero en la versión 5 no la necesitaba y, de hecho, al probar el código que tenía, no funcionaba correctamente; seguramente era algún detalle pequeño que había que corregir.

De todas formas, esta primera opción es suficiente para la mayoría de los casos. Simplemente lo tratamos un poco de manera teórica ahora, y luego sí la implementaremos en práctica:

https://ckeditor.com/docs/ckeditor5/latest/features/images/image-upload/image-upload.html

Incrustar Videos de Youtube Responsivamente en tu web + Laravel CKEditor

Video thumbnail

Otro detalle que considero interesante es que se puedan incrustar videos de YouTube aaptativos o responsivos en nuestra web.

Si vamos a Youtube, seleccionamos el video, damos clic en "Compartir", luego en "Incrustar", y copiamos el código generado.

Si en el editor intentas pegar ese código directamente, verás que no se adapta automáticamente al tamaño de la ventana. Para solucionarlo, tenemos un par de maneras, y esto depende nuevamente de cómo hayas configurado tu editor.

Responsividad del video

¿El problema? Que el video no es responsivo, es decir, no se adapta a distintos tamaños de pantalla. Verás que aparece una barra de desplazamiento muy incómoda.

Para solucionar esto, vamos a adaptar el video manualmente, definimos el siguiente CSS:

.video-container {
	aspect-ratio: 16 / 9;
	margin: 15px auto;
	overflow: hidden;
	position: relative;
	width: 100%;
	max-width: 600px;
}

.video-container iframe {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
}

Y ahora los videos lo definimos como:

<div class="video-container">
 <iframe src="..." frameborder="0" allowfullscreen></iframe>
</div>

Con eso, el iframe tiene width y height en 100%, top y left en 0, y la relación de aspecto definida se maneja desde el contenedor.

Puedes jugar con estos valores para ver cómo se comporta. Este HTML y CSS te sirve para cualquier video de YouTube con relación de aspecto 16:9, que es la más común.

Instalar y Configuración CKEditor en Laravel Livewire

Video thumbnail

En Laravel Livewire, el proceso de instalación es exactamente igual, lo que cambia es que lo usamos en un componente de Laravel Livewire; que no es más que un simple DIV, junto con un DIV de nivel superior para la alineación en base a las columnas:

resources/views/livewire/dashboard/post/save.blade.php

***
<div class="col-span-10 sm:col-span-6">
    <Label for="">Text</Label>
    <div id="ckcontent">{!! $text !!}</div>
</div>
***

Al final del componente, agregamos el script del CKEditor y para crear el mismo:

***
    </x-form-section>
    <script src="{{ asset('js/ckeditor/ckeditor.js') }}"></script>
        @vite(['resources/js/ckeditor.js'])
    </div>
</div>

Aunque en versiones recientes de Laravel Livewire no debe de existir problemas de actualizar el componente mediante Livewire al escribir sobre algún elemento HTML wire:model, en caso de que necesites realizar alguna actualización antes de enviar el formulario (es decir, cuando en la consola de desarrolladores, en la sección de network vemos peticiones, se redibuja el componente afectado que en este ejemplo sería el de guardado), puedes especificar el siguiente atributo:

<div wire:ignore class="col-span-6 sm:col-span-4">
   <div id="ckcontent">{!! $text !!}</div>
</div>

Eliminar caracteres y HTML de CKEditor

Video thumbnail

Por aquí quería mostrar rápidamente una implementación con CKEditor que estoy haciendo. Como te he comentado, tengo una relación un poco de amor y odio con CKEditor: es un plugin excelente, con muchas opciones, pero algunas configuraciones se vuelven un poco complicadas. Por ejemplo, ciertas opciones no se quitan fácilmente.

La característica que estoy implementando es en mi web de Academia. Me inspiré un poco en la gente de PacktPub, que permite visualizar los libros dentro de la web. Por ejemplo, al hacer clic en un libro, puedes ver el HTML directamente, lo cual me parece excelente.

Problema con el contenido HTML generado

El problema que tengo es que el HTML que genera CKEditor al copiarlo añade automáticamente <br> innecesarios. El contenido original que estoy copiando no tiene <br> ni nada raro, solo algunas clases y etiquetas básicas.

Para ilustrarlo, agarro un trozo de contenido (en este ejemplo no es un capítulo completo) y lo copio desde Source en CKEditor. Luego doy a guardar.

Al revisar, se ve así:

  • Algunos saltos de línea aparecen “muertos”.
  • CKEditor agrega <br> de manera automática, incluso donde no deberían estar.
  • Esto complica la manipulación directa del HTML.

Mi implementación para remover HTML no deseado

No estoy completamente orgullosa de esta solución, pero fue lo que se me ocurrió para no tener que trastear demasiado con la configuración de CKEditor.

En lugar de enviar directamente el contenido con wire:submit.prevent, hago lo siguiente:

Creo un formulario normal con un identificador:

<form id="formData">
  {{-- <form wire:submit.prevent="sendData"> --}}
</form>

Uso JavaScript para escuchar el evento submit:

document.querySelector("#formData").addEventListener("submit", function(event) {
   event.preventDefault();
   // Código para manipular HTML y enviarlo
});

Guardo el contenido del editor en un elemento oculto donde pueda manipularlo:

<div id="htmlCkeditor" class="hidden"></div>
document.querySelector("#htmlCkeditor").innerHTML = editor.getData();
document.querySelector("#htmlCkeditor").innerHTML =
   document.querySelector("#htmlCkeditor").innerHTML.replaceAll('<p>&nbsp;</p>', "");

Finalmente, envío el contenido limpio al servidor usando Livewire:

$wire.submit(document.querySelector("#htmlCkeditor").innerHTML);

Nota: esta parte se puede reemplazar con la tecnología que estés usando, como Axios o Inertia, para hacer la petición al servidor.

Problema de modificar HTML directamente en CKEditor

CKEditor vuelve a definir el HTML automáticamente y guarda una especie de espejo interno. Esto impide editar directamente el contenido mediante JavaScript dentro del editor.

Al principio, estuve más de dos horas intentando eliminar los <br> y no entendía por qué se regeneraban. Al final, descubrí que manipulando el contenido fuera del editor antes de enviarlo al servidor, puedo eliminar los elementos no deseados sin problemas.

Instalar y Configuración CKEditor en Laravel Inertia - Vue

Video thumbnail

En Laravel Inertia o Vue, el proceso cambia, ya que, al tener que configurar en Vue y no una plantilla en blade, los pasos son ligeramente diferentes.

Al usar Inertia con Vue como motor de vistas, necesitamos la integración de CKEditor para Vue. Una de las ventajas de CKEditor es que está disponible para muchas tecnologías de JavaScript, como React, Angular, WordPress, entre otros.

En el caso de Inertia con Vue, debemos instalar dos paquetes:

$ npm install --save @ckeditor/ckeditor5-vue @ckeditor/ckeditor5-build-classic

CKEditor es un editor de texto similar a Word, pero diseñado para aplicaciones web. Es un plugin WYSIWYG, de código abierto, fácil de usar y altamente recomendado. Ha estado en el mercado por mucho tiempo y recibe actualizaciones constantes, lo que lo convierte en una opción confiable para proyectos web.

Configuración en Vue

Para integrar CKEditor en Vue dentro de un proyecto Laravel con Inertia, se agrega en el archivo resources/js/app.js:

import CKEditor from '@ckeditor/ckeditor5-vue';
createInertiaApp({
   title: (title) => `${title} - ${appName}`,
   resolve: (name) => require(`./Pages/${name}.vue`),
   setup({ el, app, props, plugin }) {
       return createApp({ render: () => h(app, props) })
           .use(plugin)
           .use(Oruga) // otros plugins como Oruga UI
           .use(CKEditor)
           .mixin({ methods: { route } })
           .mount(el);
   },
});

Uso en componentes Vue

Supongamos que tenemos un campo textarea en un formulario de posts. Lo convertimos en un editor WYSIWYG con CKEditor:

<template>
 <div class="col-span-6">
     <jet-label value="Text" />
     <ckeditor :editor="editor.editor" v-model="form.text"></ckeditor>
     <jet-input-error :message="errors.text" />
 </div>
</template>
<script>
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
export default {
 components: {
   ClassicEditor
 },
 data() {
   return {
     editor: {
       editor: ClassicEditor
     }
   };
 },
};
</script>

Importamos el Classic Editor:

import ClassicEditor from "@ckeditor/ckeditor5-build-classic";

Creamos el componente <ckeditor> y asociamos el v-model a nuestro campo de formulario:

<ckeditor :editor="editor.editor" v-model="form.text"></ckeditor>

Con esto, cualquier cambio realizado en el editor se reflejará en form.text, listo para enviarse al servidor y guardarse en la base de datos.

CKEditor
CKEditor

CSS con Tailwind

Al estar usando Tailwind en la aplicación, perdemos es estilo aplicado por defecto, como los tamaños de los títulos, listados, etc, por lo tanto, para recuperar este estilo, definimos un CSS como el siguiente:

resources/css/app.css

/* CKEDITOR */
.ck-editor__editable_inline {
    min-height: 400px;
}

.ck-editor__main h1 {
    font-size: 40px;
}

.ck-editor__main h2 {
    font-size: 30px;
}

.ck-editor__main h3 {
    font-size: 25px;
}

.ck-editor__main h4 {
    font-size: 20px;
}

.ck-editor__main ul {
    list-style-type: circle;
    margin: 10px;
    padding: 10px;
}

.ck-editor__main ol {
    list-style-type: decimal;
    margin: 10px;
    padding: 10px;
}
/* CKEDITOR */

Conclusiones finales

Integrar CKEditor 5 en Laravel es más sencillo de lo que parece cuando entiendes cuatro cosas:

  1. Vite debe compilar JS y CSS del editor.
  2. Debes inicializar correctamente el editor en Blade.
  3. Debes configurar un adaptador de subida (SimpleUploadAdapter) para evitar errores.
  4. Genera tu build personalizado desde la web de CKEditor

Yo lo uso en proyectos reales, es un plugin estupendo que lo e usado por muchos años, especialmente para blogs, y puedo decirte que vale la pena dedicarle unos minutos a configurarlo. Una vez lo tienes listo, es estable, elegante y tremendamente útil.

Preguntas frecuentes sobre CKEditor 5 en Laravel

  • ¿Cómo instalar correctamente CKEditor 5?
    • Con NPM: npm i ckeditor5, crear archivos JS/CSS y configurarlos en Vite.
  • ¿Por qué CKEditor muestra un error 419?
    • Fallo en CSRF. Asegúrate de leer el token desde el <meta> y enviarlo en headers.
  • ¿Cómo activar el arrastrar y soltar imágenes?
    • Incluye SimpleUploadAdapter y define tu uploadUrl.
  • ¿Qué plugins son imprescindibles para un blog?
    • Bold, Italic, Headings, CodeBlock, Image, ImageUpload, Table.

Siguiente paso, aprende a usar el cliente HTTP de Laravel.

Acepto recibir anuncios de interes sobre este Blog.

Vamos a embeber el CKEditor como parte del formulario para que ahora el contenido del post sea enriquecido/html.

| 👤 Andrés Cruz

🇺🇸 In english